8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-31 17:23:03 +01:00
firebird-mirror/src/dsql/DdlNodes.epp
dimitr 81466c3768 1) Fixed XCP_MESSAGE_LENGTH to represent the real max length. We have the column defined as VARCHAR(1023), so there's no need to subtract the overhead.
2) Slightly refactored the message buffer management.
3) Fixed the buffer overrun in the release build.
2013-07-10 11:44:39 +00:00

10189 lines
282 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 "dyn_consts.h"
#include "../dsql/DdlNodes.h"
#include "../dsql/BoolNodes.h"
#include "../dsql/ExprNodes.h"
#include "../dsql/StmtNodes.h"
#include "../jrd/blr.h"
#include "../jrd/btr.h"
#include "../jrd/dyn.h"
#include "../jrd/flags.h"
#include "../jrd/intl.h"
#include "../jrd/jrd.h"
#include "../jrd/msg_encode.h"
#include "../jrd/obj.h"
#include "../jrd/ods.h"
#include "../jrd/tra.h"
#include "../common/os/path_utils.h"
#include "../jrd/IntlManager.h"
#include "../jrd/PreparedStatement.h"
#include "../jrd/ResultSet.h"
#include "../jrd/UserManagement.h"
#include "../jrd/blb_proto.h"
#include "../jrd/cmp_proto.h"
#include "../jrd/dfw_proto.h"
#include "../jrd/dpm_proto.h"
#include "../jrd/dyn_ut_proto.h"
#include "../jrd/exe_proto.h"
#include "../jrd/intl_proto.h"
#include "../common/isc_f_proto.h"
#include "../jrd/met_proto.h"
#include "../jrd/scl_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 "../utilities/gsec/gsec.h"
#include "../common/dsc_proto.h"
#include "../common/StatusArg.h"
namespace
{
class DynamicUserData : public Firebird::VersionedIface<Auth::UserData, FB_AUTH_USER_VERSION>
{
public:
#ifdef DEBUG_GDS_ALLOC
void* operator new(size_t size, Firebird::MemoryPool& pool, const char* fileName, int line)
{
return pool.allocate(size, fileName, line);
}
#else // DEBUG_GDS_ALLOC
void* operator new(size_t size, Firebird::MemoryPool& pool)
{
return pool.allocate(size);
}
#endif // DEBUG_GDS_ALLOC
};
} // namespace
namespace Jrd {
using namespace Firebird;
static void checkForeignKeyTempScope(thread_db* tdbb, jrd_tra* transaction,
const MetaName& childRelName, const MetaName& masterIndexName);
static void checkSpTrigDependency(thread_db* tdbb, jrd_tra* transaction,
const MetaName& relationName, const MetaName& fieldName);
static void checkViewDependency(thread_db* tdbb, jrd_tra* transaction,
const MetaName& relationName, const MetaName& fieldName);
static void clearPermanentField(dsql_rel* relation, bool permanent);
static void defineComputed(DsqlCompilerScratch* dsqlScratch, RelationSourceNode* relation,
dsql_fld* field, ValueSourceClause* clause, string& source, BlrDebugWriter::BlrData& value);
static void deleteKeyConstraint(thread_db* tdbb, jrd_tra* transaction,
const MetaName& relationName, const MetaName& constraintName, const MetaName& indexName);
static void defineFile(thread_db* tdbb, jrd_tra* transaction, SLONG shadowNumber, bool manualShadow,
bool conditionalShadow, SLONG& dbAlloc,
const PathName& name, SLONG start, SLONG length);
static bool fieldExists(thread_db* tdbb, jrd_tra* transaction, const MetaName& relationName,
const MetaName& fieldName);
static bool isItSqlRole(thread_db* tdbb, jrd_tra* transaction, const MetaName& inputName,
MetaName& outputName);
static void makeRelationScopeName(const MetaName& name, const rel_t type, string& message);
static void modifyLocalFieldPosition(thread_db* tdbb, jrd_tra* transaction,
const MetaName& relationName, const MetaName& fieldName, USHORT newPosition,
USHORT existingPosition);
static rel_t relationType(SSHORT relationTypeNull, SSHORT relationType);
static void saveField(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, const MetaName& fieldName);
static void saveRelation(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
const MetaName& relationName, bool view, bool creating);
static void updateRdbFields(const TypeClause* type,
SSHORT& fieldType,
SSHORT& fieldLength,
SSHORT& fieldSubTypeNull, SSHORT& fieldSubType,
SSHORT& fieldScaleNull, SSHORT& fieldScale,
SSHORT& characterSetIdNull, SSHORT& characterSetId,
SSHORT& characterLengthNull, SSHORT& characterLength,
SSHORT& fieldPrecisionNull, SSHORT& fieldPrecision,
SSHORT& collationIdNull, SSHORT& collationId,
SSHORT& segmentLengthNull, SSHORT& segmentLength);
static const char* const CHECK_CONSTRAINT_EXCEPTION = "check_constraint";
DATABASE DB = STATIC "ODS.RDB";
//----------------------
// Check temporary table reference rules between given child relation and master
// relation (owner of given PK/UK index).
static void checkForeignKeyTempScope(thread_db* tdbb, jrd_tra* transaction,
const MetaName& childRelName, const MetaName& masterIndexName)
{
AutoCacheRequest request(tdbb, drq_l_rel_info, DYN_REQUESTS);
bool error = false;
string master, child;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
RLC_M IN RDB$RELATION_CONSTRAINTS CROSS
REL_C IN RDB$RELATIONS CROSS
REL_M IN RDB$RELATIONS
WITH (RLC_M.RDB$CONSTRAINT_TYPE EQ UNIQUE_CNSTRT OR
RLC_M.RDB$CONSTRAINT_TYPE EQ PRIMARY_KEY) AND
RLC_M.RDB$INDEX_NAME EQ masterIndexName.c_str() AND
REL_C.RDB$RELATION_NAME EQ childRelName.c_str() AND
REL_M.RDB$RELATION_NAME EQ RLC_M.RDB$RELATION_NAME
{
const rel_t masterType = relationType(REL_M.RDB$RELATION_TYPE.NULL, REL_M.RDB$RELATION_TYPE);
fb_assert(masterType == rel_persistent ||
masterType == rel_global_temp_preserve ||
masterType == rel_global_temp_delete);
const rel_t childType = relationType(REL_C.RDB$RELATION_TYPE.NULL, REL_C.RDB$RELATION_TYPE);
fb_assert(childType == rel_persistent ||
childType == rel_global_temp_preserve ||
childType == rel_global_temp_delete);
error = masterType != childType &&
!(masterType == rel_global_temp_preserve && childType == rel_global_temp_delete);
if (error)
{
makeRelationScopeName(masterIndexName, masterType, master);
makeRelationScopeName(childRelName, childType, child);
}
}
END_FOR
if (error)
{
// Msg 232 : "%s can't reference %s"
status_exception::raise(Arg::PrivateDyn(232) << child << master);
}
}
// Check temporary table reference rules between just created child relation and all
// its master relations.
static void checkRelationTempScope(thread_db* tdbb, jrd_tra* transaction,
const MetaName& childRelName, const rel_t childType)
{
if (childType != rel_persistent &&
childType != rel_global_temp_preserve &&
childType != rel_global_temp_delete)
{
return;
}
AutoCacheRequest request(tdbb, drq_l_rel_info2, DYN_REQUESTS);
bool error = false;
string master, child;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
RLC_C IN RDB$RELATION_CONSTRAINTS CROSS
IND_C IN RDB$INDICES CROSS
IND_M IN RDB$INDICES CROSS
REL_M IN RDB$RELATIONS
WITH RLC_C.RDB$CONSTRAINT_TYPE EQ FOREIGN_KEY AND
RLC_C.RDB$RELATION_NAME EQ childRelName.c_str() AND
IND_C.RDB$INDEX_NAME EQ RLC_C.RDB$INDEX_NAME AND
IND_M.RDB$INDEX_NAME EQ IND_C.RDB$FOREIGN_KEY AND
IND_M.RDB$RELATION_NAME EQ REL_M.RDB$RELATION_NAME
{
const rel_t masterType = relationType(REL_M.RDB$RELATION_TYPE.NULL, REL_M.RDB$RELATION_TYPE);
fb_assert(masterType == rel_persistent ||
masterType == rel_global_temp_preserve ||
masterType == rel_global_temp_delete);
error = masterType != childType &&
!(masterType == rel_global_temp_preserve && childType == rel_global_temp_delete);
if (error)
{
makeRelationScopeName(REL_M.RDB$RELATION_NAME, masterType, master);
makeRelationScopeName(childRelName, childType, child);
}
}
END_FOR
if (error)
{
// Msg 232 : "%s can't reference %s"
status_exception::raise(Arg::PrivateDyn(232) << child << master);
}
}
// Checks to see if the given field is referenced in a stored procedure or trigger.
// If the field is referenced, throw.
static void checkSpTrigDependency(thread_db* tdbb, jrd_tra* transaction,
const MetaName& relationName, const MetaName& fieldName)
{
AutoRequest request;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
FIRST 1
DEP IN RDB$DEPENDENCIES
WITH DEP.RDB$DEPENDED_ON_NAME EQ relationName.c_str() AND
DEP.RDB$FIELD_NAME EQ fieldName.c_str()
{
MetaName depName(DEP.RDB$DEPENDENT_NAME);
// msg 206: Column %s from table %s is referenced in %s.
status_exception::raise(
Arg::PrivateDyn(206) << fieldName << relationName << depName);
}
END_FOR
}
// Checks to see if the given field is referenced in a view. If it is, throw.
static void checkViewDependency(thread_db* tdbb, jrd_tra* transaction,
const MetaName& relationName, const MetaName& fieldName)
{
AutoRequest request;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
FIRST 1
X IN RDB$RELATION_FIELDS CROSS
Y IN RDB$RELATION_FIELDS CROSS
Z IN RDB$VIEW_RELATIONS
WITH X.RDB$RELATION_NAME EQ relationName.c_str() AND
X.RDB$FIELD_NAME EQ fieldName.c_str() AND
X.RDB$FIELD_NAME EQ Y.RDB$BASE_FIELD AND
X.RDB$FIELD_SOURCE EQ Y.RDB$FIELD_SOURCE AND
Y.RDB$RELATION_NAME EQ Z.RDB$VIEW_NAME AND
X.RDB$RELATION_NAME EQ Z.RDB$RELATION_NAME AND
Y.RDB$VIEW_CONTEXT EQ Z.RDB$VIEW_CONTEXT
{
MetaName viewName(Z.RDB$VIEW_NAME);
// msg 206: Column %s from table %s is referenced in %s.
status_exception::raise(
Arg::PrivateDyn(206) << fieldName << relationName << viewName);
}
END_FOR
}
// Removes temporary pool pointers from field, stored in permanent cache.
static void clearPermanentField(dsql_rel* relation, bool permanent)
{
if (relation && relation->rel_fields && permanent)
{
relation->rel_fields->fld_procedure = NULL;
relation->rel_fields->ranges = NULL;
relation->rel_fields->charSet = NULL;
relation->rel_fields->subTypeName = NULL;
relation->rel_fields->fld_relation = relation;
}
}
// Define a COMPUTED BY clause, for a field or an index.
void defineComputed(DsqlCompilerScratch* dsqlScratch, RelationSourceNode* relation, dsql_fld* field,
ValueSourceClause* clause, string& source, BlrDebugWriter::BlrData& value)
{
// Get the table node and set up correct context.
dsqlScratch->resetContextStack();
// Save the size of the field if it is specified.
dsc saveDesc;
saveDesc.dsc_dtype = 0;
if (field && field->dtype)
{
fb_assert(field->dtype <= MAX_UCHAR);
saveDesc.dsc_dtype = (UCHAR) field->dtype;
saveDesc.dsc_length = field->length;
fb_assert(field->scale <= MAX_SCHAR);
saveDesc.dsc_scale = (SCHAR) field->scale;
saveDesc.dsc_sub_type = field->subType;
field->dtype = 0;
field->length = 0;
field->scale = 0;
field->subType = 0;
}
PASS1_make_context(dsqlScratch, relation);
ValueExprNode* input = Node::doDsqlPass(dsqlScratch, clause->value);
// Try to calculate size of the computed field. The calculated size
// may be ignored, but it will catch self references.
dsc desc;
MAKE_desc(dsqlScratch, &desc, input);
// Generate the blr expression.
dsqlScratch->getBlrData().clear();
dsqlScratch->getDebugData().clear();
dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5);
GEN_expr(dsqlScratch, input);
dsqlScratch->appendUChar(blr_eoc);
if (saveDesc.dsc_dtype)
{
// Restore the field size/type overrides.
field->dtype = saveDesc.dsc_dtype;
field->length = saveDesc.dsc_length;
field->scale = saveDesc.dsc_scale;
if (field->dtype <= dtype_any_text)
{
field->charSetId = DSC_GET_CHARSET(&saveDesc);
field->collationId = DSC_GET_COLLATE(&saveDesc);
}
else
field->subType = saveDesc.dsc_sub_type;
}
else if (field)
{
// Use size calculated.
field->dtype = desc.dsc_dtype;
field->length = desc.dsc_length;
field->scale = desc.dsc_scale;
if (field->dtype <= dtype_any_text)
{
field->charSetId = DSC_GET_CHARSET(&desc);
field->collationId = DSC_GET_COLLATE(&desc);
}
else
field->subType = desc.dsc_sub_type;
}
dsqlScratch->resetContextStack();
// Generate the source text.
source = clause->source;
value.assign(dsqlScratch->getBlrData());
}
// Delete a record from RDB$RELATION_CONSTRAINTS based on a constraint name.
//
// On deleting from RDB$RELATION_CONSTRAINTS, 2 system triggers fire:
//
// (A) pre delete trigger: pre_delete_constraint, will:
//
// 1. delete a record first from RDB$REF_CONSTRAINTS where
// RDB$REF_CONSTRAINTS.RDB$CONSTRAINT_NAME =
// RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME
//
// (B) post delete trigger: post_delete_constraint will:
//
// 1. also delete a record from RDB$INDICES where
// RDB$INDICES.RDB$INDEX_NAME =
// RDB$RELATION_CONSTRAINTS.RDB$INDEX_NAME
//
// 2. also delete a record from RDB$INDEX_SEGMENTS where
// RDB$INDEX_SEGMENTS.RDB$INDEX_NAME =
// RDB$RELATION_CONSTRAINTS.RDB$INDEX_NAME
static void deleteKeyConstraint(thread_db* tdbb, jrd_tra* transaction,
const MetaName& relationName, const MetaName& constraintName, const MetaName& indexName)
{
SET_TDBB(tdbb);
AutoCacheRequest request(tdbb, drq_e_rel_const, DYN_REQUESTS);
bool found = false;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
RC IN RDB$RELATION_CONSTRAINTS
WITH RC.RDB$CONSTRAINT_NAME EQ constraintName.c_str() AND
RC.RDB$CONSTRAINT_TYPE EQ FOREIGN_KEY AND
RC.RDB$RELATION_NAME EQ relationName.c_str() AND
RC.RDB$INDEX_NAME EQ indexName.c_str()
{
found = true;
ERASE RC;
}
END_FOR
if (!found)
{
// msg 130: "CONSTRAINT %s does not exist."
status_exception::raise(
Arg::PrivateDyn(130) << constraintName);
}
}
// Define a database or shadow file.
static void defineFile(thread_db* tdbb, jrd_tra* transaction, SLONG shadowNumber, bool manualShadow,
bool conditionalShadow, SLONG& dbAlloc, const PathName& name, SLONG start, SLONG length)
{
PathName expandedName = name;
if (!ISC_expand_filename(expandedName, false))
status_exception::raise(Arg::PrivateDyn(231)); // File name is invalid.
if (tdbb->getDatabase()->dbb_filename == expandedName)
status_exception::raise(Arg::PrivateDyn(166));
AutoCacheRequest request(tdbb, drq_l_files, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
FIRST 1 X IN RDB$FILES
WITH X.RDB$FILE_NAME EQ expandedName.c_str()
{
status_exception::raise(Arg::PrivateDyn(166));
}
END_FOR
request.reset(tdbb, drq_s_files, DYN_REQUESTS);
STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
X IN RDB$FILES
{
expandedName.copyTo(X.RDB$FILE_NAME, sizeof(X.RDB$FILE_NAME));
X.RDB$SHADOW_NUMBER = shadowNumber;
X.RDB$FILE_FLAGS = (manualShadow ? FILE_manual : 0) |
(conditionalShadow ? FILE_conditional : 0);
dbAlloc = MAX(dbAlloc, start);
X.RDB$FILE_START = dbAlloc;
X.RDB$FILE_LENGTH = length;
dbAlloc += length;
}
END_STORE
}
// Checks to see if the given field already exists in a relation.
static bool fieldExists(thread_db* tdbb, jrd_tra* transaction, const MetaName& relationName,
const MetaName& fieldName)
{
AutoRequest request;
bool found = false;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
FLD IN RDB$RELATION_FIELDS
WITH FLD.RDB$RELATION_NAME EQ relationName.c_str() AND
FLD.RDB$FIELD_NAME EQ fieldName.c_str()
{
found = true;
}
END_FOR
return found;
}
// If inputName is found in RDB$ROLES, then returns true. Otherwise returns false.
static bool isItSqlRole(thread_db* tdbb, jrd_tra* transaction, const MetaName& inputName,
MetaName& outputName)
{
AutoCacheRequest request(tdbb, drq_get_role_nm, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
X IN RDB$ROLES
WITH X.RDB$ROLE_NAME EQ inputName.c_str()
{
outputName = X.RDB$OWNER_NAME;
return true;
}
END_FOR
return false;
}
// Make string with relation name and type of its temporary scope.
static void makeRelationScopeName(const MetaName& name, const rel_t type, string& message)
{
const char* scope = NULL;
if (type == rel_global_temp_preserve)
scope = REL_SCOPE_GTT_PRESERVE;
else if (type == rel_global_temp_delete)
scope = REL_SCOPE_GTT_DELETE;
else
scope = REL_SCOPE_PERSISTENT;
message.printf(scope, name.c_str());
}
// Alters the position of a field with respect to the
// other fields in the relation. This will only affect
// the order in which the fields will be returned when either
// viewing the relation or performing select * from the relation.
//
// The rules of engagement are as follows:
// if new_position > original position
// increase RDB$FIELD_POSITION for all fields with RDB$FIELD_POSITION
// between the new_position and existing position of the field
// then update the position of the field being altered.
// just update the position
//
// if new_position < original position
// decrease RDB$FIELD_POSITION for all fields with RDB$FIELD_POSITION
// between the new_position and existing position of the field
// then update the position of the field being altered.
//
// if new_position == original_position -- no_op
static void modifyLocalFieldPosition(thread_db* tdbb, jrd_tra* transaction,
const MetaName& relationName, const MetaName& fieldName, USHORT newPosition,
USHORT existingPosition)
{
// Make sure that there are no duplicate field positions and no gaps in the position sequence.
// (gaps are introduced when fields are removed)
AutoRequest request;
USHORT newPos = 0;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
FLD IN RDB$RELATION_FIELDS
WITH FLD.RDB$RELATION_NAME EQ relationName.c_str()
SORTED BY ASCENDING FLD.RDB$FIELD_POSITION
{
if (FLD.RDB$FIELD_POSITION != newPos)
{
MODIFY FLD USING
FLD.RDB$FIELD_POSITION = newPos;
END_MODIFY
}
++newPos;
}
END_FOR
// Find the position of the last field in the relation.
SLONG maxPosition = -1;
DYN_UTIL_generate_field_position(tdbb, relationName, &maxPosition);
// If the existing position of the field is less than the new position of
// the field, subtract 1 to move the fields to their new positions otherwise,
// increase the value in RDB$FIELD_POSITION by one.
const bool moveDown = (existingPosition < newPosition);
// Retrieve the records for the fields which have a position between the
// existing field position and the new field position.
request.reset();
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
FLD IN RDB$RELATION_FIELDS
WITH FLD.RDB$RELATION_NAME EQ relationName.c_str() AND
FLD.RDB$FIELD_POSITION >= MIN(newPosition, existingPosition) AND
FLD.RDB$FIELD_POSITION <= MAX(newPosition, existingPosition)
{
MODIFY FLD USING
// If the field is the one we want, change the position, otherwise
// increase the value of RDB$FIELD_POSITION.
if (fieldName == FLD.RDB$FIELD_NAME)
{
if (newPosition > maxPosition)
{
// This prevents gaps in the position sequence of the fields.
FLD.RDB$FIELD_POSITION = maxPosition;
}
else
FLD.RDB$FIELD_POSITION = newPosition;
}
else
{
if (moveDown)
FLD.RDB$FIELD_POSITION = FLD.RDB$FIELD_POSITION - 1;
else
FLD.RDB$FIELD_POSITION = FLD.RDB$FIELD_POSITION + 1;
}
FLD.RDB$FIELD_POSITION.NULL = FALSE;
END_MODIFY
}
END_FOR
}
// Convert RDB$RELATION_TYPE to rel_t type.
static rel_t relationType(SSHORT relationTypeNull, SSHORT relationType)
{
return relationTypeNull ? rel_persistent : rel_t(relationType);
}
// Save the name of a field in the relation or view currently being defined. This is done to support
// definition of triggers which will depend on the metadata created in this statement.
static void saveField(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, const MetaName& fieldName)
{
dsql_rel* relation = dsqlScratch->relation;
if (!relation)
return;
MemoryPool& p = relation->rel_flags & REL_new_relation ?
*tdbb->getDefaultPool() : dsqlScratch->getAttachment()->dbb_pool;
dsql_fld* field = FB_NEW(p) dsql_fld(p);
field->fld_name = fieldName.c_str();
field->fld_next = relation->rel_fields;
relation->rel_fields = field;
}
// Save the name of the relation or view currently being defined. This is done to support definition
// of triggers which will depend on the metadata created in this statement.
static void saveRelation(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
const MetaName& relationName, bool view, bool creating)
{
if (dsqlScratch->flags & DsqlCompilerScratch::FLAG_METADATA_SAVED)
return;
dsqlScratch->flags |= DsqlCompilerScratch::FLAG_METADATA_SAVED;
dsql_rel* relation;
if (!view && !creating)
relation = METD_get_relation(dsqlScratch->getTransaction(), dsqlScratch, relationName);
else
{
MemoryPool& pool = *tdbb->getDefaultPool();
relation = FB_NEW(pool) dsql_rel(pool);
relation->rel_name = relationName;
if (!view)
relation->rel_flags = REL_creating;
}
dsqlScratch->relation = relation;
}
// Update RDB$FIELDS received by reference.
static void updateRdbFields(const TypeClause* type,
SSHORT& fieldType,
SSHORT& fieldLength,
SSHORT& fieldSubTypeNull, SSHORT& fieldSubType,
SSHORT& fieldScaleNull, SSHORT& fieldScale,
SSHORT& characterSetIdNull, SSHORT& characterSetId,
SSHORT& characterLengthNull, SSHORT& characterLength,
SSHORT& fieldPrecisionNull, SSHORT& fieldPrecision,
SSHORT& collationIdNull, SSHORT& collationId,
SSHORT& segmentLengthNull, SSHORT& segmentLength)
{
// Initialize all nullable fields.
fieldSubTypeNull = fieldScaleNull = characterSetIdNull = characterLengthNull =
fieldPrecisionNull = collationIdNull = segmentLengthNull = TRUE;
if (type->dtype == dtype_blob)
{
fieldSubTypeNull = FALSE;
fieldSubType = type->subType;
fieldScaleNull = FALSE;
fieldScale = 0;
if (type->subType == isc_blob_text)
{
characterSetIdNull = FALSE;
characterSetId = type->charSetId;
collationIdNull = FALSE;
collationId = type->collationId;
}
if (type->segLength != 0)
{
segmentLengthNull = FALSE;
segmentLength = type->segLength;
}
}
else if (type->dtype <= dtype_any_text)
{
fieldSubTypeNull = FALSE;
fieldSubType = type->subType;
fieldScaleNull = FALSE;
fieldScale = 0;
if (type->charLength != 0)
{
characterLengthNull = FALSE;
characterLength = type->charLength;
}
characterSetIdNull = FALSE;
characterSetId = type->charSetId;
collationIdNull = FALSE;
collationId = type->collationId;
}
else
{
fieldScaleNull = FALSE;
fieldScale = type->scale;
if (DTYPE_IS_EXACT(type->dtype))
{
fieldPrecisionNull = FALSE;
fieldPrecision = type->precision;
fieldSubTypeNull = FALSE;
fieldSubType = type->subType;
}
}
if (type->dtype == dtype_varying)
{
fb_assert(type->length <= MAX_SSHORT);
fieldLength = (SSHORT) (type->length - sizeof(USHORT));
}
else
fieldLength = type->length;
fieldType = blr_dtypes[type->dtype];
}
//----------------------
// Delete a security class.
bool DdlNode::deleteSecurityClass(thread_db* tdbb, jrd_tra* transaction,
const MetaName& secClass)
{
AutoCacheRequest request(tdbb, drq_e_class, DYN_REQUESTS);
bool found = false;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
SC IN RDB$SECURITY_CLASSES
WITH SC.RDB$SECURITY_CLASS EQ secClass.c_str()
{
found = true;
ERASE SC;
}
END_FOR
return found;
}
void DdlNode::storePrivileges(thread_db* tdbb, jrd_tra* transaction,
const MetaName& name, int type,
const char* privileges)
{
Attachment* const attachment = transaction->tra_attachment;
const string& userName = attachment->att_user->usr_user_name;
AutoCacheRequest request(tdbb, drq_s_usr_prvs, DYN_REQUESTS);
for (const char* p = privileges; *p; ++p)
{
STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
X IN RDB$USER_PRIVILEGES
{
strcpy(X.RDB$RELATION_NAME, name.c_str());
strcpy(X.RDB$USER, userName.c_str());
X.RDB$USER_TYPE = obj_user;
X.RDB$OBJECT_TYPE = type;
X.RDB$PRIVILEGE[0] = *p;
X.RDB$PRIVILEGE[1] = 0;
X.RDB$GRANT_OPTION = 1;
}
END_STORE
}
}
void DdlNode::executeDdlTrigger(thread_db* tdbb, jrd_tra* transaction, DdlTriggerWhen when,
int action, const MetaName& objectName, const string& sqlText)
{
Attachment* const 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.eventType = DDL_TRIGGER_ACTION_NAMES[action][0];
context.objectType = DDL_TRIGGER_ACTION_NAMES[action][1];
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, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction, DdlTriggerWhen when, int action, const MetaName& objectName)
{
executeDdlTrigger(tdbb, transaction, when, action, objectName,
*dsqlScratch->getStatement()->getSqlText());
}
void DdlNode::storeGlobalField(thread_db* tdbb, jrd_tra* transaction, MetaName& name,
const TypeClause* field, const string& computedSource, const BlrDebugWriter::BlrData& computedValue)
{
Attachment* const attachment = transaction->tra_attachment;
const string& userName = attachment->att_user->usr_user_name;
const ValueListNode* elements = field->ranges;
const USHORT dims = elements ? elements->items.getCount() / 2 : 0;
if (dims > MAX_ARRAY_DIMENSIONS)
{
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-604) <<
Arg::Gds(isc_dsql_max_arr_dim_exceeded));
}
if (name.isEmpty())
DYN_UTIL_generate_field_name(tdbb, name);
AutoCacheRequest requestHandle(tdbb, drq_s_fld_src, DYN_REQUESTS);
STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
FLD IN RDB$FIELDS
{
FLD.RDB$SYSTEM_FLAG = 0;
strcpy(FLD.RDB$FIELD_NAME, name.c_str());
FLD.RDB$OWNER_NAME.NULL = FALSE;
strcpy(FLD.RDB$OWNER_NAME, userName.c_str());
FLD.RDB$COMPUTED_SOURCE.NULL = TRUE;
FLD.RDB$COMPUTED_BLR.NULL = TRUE;
FLD.RDB$DIMENSIONS.NULL = TRUE;
updateRdbFields(field,
FLD.RDB$FIELD_TYPE,
FLD.RDB$FIELD_LENGTH,
FLD.RDB$FIELD_SUB_TYPE.NULL, FLD.RDB$FIELD_SUB_TYPE,
FLD.RDB$FIELD_SCALE.NULL, FLD.RDB$FIELD_SCALE,
FLD.RDB$CHARACTER_SET_ID.NULL, FLD.RDB$CHARACTER_SET_ID,
FLD.RDB$CHARACTER_LENGTH.NULL, FLD.RDB$CHARACTER_LENGTH,
FLD.RDB$FIELD_PRECISION.NULL, FLD.RDB$FIELD_PRECISION,
FLD.RDB$COLLATION_ID.NULL, FLD.RDB$COLLATION_ID,
FLD.RDB$SEGMENT_LENGTH.NULL, FLD.RDB$SEGMENT_LENGTH);
if (dims != 0)
{
FLD.RDB$DIMENSIONS.NULL = FALSE;
FLD.RDB$DIMENSIONS = dims;
}
if (computedSource.hasData())
{
FLD.RDB$COMPUTED_SOURCE.NULL = FALSE;
attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$COMPUTED_SOURCE,
computedSource);
}
if (computedValue.hasData())
{
FLD.RDB$COMPUTED_BLR.NULL = FALSE;
attachment->storeBinaryBlob(tdbb, transaction, &FLD.RDB$COMPUTED_BLR,
computedValue);
}
}
END_STORE
if (elements) // Is the type an array?
{
requestHandle.reset(tdbb, drq_s_fld_dym, DYN_REQUESTS);
SSHORT position = 0;
const NestConst<ValueExprNode>* ptr = elements->items.begin();
for (const NestConst<ValueExprNode>* const end = elements->items.end();
ptr != end;
++ptr, ++position)
{
const ValueExprNode* element = *ptr++;
const SLONG lrange = element->as<LiteralNode>()->getSlong();
element = *ptr;
const SLONG hrange = element->as<LiteralNode>()->getSlong();
if (lrange >= hrange)
{
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-604) <<
Arg::Gds(isc_dsql_arr_range_error));
}
STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
DIM IN RDB$FIELD_DIMENSIONS
{
strcpy(DIM.RDB$FIELD_NAME, name.c_str());
DIM.RDB$DIMENSION = position;
DIM.RDB$UPPER_BOUND = hrange;
DIM.RDB$LOWER_BOUND = lrange;
}
END_STORE
}
}
storePrivileges(tdbb, transaction, name, obj_field, USAGE_PRIVILEGES);
}
//----------------------
ParameterClause::ParameterClause(MemoryPool& pool, dsql_fld* field, const MetaName& aCollate,
ValueSourceClause* aDefaultClause, ValueExprNode* aParameterExpr)
: name(pool, field ? field->fld_name : ""),
type(field),
defaultClause(aDefaultClause),
parameterExpr(aParameterExpr)
{
type->collate = aCollate;
}
void ParameterClause::print(string& text) const
{
string s;
type->print(s);
text.printf("name: '%s' %s", name.c_str(), s.c_str());
}
//----------------------
void AlterCharSetNode::print(string& text) const
{
text.printf(
"AlterCharSetNode\n"
" charSet: %s\n"
" defaultCollation: %s\n",
charSet.c_str(), defaultCollation.c_str());
}
void AlterCharSetNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
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, dsqlScratch, 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_charset_not_found) << Arg::Str(charSet));
if (!collationFound)
{
status_exception::raise(
Arg::Gds(isc_collation_not_found) << Arg::Str(defaultCollation) << Arg::Str(charSet));
}
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER,
DDL_TRIGGER_ALTER_CHARACTER_SET, charSet);
}
//----------------------
void CommentOnNode::print(string& text) const
{
text.printf(
"CommentOnNode\n"
" objType: %s\n"
" objName: %s\n"
" text: %s\n",
objType, objName.c_str(), this->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.
// In FB3 we added function's arguments.
void CommentOnNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
Attachment* const attachment = transaction->tra_attachment;
const char* tableClause = NULL;
const char* columnClause = NULL;
const char* subColumnClause = NULL;
const char* addWhereClause = NULL;
Arg::StatusVector status;
if (objType == obj_parameter)
{
fb_assert(subName.hasData());
PreparedStatement::Builder sql;
sql << "select 1 from rdb$procedures p join rdb$procedure_parameters pp using (rdb$procedure_name)" <<
"where p.rdb$procedure_name =" << objName <<
"and p.rdb$package_name is null and pp.rdb$package_name is null" <<
"and pp.rdb$parameter_name =" << subName <<
"union all" <<
"select 2 from rdb$functions f join rdb$function_arguments fa using (rdb$function_name)" <<
"where f.rdb$function_name =" << objName <<
"and f.rdb$package_name is null and fa.rdb$package_name is null" <<
"and fa.rdb$argument_name =" << subName;
AutoPreparedStatement ps(attachment->prepareStatement(tdbb, transaction, sql));
AutoResultSet rs(ps->executeQuery(tdbb, transaction));
if (!rs->fetch(tdbb))
{
status_exception::raise(Arg::Gds(isc_dyn_routine_param_not_found) <<
Arg::Str(subName) << Arg::Str(objName));
}
fb_assert(!rs->isNull(1));
dsc& desc = rs->getDesc(1);
fb_assert(desc.dsc_dtype == dtype_long);
const SLONG result = *reinterpret_cast<SLONG*>(desc.dsc_address);
switch (result)
{
case 1:
objType = obj_procedure;
break;
case 2:
objType = obj_udf;
break;
default:
fb_assert(false);
}
if (rs->fetch(tdbb))
{
status_exception::raise(Arg::Gds(isc_dyn_routine_param_ambiguous) <<
Arg::Str(subName) << Arg::Str(objName));
}
}
switch (objType)
{
case obj_database:
tableClause = "rdb$database";
break;
case obj_field:
tableClause = "rdb$fields";
columnClause = "rdb$field_name";
status << Arg::Gds(isc_dyn_domain_not_found);
break;
case obj_relation:
if (subName.hasData())
{
tableClause = "rdb$relation_fields";
subColumnClause = "rdb$field_name";
status << Arg::Gds(isc_dyn_column_does_not_exist) <<
Arg::Str(subName) << Arg::Str(objName);
}
else
{
tableClause = "rdb$relations";
addWhereClause = "rdb$view_blr is null";
status << Arg::Gds(isc_dyn_table_not_found) << Arg::Str(objName);
}
columnClause = "rdb$relation_name";
break;
case obj_view:
tableClause = "rdb$relations";
columnClause = "rdb$relation_name";
status << Arg::Gds(isc_dyn_view_not_found) << Arg::Str(objName);
addWhereClause = "rdb$view_blr is not null";
break;
case obj_procedure:
if (subName.hasData())
{
tableClause = "rdb$procedure_parameters";
subColumnClause = "rdb$parameter_name";
status << Arg::Gds(isc_dyn_proc_param_not_found) <<
Arg::Str(subName) << Arg::Str(objName);
}
else
{
tableClause = "rdb$procedures";
status << Arg::Gds(isc_dyn_proc_not_found) << Arg::Str(objName);
}
addWhereClause = "rdb$package_name is null";
columnClause = "rdb$procedure_name";
break;
case obj_trigger:
tableClause = "rdb$triggers";
columnClause = "rdb$trigger_name";
status << Arg::Gds(isc_dyn_trig_not_found) << Arg::Str(objName);
break;
case obj_udf:
if (subName.hasData())
{
tableClause = "rdb$function_arguments";
subColumnClause = "rdb$argument_name";
status << Arg::Gds(isc_dyn_func_param_not_found) <<
Arg::Str(subName) << Arg::Str(objName);
}
else
{
tableClause = "rdb$functions";
status << Arg::Gds(isc_dyn_func_not_found) << Arg::Str(objName);
}
addWhereClause = "rdb$package_name is null";
columnClause = "rdb$function_name";
break;
case obj_blob_filter:
tableClause = "rdb$filters";
columnClause = "rdb$function_name";
status << Arg::Gds(isc_dyn_filter_not_found) << Arg::Str(objName);
break;
case obj_exception:
tableClause = "rdb$exceptions";
columnClause = "rdb$exception_name";
status << Arg::Gds(isc_dyn_exception_not_found) << Arg::Str(objName);
break;
case obj_generator:
tableClause = "rdb$generators";
columnClause = "rdb$generator_name";
status << Arg::Gds(isc_dyn_gen_not_found) << Arg::Str(objName);
break;
case obj_index:
tableClause = "rdb$indices";
columnClause = "rdb$index_name";
status << Arg::Gds(isc_dyn_index_not_found) << Arg::Str(objName);
break;
case obj_sql_role:
tableClause = "rdb$roles";
columnClause = "rdb$role_name";
status << Arg::Gds(isc_dyn_role_not_found) << Arg::Str(objName);
break;
case obj_charset:
tableClause = "rdb$character_sets";
columnClause = "rdb$character_set_name";
status << Arg::Gds(isc_dyn_charset_not_found) << Arg::Str(objName);
break;
case obj_collation:
tableClause = "rdb$collations";
columnClause = "rdb$collation_name";
status << Arg::Gds(isc_dyn_collation_not_found) << Arg::Str(objName);
break;
case obj_package_header:
tableClause = "rdb$packages";
columnClause = "rdb$package_name";
status << Arg::Gds(isc_dyn_package_not_found) << Arg::Str(objName);
break;
case obj_schema:
tableClause = "rdb$schemas";
columnClause = "rdb$schema_name";
status << Arg::Gds(isc_dyn_schema_not_found) << Arg::Str(objName);
break;
default:
fb_assert(false);
return;
}
Nullable<string> description;
if (text.hasData())
description = text;
PreparedStatement::Builder sql;
sql << "update" << tableClause << "set rdb$description =" << description;
if (columnClause)
{
sql << "where" << columnClause << "=" << objName;
if (subColumnClause)
sql << "and" << subColumnClause << "=" << subName;
}
if (addWhereClause)
sql << "and" << addWhereClause;
AutoPreparedStatement ps(attachment->prepareStatement(tdbb, transaction, sql));
if (ps->executeUpdate(tdbb, transaction) == 0)
status_exception::raise(status);
}
//----------------------
void CreateAlterFunctionNode::print(string& text) 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::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_FUNCTION);
const CompoundStmtNode* 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 NestConst<StmtNode>* ptr = variables->statements.begin();
for (const NestConst<StmtNode>* const end = variables->statements.end(); ptr != end; ++ptr)
{
const DeclareVariableNode* varNode = (*ptr)->as<DeclareVariableNode>();
if (varNode)
{
const dsql_fld* field = varNode->dsqlDef->type;
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 ");
bool hasDefaultParams = false;
// compile default expressions
for (unsigned i = 0; i < parameters.getCount(); ++i)
{
ParameterClause* parameter = parameters[i];
if (parameter->defaultClause)
{
hasDefaultParams = true;
parameter->defaultClause->value = doDsqlPass(dsqlScratch, parameter->defaultClause->value);
}
else if (hasDefaultParams)
{
// parameter without default value after parameters with default
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
Arg::Gds(isc_bad_default_value) <<
Arg::Gds(isc_invalid_clause) << Arg::Str("defaults must be last"));
}
}
for (unsigned i = 0; i < parameters.getCount(); ++i)
parameters[i]->type->resolve(dsqlScratch);
if (returnType && returnType->type)
returnType->type->resolve(dsqlScratch);
return DdlNode::dsqlPass(dsqlScratch);
}
void CreateAlterFunctionNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
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, dsqlScratch, transaction, false, true))
{
altered = true;
}
else
{
if (create) // create or alter
executeCreate(tdbb, dsqlScratch, transaction);
else
status_exception::raise(Arg::Gds(isc_dyn_func_not_found) << Arg::Str(name));
}
}
else
executeCreate(tdbb, dsqlScratch, transaction);
compile(tdbb, dsqlScratch);
executeAlter(tdbb, dsqlScratch, transaction, true, false); // second pass
if (package.isEmpty())
{
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER,
(altered ? DDL_TRIGGER_ALTER_FUNCTION : DDL_TRIGGER_CREATE_FUNCTION), name);
}
savePoint.release(); // everything is ok
if (alter)
{
// Update DSQL cache
METD_drop_function(transaction, QualifiedName(name, package));
MET_dsql_cache_release(tdbb, SYM_udf, name, package);
}
}
void CreateAlterFunctionNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
Attachment* const attachment = transaction->getAttachment();
const string& userName = attachment->att_user->usr_user_name;
if (package.isEmpty())
{
executeDdlTrigger(tdbb, dsqlScratch, 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());
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, userName.c_str());
}
FUN.RDB$LEGACY_FLAG.NULL = FALSE;
FUN.RDB$LEGACY_FLAG = isUdf() ? TRUE : FALSE;
FUN.RDB$RETURN_ARGUMENT.NULL = FALSE;
FUN.RDB$RETURN_ARGUMENT = 0;
}
END_STORE
break;
}
catch (const status_exception& ex)
{
if (ex.value()[1] != isc_unique_key_violation)
throw;
if (++faults > MAX_SSHORT)
throw;
fb_utils::init_status(tdbb->tdbb_status_vector);
}
}
if (package.isEmpty())
storePrivileges(tdbb, transaction, name, obj_udf, EXEC_PRIVILEGES);
executeAlter(tdbb, dsqlScratch, transaction, false, false);
}
bool CreateAlterFunctionNode::executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction, bool secondPass, bool runTriggers)
{
Attachment* const attachment = transaction->getAttachment();
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_dyn_cannot_mod_sysfunc) << FUN.RDB$FUNCTION_NAME);
}
if (!secondPass && runTriggers && package.isEmpty())
{
executeDdlTrigger(tdbb, dsqlScratch, 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$MODULE_NAME.NULL = TRUE;
FUN.RDB$ENTRYPOINT.NULL = TRUE;
FUN.RDB$VALID_BLR.NULL = 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);
FUN.RDB$DETERMINISTIC_FLAG.NULL = FALSE;
FUN.RDB$DETERMINISTIC_FLAG = deterministic ? TRUE : FALSE;
if (isUdf())
{
dsql_fld* field = returnType ? returnType->type : NULL;
if (field)
{
// CVC: This is case of "returns <type> [by value|reference]".
// Some data types can not be returned as value.
if (returnType->udfMechanism.value == FUN_value &&
(field->dtype == dtype_text || field->dtype == dtype_varying ||
field->dtype == dtype_cstring || field->dtype == dtype_blob ||
field->dtype == dtype_timestamp))
{
// Return mode by value not allowed for this data type.
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_return_mode_err));
}
// For functions returning a blob, coerce return argument position to
// be the last parameter.
if (field->dtype == dtype_blob)
{
const size_t blobPosition = parameters.getCount() + 1;
if (blobPosition > MAX_UDF_ARGUMENTS)
{
// External functions can not have more than 10 parameters
// Or 9 if the function returns a BLOB.
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_extern_func_err));
}
FUN.RDB$RETURN_ARGUMENT = (SSHORT) blobPosition;
}
else
FUN.RDB$RETURN_ARGUMENT = 0;
}
else // CVC: This is case of "returns parameter <N>"
{
// Function modifies an argument whose value is the function return value.
if (udfReturnPos < 1 || ULONG(udfReturnPos) > parameters.getCount())
{
// CVC: We should devise new msg "position should be between 1 and #params";
// here it is: dsql_udf_return_pos_err
// External functions can not have more than 10 parameters
// Not strictly correct -- return position error
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
Arg::Gds(isc_dsql_command_err) << // gds__extern_func_err
Arg::Gds(isc_dsql_udf_return_pos_err) << Arg::Num(parameters.getCount()));
}
// We'll verify that SCALAR_ARRAY can't be used as a return type.
// The support for SCALAR_ARRAY is only for input parameters.
if (parameters[udfReturnPos - 1]->udfMechanism.specified &&
parameters[udfReturnPos - 1]->udfMechanism.value == FUN_scalar_array)
{
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_random) << "BY SCALAR_ARRAY can't be used as a return parameter");
}
FUN.RDB$RETURN_ARGUMENT = (SSHORT) udfReturnPos;
}
}
}
if (external)
{
if (!secondPass)
{
FUN.RDB$ENGINE_NAME.NULL = (SSHORT) external->engine.isEmpty();
external->engine.copyTo(FUN.RDB$ENGINE_NAME, sizeof(FUN.RDB$ENGINE_NAME));
if (external->udfModule.length() >= sizeof(FUN.RDB$MODULE_NAME))
status_exception::raise(Arg::Gds(isc_dyn_name_longer));
FUN.RDB$MODULE_NAME.NULL = (SSHORT) external->udfModule.isEmpty();
external->udfModule.copyTo(FUN.RDB$MODULE_NAME, sizeof(FUN.RDB$MODULE_NAME));
if (external->name.length() >= sizeof(FUN.RDB$ENTRYPOINT))
status_exception::raise(Arg::Gds(isc_dyn_name_longer));
FUN.RDB$ENTRYPOINT.NULL = (SSHORT) external->name.isEmpty();
external->name.copyTo(FUN.RDB$ENTRYPOINT, sizeof(FUN.RDB$ENTRYPOINT));
}
}
else if (body)
{
if (secondPass)
{
FUN.RDB$VALID_BLR.NULL = FALSE;
FUN.RDB$VALID_BLR = TRUE;
FUN.RDB$FUNCTION_BLR.NULL = FALSE;
attachment->storeBinaryBlob(tdbb, transaction, &FUN.RDB$FUNCTION_BLR,
dsqlScratch->getBlrData());
FUN.RDB$DEBUG_INFO.NULL = FALSE;
attachment->storeBinaryBlob(tdbb, transaction, &FUN.RDB$DEBUG_INFO,
dsqlScratch->getDebugData());
}
}
if (package.hasData())
{
FUN.RDB$PRIVATE_FLAG.NULL = FALSE;
FUN.RDB$PRIVATE_FLAG = privateScope;
}
else
FUN.RDB$PRIVATE_FLAG.NULL = TRUE;
modified = true;
END_MODIFY
}
END_FOR
if (!secondPass && modified)
{
// Get all comments from the old parameter list.
MetaNameBidMap comments;
collectParamComments(tdbb, transaction, comments);
// delete all old arguments
DropFunctionNode::dropArguments(tdbb, transaction, name, package);
// and insert the new ones
if (returnType && returnType->type)
storeArgument(tdbb, dsqlScratch, transaction, 0, returnType, NULL);
for (size_t i = 0; i < parameters.getCount(); ++i)
{
ParameterClause* parameter = parameters[i];
bid comment;
// Find the original comment to recreate in the new parameter.
if (!comments.get(parameter->name, comment))
comment.clear();
storeArgument(tdbb, dsqlScratch, transaction, i + 1, parameter, &comment);
}
}
return modified;
}
void CreateAlterFunctionNode::storeArgument(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction, unsigned pos, ParameterClause* parameter,
const bid* comment)
{
Attachment* const attachment = transaction->getAttachment();
TypeClause* type = parameter->type;
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());
ARG.RDB$ARGUMENT_NAME.NULL = (SSHORT) parameter->name.isEmpty();
strcpy(ARG.RDB$ARGUMENT_NAME, parameter->name.c_str());
ARG.RDB$PACKAGE_NAME.NULL = (SSHORT) package.isEmpty();
strcpy(ARG.RDB$PACKAGE_NAME, package.c_str());
ARG.RDB$SYSTEM_FLAG = 0;
ARG.RDB$SYSTEM_FLAG.NULL = FALSE;
ARG.RDB$ARGUMENT_POSITION.NULL = FALSE;
ARG.RDB$ARGUMENT_POSITION = pos;
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 = TRUE;
if (!isUdf())
{
ARG.RDB$ARGUMENT_MECHANISM.NULL = FALSE;
ARG.RDB$ARGUMENT_MECHANISM = (USHORT) (type->fullDomain || type->typeOfName.isEmpty() ?
prm_mech_normal : prm_mech_type_of);
}
if (type->notNull)
{
ARG.RDB$NULL_FLAG.NULL = FALSE;
ARG.RDB$NULL_FLAG = TRUE;
}
if (type->typeOfTable.isEmpty())
{
if (type->typeOfName.hasData())
{
ARG.RDB$FIELD_SOURCE.NULL = FALSE;
strcpy(ARG.RDB$FIELD_SOURCE, type->typeOfName.c_str());
}
else
{
if (isUdf())
{
SSHORT segmentLength, segmentLengthNull;
ARG.RDB$FIELD_TYPE.NULL = FALSE;
ARG.RDB$FIELD_LENGTH.NULL = FALSE;
updateRdbFields(type,
ARG.RDB$FIELD_TYPE,
ARG.RDB$FIELD_LENGTH,
ARG.RDB$FIELD_SUB_TYPE.NULL, ARG.RDB$FIELD_SUB_TYPE,
ARG.RDB$FIELD_SCALE.NULL, ARG.RDB$FIELD_SCALE,
ARG.RDB$CHARACTER_SET_ID.NULL, ARG.RDB$CHARACTER_SET_ID,
ARG.RDB$CHARACTER_LENGTH.NULL, ARG.RDB$CHARACTER_LENGTH,
ARG.RDB$FIELD_PRECISION.NULL, ARG.RDB$FIELD_PRECISION,
ARG.RDB$COLLATION_ID.NULL, ARG.RDB$COLLATION_ID,
segmentLengthNull, segmentLength);
}
else
{
MetaName fieldName;
storeGlobalField(tdbb, transaction, fieldName, type);
ARG.RDB$FIELD_SOURCE.NULL = FALSE;
strcpy(ARG.RDB$FIELD_SOURCE, fieldName.c_str());
}
}
}
else
{
ARG.RDB$RELATION_NAME.NULL = FALSE;
strcpy(ARG.RDB$RELATION_NAME, type->typeOfTable.c_str());
ARG.RDB$FIELD_NAME.NULL = FALSE;
strcpy(ARG.RDB$FIELD_NAME, type->typeOfName.c_str());
ARG.RDB$FIELD_SOURCE.NULL = FALSE;
strcpy(ARG.RDB$FIELD_SOURCE, type->fieldSource.c_str());
}
// ASF: If we used a collate with a domain or table.column type, write it
// into RDB$FUNCTION_ARGUMENTS.
if (type->collate.hasData() && type->typeOfName.hasData())
{
ARG.RDB$COLLATION_ID.NULL = FALSE;
ARG.RDB$COLLATION_ID = type->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->defaultClause)
{
ARG.RDB$DEFAULT_VALUE.NULL = FALSE;
ARG.RDB$DEFAULT_SOURCE.NULL = FALSE;
attachment->storeMetaDataBlob(tdbb, transaction, &ARG.RDB$DEFAULT_SOURCE,
parameter->defaultClause->source);
dsqlScratch->getBlrData().clear();
if (dsqlScratch->isVersion4())
dsqlScratch->appendUChar(blr_version4);
else
dsqlScratch->appendUChar(blr_version5);
GEN_expr(dsqlScratch, parameter->defaultClause->value);
dsqlScratch->appendUChar(blr_eoc);
attachment->storeBinaryBlob(tdbb, transaction, &ARG.RDB$DEFAULT_VALUE,
dsqlScratch->getBlrData());
}
if (isUdf())
{
ARG.RDB$MECHANISM.NULL = FALSE;
if (pos == 0 && // CVC: This is case of "returns <type> [by value|reference]"
type->dtype == dtype_blob)
{
// CVC: I need to test returning blobs by descriptor before allowing the
// change there. For now, I ignore the return type specification.
const bool freeIt = parameter->udfMechanism.value < 0;
ARG.RDB$MECHANISM = (SSHORT) ((freeIt ? -1 : 1) * FUN_blob_struct);
// If we have the FREE_IT set then the blob has to be freed on return.
}
else if (parameter->udfMechanism.specified)
ARG.RDB$MECHANISM = (SSHORT) parameter->udfMechanism.value;
else if (type->dtype == dtype_blob)
ARG.RDB$MECHANISM = (SSHORT) FUN_blob_struct;
else
ARG.RDB$MECHANISM = (SSHORT) FUN_reference;
}
ARG.RDB$DESCRIPTION.NULL = !comment || comment->isEmpty();
if (!ARG.RDB$DESCRIPTION.NULL)
ARG.RDB$DESCRIPTION = *comment;
}
END_STORE
}
void CreateAlterFunctionNode::compile(thread_db* /*tdbb*/, DsqlCompilerScratch* dsqlScratch)
{
if (invalid)
status_exception::raise(Arg::Gds(isc_dyn_invalid_ddl_func) << name);
if (compiled)
return;
compiled = true;
invalid = true;
if (body)
{
dsqlScratch->beginDebug();
dsqlScratch->getBlrData().clear();
if (dsqlScratch->isVersion4())
dsqlScratch->appendUChar(blr_version4);
else
dsqlScratch->appendUChar(blr_version5);
dsqlScratch->appendUChar(blr_begin);
Array<NestConst<ParameterClause> > returns;
returns.add(returnType);
dsqlScratch->genParameters(parameters, returns);
if (parameters.getCount() != 0)
{
dsqlScratch->appendUChar(blr_receive);
dsqlScratch->appendUChar(0);
}
dsqlScratch->appendUChar(blr_begin);
for (unsigned i = 0; i < parameters.getCount(); ++i)
{
ParameterClause* parameter = parameters[i];
if (parameter->type->fullDomain || parameter->type->notNull)
{
// ASF: To validate input parameters we need only to read its value.
// Assigning it to null is an easy way to do this.
dsqlScratch->appendUChar(blr_assignment);
dsqlScratch->appendUChar(blr_parameter2);
dsqlScratch->appendUChar(0); // input
dsqlScratch->appendUShort(i * 2);
dsqlScratch->appendUShort(i * 2 + 1);
dsqlScratch->appendUChar(blr_null);
}
}
dsql_var* const variable = dsqlScratch->outputVariables[0];
dsqlScratch->putLocalVariable(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);
dsqlScratch->putLocalVariables(localDeclList, 1);
dsqlScratch->loopLevel = 0;
dsqlScratch->cursorNumber = 0;
StmtNode* stmtNode = body->dsqlPass(dsqlScratch);
GEN_hidden_variables(dsqlScratch);
dsqlScratch->appendUChar(blr_stall);
// put a label before body of procedure,
// so that any EXIT statement can get out
dsqlScratch->appendUChar(blr_label);
dsqlScratch->appendUChar(0);
stmtNode->genBlr(dsqlScratch);
dsqlScratch->getStatement()->setType(DsqlCompiledStatement::TYPE_DDL);
dsqlScratch->appendUChar(blr_end);
dsqlScratch->genReturn(false);
dsqlScratch->appendUChar(blr_end);
dsqlScratch->appendUChar(blr_eoc);
dsqlScratch->endDebug();
}
invalid = false;
}
void CreateAlterFunctionNode::collectParamComments(thread_db* tdbb, jrd_tra* transaction,
MetaNameBidMap& items)
{
AutoRequest requestHandle;
FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
ARG IN RDB$FUNCTION_ARGUMENTS
WITH ARG.RDB$FUNCTION_NAME EQ name.c_str() AND
ARG.RDB$PACKAGE_NAME EQUIV NULLIF(package.c_str(), '') AND
ARG.RDB$DESCRIPTION NOT MISSING
{
items.put(ARG.RDB$ARGUMENT_NAME, ARG.RDB$DESCRIPTION);
}
END_FOR
}
//----------------------
void AlterExternalFunctionNode::print(string& text) const
{
text.printf(
"AlterExternalFunctionNode\n"
" name: '%s'\n"
" entrypoint: '%s'\n"
" module: '%s'\n",
name.c_str(), clauses.name.c_str(), clauses.udfModule.c_str());
}
// Allow changing the entry point and/or the module name of a UDF.
void AlterExternalFunctionNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
if (clauses.name.isEmpty() && clauses.udfModule.isEmpty())
{
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-104) /*** FIXME: <<
// Unexpected end of command
Arg::Gds(isc_command_end_err2) << Arg::Num(node->nod_line) <<
Arg::Num(node->nod_column + obj_name->str_length)***/); // + strlen("FUNCTION")
}
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
AutoCacheRequest request(tdbb, drq_m_fun, DYN_REQUESTS);
bool found = false;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
FUN IN RDB$FUNCTIONS
WITH FUN.RDB$FUNCTION_NAME EQ name.c_str() AND
FUN.RDB$PACKAGE_NAME MISSING
{
found = true;
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_FUNCTION, name);
if (!FUN.RDB$ENGINE_NAME.NULL || !FUN.RDB$FUNCTION_BLR.NULL)
status_exception::raise(Arg::Gds(isc_dyn_newfc_oldsyntax) << name);
MODIFY FUN
if (clauses.name.hasData())
{
if (clauses.name.length() >= sizeof(FUN.RDB$ENTRYPOINT))
status_exception::raise(Arg::Gds(isc_dyn_name_longer));
FUN.RDB$ENTRYPOINT.NULL = FALSE;
strcpy(FUN.RDB$ENTRYPOINT, clauses.name.c_str());
}
if (clauses.udfModule.hasData())
{
if (clauses.udfModule.length() >= sizeof(FUN.RDB$MODULE_NAME))
status_exception::raise(Arg::Gds(isc_dyn_name_longer));
FUN.RDB$MODULE_NAME.NULL = FALSE;
strcpy(FUN.RDB$MODULE_NAME, clauses.udfModule.c_str());
}
END_MODIFY
}
END_FOR
if (found)
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_FUNCTION, name);
else
{
// msg 41: "Function %s not found"
status_exception::raise(Arg::PrivateDyn(41) << name);
}
savePoint.release(); // everything is ok
// Update DSQL cache
METD_drop_function(transaction, QualifiedName(name, ""));
MET_dsql_cache_release(tdbb, SYM_udf, name, "");
}
//----------------------
void DropFunctionNode::dropArguments(thread_db* tdbb, jrd_tra* transaction,
const MetaName& functionName, const 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 && ARG.RDB$RELATION_NAME.NULL && ARG.RDB$FIELD_NAME.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 AND
(FLD.RDB$SYSTEM_FLAG EQ 0 OR FLD.RDB$SYSTEM_FLAG MISSING)
{
ERASE FLD;
}
END_FOR
}
ERASE ARG;
}
END_FOR
}
void DropFunctionNode::print(string& text) const
{
text.printf(
"DropFunctionNode\n"
" name: '%s'\n",
name.c_str());
}
DdlNode* DropFunctionNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_FUNCTION);
return DdlNode::dsqlPass(dsqlScratch);
}
void DropFunctionNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
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_dyn_cannot_mod_sysfunc) << FUN.RDB$FUNCTION_NAME);
if (package.isEmpty())
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_DROP_FUNCTION, name);
ERASE FUN;
if (!FUN.RDB$SECURITY_CLASS.NULL)
deleteSecurityClass(tdbb, transaction, FUN.RDB$SECURITY_CLASS);
found = true;
}
END_FOR
if (!found && !silent)
status_exception::raise(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, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_FUNCTION, name);
savePoint.release(); // everything is ok
// Update DSQL cache
METD_drop_function(transaction, QualifiedName(name, package));
MET_dsql_cache_release(tdbb, SYM_udf, name, package);
}
//----------------------
void CreateAlterProcedureNode::print(string& text) 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::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_PROCEDURE);
const CompoundStmtNode* 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 NestConst<StmtNode>* ptr = variables->statements.begin();
for (const NestConst<StmtNode>* const end = variables->statements.end(); ptr != end; ++ptr)
{
const DeclareVariableNode* varNode = (*ptr)->as<DeclareVariableNode>();
if (varNode)
{
const dsql_fld* field = varNode->dsqlDef->type;
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 ");
bool hasDefaultParams = false;
// compile default expressions
for (unsigned i = 0; i < parameters.getCount(); ++i)
{
ParameterClause* parameter = parameters[i];
if (parameter->defaultClause)
{
hasDefaultParams = true;
parameter->defaultClause->value = doDsqlPass(dsqlScratch, parameter->defaultClause->value);
}
else if (hasDefaultParams)
{
// parameter without default value after parameters with default
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
Arg::Gds(isc_bad_default_value) <<
Arg::Gds(isc_invalid_clause) << Arg::Str("defaults must be last"));
}
}
for (unsigned i = 0; i < parameters.getCount(); ++i)
parameters[i]->type->resolve(dsqlScratch);
for (unsigned i = 0; i < returns.getCount(); ++i)
returns[i]->type->resolve(dsqlScratch);
return DdlNode::dsqlPass(dsqlScratch);
}
void CreateAlterProcedureNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
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, dsqlScratch, transaction, false, true))
altered = true;
else
{
if (create) // create or alter
executeCreate(tdbb, dsqlScratch, transaction);
else
status_exception::raise(Arg::Gds(isc_dyn_proc_not_found) << Arg::Str(name));
}
}
else
executeCreate(tdbb, dsqlScratch, transaction);
compile(tdbb, dsqlScratch);
executeAlter(tdbb, dsqlScratch, transaction, true, false); // second pass
if (package.isEmpty())
{
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER,
(altered ? DDL_TRIGGER_ALTER_PROCEDURE : DDL_TRIGGER_CREATE_PROCEDURE), name);
}
savePoint.release(); // everything is ok
if (alter)
{
// Update DSQL cache
METD_drop_procedure(transaction, QualifiedName(name, package));
MET_dsql_cache_release(tdbb, SYM_procedure, name, package);
}
}
void CreateAlterProcedureNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
Attachment* const attachment = transaction->getAttachment();
const string& userName = attachment->att_user->usr_user_name;
if (package.isEmpty())
{
executeDdlTrigger(tdbb, dsqlScratch, 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, userName.c_str());
}
}
END_STORE
break;
}
catch (const status_exception& ex)
{
if (ex.value()[1] != isc_unique_key_violation)
throw;
if (++faults > MAX_SSHORT)
throw;
fb_utils::init_status(tdbb->tdbb_status_vector);
}
}
if (package.isEmpty())
storePrivileges(tdbb, transaction, name, obj_procedure, EXEC_PRIVILEGES);
executeAlter(tdbb, dsqlScratch, transaction, false, false);
}
bool CreateAlterProcedureNode::executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction, bool secondPass, bool runTriggers)
{
Attachment* const 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_dyn_cannot_mod_sysproc) << P.RDB$PROCEDURE_NAME);
if (!secondPass && runTriggers && package.isEmpty())
{
executeDdlTrigger(tdbb, dsqlScratch, 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.NULL = 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_dyn_name_longer));
P.RDB$ENTRYPOINT.NULL = (SSHORT) external->name.isEmpty();
strcpy(P.RDB$ENTRYPOINT, external->name.c_str());
}
}
else if (body)
{
if (secondPass)
{
P.RDB$VALID_BLR.NULL = FALSE;
P.RDB$VALID_BLR = TRUE;
P.RDB$PROCEDURE_BLR.NULL = FALSE;
attachment->storeBinaryBlob(tdbb, transaction, &P.RDB$PROCEDURE_BLR,
dsqlScratch->getBlrData());
P.RDB$DEBUG_INFO.NULL = FALSE;
attachment->storeBinaryBlob(tdbb, transaction, &P.RDB$DEBUG_INFO,
dsqlScratch->getDebugData());
P.RDB$PROCEDURE_TYPE.NULL = FALSE;
P.RDB$PROCEDURE_TYPE = (USHORT)
(statement->getFlags() & DsqlCompiledStatement::FLAG_SELECTABLE ?
prc_selectable : prc_executable);
}
}
modified = true;
END_MODIFY
}
END_FOR
if (!secondPass && modified)
{
// Get all comments from the old parameter list.
MetaNameBidMap comments;
collectParamComments(tdbb, transaction, comments);
// Delete all old input and output parameters.
DropProcedureNode::dropParameters(tdbb, transaction, name, package);
// And insert the new ones.
for (size_t i = 0; i < parameters.getCount(); ++i)
{
ParameterClause* parameter = parameters[i];
bid comment;
// Find the original comment to recreate in the new parameter.
if (!comments.get(parameter->name, comment))
comment.clear();
storeParameter(tdbb, dsqlScratch, transaction, 0, i, parameter, &comment);
}
for (size_t i = 0; i < returns.getCount(); ++i)
{
ParameterClause* parameter = returns[i];
bid comment;
// Find the original comment to recreate in the new parameter.
if (!comments.get(parameter->name, comment))
comment.clear();
storeParameter(tdbb, dsqlScratch, transaction, 1, i, parameter, &comment);
}
AutoCacheRequest requestHandle2(tdbb, drq_m_prm_view, DYN_REQUESTS);
FOR (REQUEST_HANDLE requestHandle2 TRANSACTION_HANDLE transaction)
PRM IN RDB$PROCEDURE_PARAMETERS CROSS
RFR IN RDB$RELATION_FIELDS CROSS
VRL IN RDB$VIEW_RELATIONS
WITH PRM.RDB$PROCEDURE_NAME EQ name.c_str() AND
PRM.RDB$PACKAGE_NAME EQUIV NULLIF(package.c_str(), '') AND
VRL.RDB$RELATION_NAME EQ PRM.RDB$PROCEDURE_NAME AND
VRL.RDB$PACKAGE_NAME EQUIV PRM.RDB$PACKAGE_NAME AND
VRL.RDB$CONTEXT_TYPE EQ VCT_PROCEDURE AND
RFR.RDB$RELATION_NAME EQ VRL.RDB$VIEW_NAME AND
RFR.RDB$VIEW_CONTEXT EQ VRL.RDB$VIEW_CONTEXT AND
RFR.RDB$BASE_FIELD = PRM.RDB$PARAMETER_NAME
{
MODIFY RFR
{
strcpy(RFR.RDB$FIELD_SOURCE, PRM.RDB$FIELD_SOURCE);
}
END_MODIFY
}
END_FOR
}
return modified;
}
void CreateAlterProcedureNode::storeParameter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction, USHORT parameterType, unsigned pos, ParameterClause* parameter,
const bid* comment)
{
Attachment* const attachment = transaction->getAttachment();
TypeClause* type = parameter->type;
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 = parameterType;
PRM.RDB$PARAMETER_MECHANISM.NULL = FALSE;
PRM.RDB$PARAMETER_MECHANISM = (USHORT) (type->fullDomain || type->typeOfName.isEmpty() ?
prm_mech_normal : prm_mech_type_of);
PRM.RDB$NULL_FLAG.NULL = !type->notNull;
PRM.RDB$NULL_FLAG = type->notNull;
PRM.RDB$RELATION_NAME.NULL = type->typeOfTable.isEmpty();
PRM.RDB$FIELD_NAME.NULL = PRM.RDB$RELATION_NAME.NULL || type->typeOfName.isEmpty();
PRM.RDB$FIELD_SOURCE.NULL = FALSE;
if (PRM.RDB$RELATION_NAME.NULL)
{
if (type->typeOfName.hasData())
strcpy(PRM.RDB$FIELD_SOURCE, type->typeOfName.c_str());
else
{
MetaName fieldName;
storeGlobalField(tdbb, transaction, fieldName, type);
strcpy(PRM.RDB$FIELD_SOURCE, fieldName.c_str());
}
}
else
{
strcpy(PRM.RDB$RELATION_NAME, type->typeOfTable.c_str());
strcpy(PRM.RDB$FIELD_NAME, type->typeOfName.c_str());
strcpy(PRM.RDB$FIELD_SOURCE, type->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 = !(type->collate.hasData() && type->typeOfName.hasData());
if (!PRM.RDB$COLLATION_ID.NULL)
PRM.RDB$COLLATION_ID = type->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->defaultClause;
PRM.RDB$DEFAULT_SOURCE.NULL = !parameter->defaultClause;
if (parameter->defaultClause)
{
attachment->storeMetaDataBlob(tdbb, transaction, &PRM.RDB$DEFAULT_SOURCE,
parameter->defaultClause->source);
dsqlScratch->getBlrData().clear();
if (dsqlScratch->isVersion4())
dsqlScratch->appendUChar(blr_version4);
else
dsqlScratch->appendUChar(blr_version5);
GEN_expr(dsqlScratch, parameter->defaultClause->value);
dsqlScratch->appendUChar(blr_eoc);
attachment->storeBinaryBlob(tdbb, transaction, &PRM.RDB$DEFAULT_VALUE,
dsqlScratch->getBlrData());
}
PRM.RDB$DESCRIPTION.NULL = !comment || comment->isEmpty();
if (!PRM.RDB$DESCRIPTION.NULL)
PRM.RDB$DESCRIPTION = *comment;
}
END_STORE
}
void CreateAlterProcedureNode::compile(thread_db* /*tdbb*/, DsqlCompilerScratch* dsqlScratch)
{
if (invalid)
status_exception::raise(Arg::Gds(isc_dyn_invalid_ddl_proc) << name);
if (compiled)
return;
compiled = true;
if (!body)
return;
invalid = true;
dsqlScratch->beginDebug();
dsqlScratch->getBlrData().clear();
if (dsqlScratch->isVersion4())
dsqlScratch->appendUChar(blr_version4);
else
dsqlScratch->appendUChar(blr_version5);
dsqlScratch->appendUChar(blr_begin);
dsqlScratch->genParameters(parameters, returns);
if (parameters.getCount() != 0)
{
dsqlScratch->appendUChar(blr_receive);
dsqlScratch->appendUChar(0);
}
dsqlScratch->appendUChar(blr_begin);
for (unsigned i = 0; i < parameters.getCount(); ++i)
{
ParameterClause* parameter = parameters[i];
if (parameter->type->fullDomain || parameter->type->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.
dsqlScratch->appendUChar(blr_assignment);
dsqlScratch->appendUChar(blr_parameter2);
dsqlScratch->appendUChar(0); // input
dsqlScratch->appendUShort(i * 2);
dsqlScratch->appendUShort(i * 2 + 1);
dsqlScratch->appendUChar(blr_null);
}
}
for (Array<dsql_var*>::const_iterator i = dsqlScratch->outputVariables.begin();
i != dsqlScratch->outputVariables.end();
++i)
{
dsqlScratch->putLocalVariable(*i, 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);
dsqlScratch->putLocalVariables(localDeclList, returns.getCount());
dsqlScratch->loopLevel = 0;
dsqlScratch->cursorNumber = 0;
StmtNode* stmtNode = body->dsqlPass(dsqlScratch);
GEN_hidden_variables(dsqlScratch);
dsqlScratch->appendUChar(blr_stall);
// put a label before body of procedure,
// so that any EXIT statement can get out
dsqlScratch->appendUChar(blr_label);
dsqlScratch->appendUChar(0);
stmtNode->genBlr(dsqlScratch);
dsqlScratch->getStatement()->setType(DsqlCompiledStatement::TYPE_DDL);
dsqlScratch->appendUChar(blr_end);
dsqlScratch->genReturn(true);
dsqlScratch->appendUChar(blr_end);
dsqlScratch->appendUChar(blr_eoc);
dsqlScratch->endDebug();
invalid = false;
}
void CreateAlterProcedureNode::collectParamComments(thread_db* tdbb, jrd_tra* transaction,
MetaNameBidMap& items)
{
AutoRequest requestHandle;
FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
PRM IN RDB$PROCEDURE_PARAMETERS
WITH PRM.RDB$PROCEDURE_NAME EQ name.c_str() AND
PRM.RDB$PACKAGE_NAME EQUIV NULLIF(package.c_str(), '') AND
PRM.RDB$DESCRIPTION NOT MISSING
{
items.put(PRM.RDB$PARAMETER_NAME, PRM.RDB$DESCRIPTION);
}
END_FOR
}
//----------------------
void DropProcedureNode::dropParameters(thread_db* tdbb, jrd_tra* transaction,
const MetaName& procedureName, const 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 && PRM.RDB$RELATION_NAME.NULL && PRM.RDB$FIELD_NAME.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)
{
ERASE FLD;
}
END_FOR
}
ERASE PRM;
}
END_FOR
}
void DropProcedureNode::print(string& text) const
{
text.printf(
"DropProcedureNode\n"
" name: '%s'\n",
name.c_str());
}
DdlNode* DropProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_PROCEDURE);
return DdlNode::dsqlPass(dsqlScratch);
}
void DropProcedureNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
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_dyn_cannot_mod_sysproc) << PRC.RDB$PROCEDURE_NAME);
if (package.isEmpty())
{
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
DDL_TRIGGER_DROP_PROCEDURE, name);
}
ERASE PRC;
if (!PRC.RDB$SECURITY_CLASS.NULL)
deleteSecurityClass(tdbb, transaction, PRC.RDB$SECURITY_CLASS);
found = true;
}
END_FOR
if (!found && !silent)
status_exception::raise(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, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_PROCEDURE, name);
savePoint.release(); // everything is ok
// Update DSQL cache
METD_drop_procedure(transaction, QualifiedName(name, package));
MET_dsql_cache_release(tdbb, SYM_procedure, name, package);
}
//----------------------
void TriggerDefinition::store(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
if (name.isEmpty())
DYN_UTIL_generate_trigger_name(tdbb, transaction, name);
AutoCacheRequest requestHandle(tdbb, drq_s_triggers, DYN_REQUESTS);
STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
TRG IN RDB$TRIGGERS
{
TRG.RDB$SYSTEM_FLAG = SSHORT(systemFlag);
TRG.RDB$FLAGS = TRG_sql | (fkTrigger ? TRG_ignore_perm : 0);
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
modify(tdbb, dsqlScratch, transaction);
}
bool TriggerDefinition::modify(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
Attachment* const 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 &&
TRG.RDB$RELATION_NAME.NULL)
{
status_exception::raise(
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_dsql_db_trigger_type_cant_change));
}
if (systemFlag == fb_sysflag_user && !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_dyn_cant_modify_auto_trig));
break;
case fb_sysflag_system:
status_exception::raise(
Arg::Gds(isc_dyn_cannot_mod_systrig) << TRG.RDB$TRIGGER_NAME);
break;
default:
break;
}
}
preModify(tdbb, dsqlScratch, transaction);
MODIFY TRG
if (blrData.length > 0 || external)
{
fb_assert(!(blrData.length > 0 && 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;
TRG.RDB$VALID_BLR.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_dyn_name_longer));
TRG.RDB$ENTRYPOINT.NULL = (SSHORT) external->name.isEmpty();
strcpy(TRG.RDB$ENTRYPOINT, external->name.c_str());
}
else if (blrData.length > 0)
{
TRG.RDB$VALID_BLR.NULL = FALSE;
TRG.RDB$VALID_BLR = TRUE;
TRG.RDB$TRIGGER_BLR.NULL = FALSE;
attachment->storeBinaryBlob(tdbb, transaction, &TRG.RDB$TRIGGER_BLR, blrData);
}
if (debugData.length > 0)
{
TRG.RDB$DEBUG_INFO.NULL = FALSE;
attachment->storeBinaryBlob(tdbb, transaction, &TRG.RDB$DEBUG_INFO, debugData);
}
if (source.hasData())
{
TRG.RDB$TRIGGER_SOURCE.NULL = FALSE;
attachment->storeMetaDataBlob(tdbb, transaction, &TRG.RDB$TRIGGER_SOURCE, source);
}
modified = true;
END_MODIFY
}
END_FOR
if (modified)
postModify(tdbb, dsqlScratch, transaction);
return modified;
}
//----------------------
void CreateAlterTriggerNode::print(string& text) 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::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
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::dsqlPass(dsqlScratch);
}
void CreateAlterTriggerNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
fb_assert(create || alter);
Attachment* const 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)
{
AutoRequest requestHandle;
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
if (!type.specified)
status_exception::raise(Arg::Gds(isc_dyn_trig_not_found) << Arg::Str(name));
}
compile(tdbb, dsqlScratch);
blrData = dsqlScratch->getBlrData();
debugData = dsqlScratch->getDebugData();
if (alter)
{
if (!modify(tdbb, dsqlScratch, transaction))
{
if (create) // create or alter
executeCreate(tdbb, dsqlScratch, transaction);
else
status_exception::raise(Arg::Gds(isc_dyn_trig_not_found) << Arg::Str(name));
}
}
else
executeCreate(tdbb, dsqlScratch, transaction);
savePoint.release(); // everything is ok
}
void CreateAlterTriggerNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_TRIGGER, name);
store(tdbb, dsqlScratch, transaction);
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_TRIGGER, name);
}
void CreateAlterTriggerNode::compile(thread_db* /*tdbb*/, DsqlCompilerScratch* dsqlScratch)
{
if (invalid)
status_exception::raise(Arg::Gds(isc_dyn_invalid_ddl_trig) << name);
if (compiled)
return;
compiled = true;
invalid = true;
if (body)
{
dsqlScratch->beginDebug();
dsqlScratch->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)
dsqlScratch->resetTriggerContextStack();
if (relationName.hasData())
{
RelationSourceNode* relationNode = FB_NEW(getPool()) RelationSourceNode(getPool(),
relationName);
const string temp = relationNode->alias; // always empty?
if (hasOldContext(type.value))
{
relationNode->alias = OLD_CONTEXT;
dsql_ctx* oldContext = PASS1_make_context(dsqlScratch, relationNode);
oldContext->ctx_flags |= CTX_system;
}
else
dsqlScratch->contextNumber++;
if (hasNewContext(type.value))
{
relationNode->alias = NEW_CONTEXT;
dsql_ctx* newContext = PASS1_make_context(dsqlScratch, relationNode);
newContext->ctx_flags |= CTX_system;
}
else
dsqlScratch->contextNumber++;
relationNode->alias = temp;
}
// generate the trigger blr
if (dsqlScratch->isVersion4())
dsqlScratch->appendUChar(blr_version4);
else
dsqlScratch->appendUChar(blr_version5);
dsqlScratch->appendUChar(blr_begin);
dsqlScratch->setPsql(true);
dsqlScratch->putLocalVariables(localDeclList, 0);
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.
dsqlScratch->appendUChar(blr_label);
dsqlScratch->appendUChar(0);
dsqlScratch->loopLevel = 0;
dsqlScratch->cursorNumber = 0;
body->dsqlPass(dsqlScratch)->genBlr(dsqlScratch);
dsqlScratch->scopeLevel--;
dsqlScratch->appendUChar(blr_end);
dsqlScratch->appendUChar(blr_eoc);
dsqlScratch->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.
dsqlScratch->getStatement()->setType(DsqlCompiledStatement::TYPE_DDL);
}
invalid = false;
}
//----------------------
void DropTriggerNode::print(string& text) const
{
text.printf(
"DropTriggerNode\n"
" name: '%s'\n",
name.c_str());
}
DdlNode* DropTriggerNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_TRIGGER);
return DdlNode::dsqlPass(dsqlScratch);
}
void DropTriggerNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
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_dyn_cant_modify_auto_trig));
break;
case fb_sysflag_system:
status_exception::raise(
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, dsqlScratch, 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_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, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_TRIGGER, name);
savePoint.release(); // everything is ok
}
//----------------------
void CreateCollationNode::print(string& text) const
{
text.printf(
"CreateCollationNode\n"
" name: '%s'\n"
" forCharSet: '%s'\n"
" fromName: '%s'\n"
" fromExternal: '%s'\n"
" attributesOn: %x\n"
" attributesOff: %x\n",
name.c_str(), forCharSet.c_str(), fromName.c_str(), fromExternal.c_str(),
attributesOn, attributesOff);
}
void CreateCollationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
Attachment* const attachment = transaction->tra_attachment;
const string& userName = attachment->att_user->usr_user_name;
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
DDL_TRIGGER_CREATE_COLLATION, name);
AutoCacheRequest request(tdbb, drq_s_colls, DYN_REQUESTS);
STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
X IN RDB$COLLATIONS
{
X.RDB$CHARACTER_SET_ID = forCharSetId;
strcpy(X.RDB$COLLATION_NAME, name.c_str());
X.RDB$SYSTEM_FLAG = 0;
X.RDB$OWNER_NAME.NULL = FALSE;
strcpy(X.RDB$OWNER_NAME, userName.c_str());
X.RDB$SPECIFIC_ATTRIBUTES.NULL = TRUE;
X.RDB$BASE_COLLATION_NAME.NULL = TRUE;
CharSet* cs = INTL_charset_lookup(tdbb, forCharSetId);
SubtypeInfo info;
if (fromName.hasData())
{
if (MET_get_char_coll_subtype_info(tdbb,
INTL_CS_COLL_TO_TTYPE(forCharSetId, fromCollationId), &info) &&
info.specificAttributes.hasData())
{
UCharBuffer temp;
ULONG size = info.specificAttributes.getCount() * cs->maxBytesPerChar();
size = INTL_convert_bytes(tdbb, forCharSetId, temp.getBuffer(size), size,
CS_METADATA, info.specificAttributes.begin(),
info.specificAttributes.getCount(), status_exception::raise);
temp.shrink(size);
info.specificAttributes = temp;
}
strcpy(X.RDB$BASE_COLLATION_NAME, info.baseCollationName.c_str());
X.RDB$BASE_COLLATION_NAME.NULL = FALSE;
}
else if (fromExternal.hasData())
{
strcpy(X.RDB$BASE_COLLATION_NAME, fromExternal.c_str());
X.RDB$BASE_COLLATION_NAME.NULL = FALSE;
}
if (specificAttributes.hasData() && forCharSetId != attachment->att_charset)
{
UCharBuffer temp;
ULONG size = specificAttributes.getCount() * cs->maxBytesPerChar();
size = INTL_convert_bytes(tdbb, forCharSetId, temp.getBuffer(size), size,
attachment->att_charset, specificAttributes.begin(),
specificAttributes.getCount(), status_exception::raise);
temp.shrink(size);
specificAttributes = temp;
}
info.charsetName = forCharSet.c_str();
info.collationName = name;
if (X.RDB$BASE_COLLATION_NAME.NULL)
info.baseCollationName = info.collationName;
else
info.baseCollationName = X.RDB$BASE_COLLATION_NAME;
info.ignoreAttributes = false;
if (!IntlManager::collationInstalled(info.baseCollationName.c_str(),
info.charsetName.c_str()))
{
// msg: 223: "Collation @1 not installed for character set @2"
status_exception::raise(
Arg::PrivateDyn(223) << info.baseCollationName << info.charsetName);
}
IntlUtil::SpecificAttributesMap map;
if (!IntlUtil::parseSpecificAttributes(
cs, info.specificAttributes.getCount(), info.specificAttributes.begin(), &map) ||
!IntlUtil::parseSpecificAttributes(
cs, specificAttributes.getCount(), specificAttributes.begin(), &map))
{
// msg: 222: "Invalid collation attributes"
status_exception::raise(Arg::PrivateDyn(222));
}
const string s = IntlUtil::generateSpecificAttributes(cs, map);
string newSpecificAttributes;
if (!IntlManager::setupCollationAttributes(
info.baseCollationName.c_str(), info.charsetName.c_str(), s,
newSpecificAttributes))
{
// msg: 222: "Invalid collation attributes"
status_exception::raise(Arg::PrivateDyn(222));
}
memcpy(info.specificAttributes.getBuffer(newSpecificAttributes.length()),
newSpecificAttributes.begin(), newSpecificAttributes.length());
if (info.specificAttributes.hasData())
{
X.RDB$SPECIFIC_ATTRIBUTES.NULL = FALSE;
attachment->storeMetaDataBlob(tdbb, transaction, &X.RDB$SPECIFIC_ATTRIBUTES,
string(info.specificAttributes.begin(), info.specificAttributes.getCount()),
forCharSetId);
}
info.attributes = (info.attributes | attributesOn) & (~attributesOff);
X.RDB$COLLATION_ATTRIBUTES = info.attributes;
// Do not allow invalid attributes here.
if (!INTL_texttype_validate(tdbb, &info))
{
// msg: 222: "Invalid collation attributes"
status_exception::raise(Arg::PrivateDyn(222));
}
// ASF: User collations are created with the last number available,
// to minimize the possibility of conflicts with future system collations.
// The greater available number is 126 to avoid signed/unsigned problems.
X.RDB$COLLATION_ID.NULL = TRUE;
X.RDB$COLLATION_ID = 126;
AutoCacheRequest request2(tdbb, drq_l_max_coll_id, DYN_REQUESTS);
FOR(REQUEST_HANDLE request2)
Y IN RDB$COLLATIONS
WITH Y.RDB$CHARACTER_SET_ID = forCharSetId AND
Y.RDB$COLLATION_ID NOT MISSING
SORTED BY DESCENDING Y.RDB$COLLATION_ID
{
if (Y.RDB$COLLATION_ID + 1 <= X.RDB$COLLATION_ID)
{
X.RDB$COLLATION_ID.NULL = FALSE;
break;
}
else
X.RDB$COLLATION_ID = Y.RDB$COLLATION_ID - 1;
}
END_FOR
if (X.RDB$COLLATION_ID.NULL)
status_exception::raise(Arg::Gds(isc_max_coll_per_charset));
}
END_STORE
storePrivileges(tdbb, transaction, name, obj_collation, USAGE_PRIVILEGES);
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER,
DDL_TRIGGER_CREATE_COLLATION, name);
savePoint.release(); // everything is ok
// Update DSQL cache
METD_drop_collation(transaction, name);
MET_dsql_cache_release(tdbb, SYM_intlsym_collation, name);
}
DdlNode* CreateCollationNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
const dsql_intlsym* resolvedCharSet = METD_get_charset(
dsqlScratch->getTransaction(), forCharSet.length(), forCharSet.c_str());
if (!resolvedCharSet)
{
// specified character set not found
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-504) <<
Arg::Gds(isc_charset_not_found) << forCharSet);
}
forCharSetId = resolvedCharSet->intlsym_charset_id;
if (fromName.hasData())
{
const dsql_intlsym* resolvedCollation = METD_get_collation(
dsqlScratch->getTransaction(), fromName, forCharSetId);
if (!resolvedCollation)
{
// Specified collation not found
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
Arg::Gds(isc_collation_not_found) << fromName << forCharSet);
}
fromCollationId = resolvedCollation->intlsym_collate_id;
}
return DdlNode::dsqlPass(dsqlScratch);
}
//----------------------
void DropCollationNode::print(string& text) const
{
text.printf(
"DropCollationNode\n"
" name: '%s'\n",
name.c_str());
}
void DropCollationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
bool found = false;
AutoCacheRequest request(tdbb, drq_e_colls, DYN_REQUESTS);
FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
COLL IN RDB$COLLATIONS
CROSS CS IN RDB$CHARACTER_SETS
WITH COLL.RDB$COLLATION_NAME EQ name.c_str() AND
CS.RDB$CHARACTER_SET_ID EQ COLL.RDB$CHARACTER_SET_ID
{
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
DDL_TRIGGER_DROP_COLLATION, name);
if (!COLL.RDB$SYSTEM_FLAG.NULL && COLL.RDB$SYSTEM_FLAG)
status_exception::raise(Arg::Gds(isc_dyn_cannot_del_syscoll));
if (COLL.RDB$COLLATION_ID == 0 ||
(!CS.RDB$DEFAULT_COLLATE_NAME.NULL &&
MetaName(COLL.RDB$COLLATION_NAME) == MetaName(CS.RDB$DEFAULT_COLLATE_NAME)))
{
fb_utils::exact_name_limit(CS.RDB$CHARACTER_SET_NAME,
sizeof(CS.RDB$CHARACTER_SET_NAME));
status_exception::raise(
Arg::Gds(isc_dyn_cannot_del_def_coll) << CS.RDB$CHARACTER_SET_NAME);
}
found = true;
fb_utils::exact_name_limit(COLL.RDB$COLLATION_NAME, sizeof(COLL.RDB$COLLATION_NAME));
AutoCacheRequest request2(tdbb, drq_l_rfld_coll, DYN_REQUESTS);
FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
RF IN RDB$RELATION_FIELDS
CROSS F IN RDB$FIELDS
WITH RF.RDB$FIELD_SOURCE EQ F.RDB$FIELD_NAME AND
F.RDB$CHARACTER_SET_ID EQ COLL.RDB$CHARACTER_SET_ID AND
RF.RDB$COLLATION_ID EQ COLL.RDB$COLLATION_ID
{
fb_utils::exact_name_limit(RF.RDB$RELATION_NAME, sizeof(RF.RDB$RELATION_NAME));
fb_utils::exact_name_limit(RF.RDB$FIELD_NAME, sizeof(RF.RDB$FIELD_NAME));
status_exception::raise(
Arg::Gds(isc_dyn_coll_used_table) << COLL.RDB$COLLATION_NAME <<
RF.RDB$RELATION_NAME << RF.RDB$FIELD_NAME);
}
END_FOR
request2.reset(tdbb, drq_l_prm_coll, DYN_REQUESTS);
FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
PRM IN RDB$PROCEDURE_PARAMETERS CROSS F IN RDB$FIELDS
WITH PRM.RDB$FIELD_SOURCE EQ F.RDB$FIELD_NAME AND
F.RDB$CHARACTER_SET_ID EQ COLL.RDB$CHARACTER_SET_ID AND
PRM.RDB$COLLATION_ID EQ COLL.RDB$COLLATION_ID
{
fb_utils::exact_name_limit(PRM.RDB$PARAMETER_NAME, sizeof(PRM.RDB$PARAMETER_NAME));
status_exception::raise(
Arg::Gds(isc_dyn_coll_used_procedure) <<
COLL.RDB$COLLATION_NAME <<
QualifiedName(PRM.RDB$PROCEDURE_NAME,
(PRM.RDB$PACKAGE_NAME.NULL ? NULL : PRM.RDB$PACKAGE_NAME)).toString().c_str() <<
PRM.RDB$PARAMETER_NAME);
}
END_FOR
request2.reset(tdbb, drq_l_arg_coll, DYN_REQUESTS);
FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
ARG IN RDB$FUNCTION_ARGUMENTS CROSS F IN RDB$FIELDS
WITH ARG.RDB$FIELD_SOURCE EQ F.RDB$FIELD_NAME AND
F.RDB$CHARACTER_SET_ID EQ COLL.RDB$CHARACTER_SET_ID AND
ARG.RDB$COLLATION_ID EQ COLL.RDB$COLLATION_ID
{
fb_utils::exact_name_limit(ARG.RDB$ARGUMENT_NAME, sizeof(ARG.RDB$ARGUMENT_NAME));
status_exception::raise(
Arg::Gds(isc_dyn_coll_used_function) <<
COLL.RDB$COLLATION_NAME <<
QualifiedName(ARG.RDB$FUNCTION_NAME,
(ARG.RDB$PACKAGE_NAME.NULL ? NULL : ARG.RDB$PACKAGE_NAME)).toString().c_str() <<
ARG.RDB$ARGUMENT_NAME);
}
END_FOR
request2.reset(tdbb, drq_l_fld_coll, DYN_REQUESTS);
FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
F IN RDB$FIELDS
WITH F.RDB$CHARACTER_SET_ID EQ COLL.RDB$CHARACTER_SET_ID AND
F.RDB$COLLATION_ID EQ COLL.RDB$COLLATION_ID
{
fb_utils::exact_name_limit(F.RDB$FIELD_NAME, sizeof(F.RDB$FIELD_NAME));
status_exception::raise(
Arg::Gds(isc_dyn_coll_used_domain) << COLL.RDB$COLLATION_NAME << F.RDB$FIELD_NAME);
}
END_FOR
ERASE COLL;
if (!COLL.RDB$SECURITY_CLASS.NULL)
deleteSecurityClass(tdbb, transaction, COLL.RDB$SECURITY_CLASS);
}
END_FOR
request.reset(tdbb, drq_e_coll_prvs, DYN_REQUESTS);
FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
PRIV IN RDB$USER_PRIVILEGES
WITH PRIV.RDB$RELATION_NAME EQ name.c_str() AND
PRIV.RDB$OBJECT_TYPE = obj_collation
{
ERASE PRIV;
}
END_FOR
if (found)
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_COLLATION, name);
else
status_exception::raise(Arg::Gds(isc_dyn_collation_not_found) << Arg::Str(name));
savePoint.release(); // everything is ok
// Update DSQL cache
METD_drop_collation(transaction, name);
MET_dsql_cache_release(tdbb, SYM_intlsym_collation, name);
}
//----------------------
void CreateDomainNode::print(string& text) const
{
string nameTypeStr;
nameType->print(nameTypeStr);
text =
"CreateDomainNode\n"
" " + nameTypeStr + "\n";
}
void CreateDomainNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
Attachment* const attachment = transaction->tra_attachment;
dsql_fld* type = nameType->type;
if (fb_utils::implicit_domain(nameType->name.c_str()))
{
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-637) <<
Arg::Gds(isc_dsql_implicit_domain_name) << nameType->name);
}
const ValueListNode* elements = type->ranges;
const USHORT dims = elements ? elements->items.getCount() / 2 : 0;
if (nameType->defaultClause && dims != 0)
{
// Default value is not allowed for array type in domain %s
status_exception::raise(Arg::PrivateDyn(226) << nameType->name);
}
type->resolve(dsqlScratch);
dsqlScratch->domainValue.dsc_dtype = type->dtype;
dsqlScratch->domainValue.dsc_length = type->length;
dsqlScratch->domainValue.dsc_scale = type->scale;
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
DDL_TRIGGER_CREATE_DOMAIN, nameType->name);
storeGlobalField(tdbb, transaction, nameType->name, type);
if (nameType->defaultClause || check || notNull)
{
AutoCacheRequest request(tdbb, drq_m_fld, DYN_REQUESTS);
FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
FLD IN RDB$FIELDS
WITH FLD.RDB$FIELD_NAME EQ nameType->name.c_str()
{
MODIFY FLD
if (nameType->defaultClause)
{
FLD.RDB$DEFAULT_SOURCE.NULL = FALSE;
attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$DEFAULT_SOURCE,
nameType->defaultClause->source);
dsqlScratch->getBlrData().clear();
dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5);
ValueExprNode* node = doDsqlPass(dsqlScratch, nameType->defaultClause->value);
GEN_expr(dsqlScratch, node);
dsqlScratch->appendUChar(blr_eoc);
FLD.RDB$DEFAULT_VALUE.NULL = FALSE;
attachment->storeBinaryBlob(tdbb, transaction, &FLD.RDB$DEFAULT_VALUE,
dsqlScratch->getBlrData());
}
if (check)
{
FLD.RDB$VALIDATION_SOURCE.NULL = FALSE;
attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$VALIDATION_SOURCE,
check->source);
dsqlScratch->getBlrData().clear();
dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5);
// Increment the context level for this statement, so that the context number for
// any RSE generated for a SELECT within the CHECK clause will be greater than 0.
// In the environment of a domain check constraint, context number 0 is reserved
// for the "blr_fid, 0, 0, 0," which is emitted for a nod_dom_value, corresponding
// to an occurance of the VALUE keyword in the body of the check constraint.
// -- chrisj 1999-08-20
++dsqlScratch->contextNumber;
BoolExprNode* node = doDsqlPass(dsqlScratch, check->value);
GEN_expr(dsqlScratch, node);
dsqlScratch->appendUChar(blr_eoc);
FLD.RDB$VALIDATION_BLR.NULL = FALSE;
attachment->storeBinaryBlob(tdbb, transaction, &FLD.RDB$VALIDATION_BLR,
dsqlScratch->getBlrData());
}
if (notNull)
{
FLD.RDB$NULL_FLAG.NULL = FALSE;
FLD.RDB$NULL_FLAG = 1;
}
END_MODIFY
}
END_FOR
}
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER,
DDL_TRIGGER_CREATE_DOMAIN, nameType->name);
savePoint.release(); // everything is ok
}
//----------------------
// Compare the original field type with the new field type to determine if the original type can be
// changed to the new type.
//
// The following conversions are not allowed:
// Blob to anything
// Array to anything
// Date to anything
// Char to any numeric
// Varchar to any numeric
// Anything to Blob
// Anything to Array
//
// This function throws an exception if the conversion can not be made.
//
// ASF: We should stop using dyn_fld here as soon DYN stops to be a caller of this function.
void AlterDomainNode::checkUpdate(const dyn_fld& origFld, const dyn_fld& newFld)
{
ULONG errorCode = FB_SUCCESS;
const USHORT origLen = DTYPE_IS_TEXT(origFld.dyn_dsc.dsc_dtype) ?
origFld.dyn_charlen : DSC_string_length(&origFld.dyn_dsc);
// Check to make sure that the old and new types are compatible
switch (origFld.dyn_dtype)
{
// CHARACTER types
case blr_text:
case blr_varying:
case blr_cstring:
switch (newFld.dyn_dtype)
{
case blr_blob:
case blr_blob_id:
// Cannot change datatype for column %s.
// The operation cannot be performed on BLOB, or ARRAY columns.
errorCode = isc_dyn_dtype_invalid;
break;
case blr_sql_date:
case blr_sql_time:
case blr_timestamp:
case blr_int64:
case blr_long:
case blr_short:
case blr_d_float:
case blr_double:
case blr_float:
// Cannot convert column %s from character to non-character data.
errorCode = isc_dyn_dtype_conv_invalid;
break;
// If the original field is a character field and the new field is a character field,
// is there enough space in the new field?
case blr_text:
case blr_varying:
case blr_cstring:
if (newFld.dyn_charlen < origLen)
{
// msg 208: New size specified for column %s must be at least %d characters.
errorCode = isc_dyn_char_fld_too_small;
}
break;
default:
fb_assert(FALSE);
errorCode = ENCODE_ISC_MSG(87, DYN_MSG_FAC); // MODIFY RDB$FIELDS FAILED
break;
}
break;
// BLOB and ARRAY types
case blr_blob:
case blr_blob_id:
// Cannot change datatype for column %s.
// The operation cannot be performed on BLOB, or ARRAY columns.
errorCode = isc_dyn_dtype_invalid;
break;
// DATE types
case blr_sql_date:
case blr_sql_time:
case blr_timestamp:
switch (newFld.dyn_dtype)
{
case blr_sql_date:
if (origFld.dyn_dtype == blr_sql_time)
{
// Cannot change datatype for column %s. Conversion from base type %s to base type %s is not supported.
errorCode = isc_dyn_invalid_dtype_conversion;
}
break;
case blr_sql_time:
if (origFld.dyn_dtype == blr_sql_date)
{
// Cannot change datatype for column %s. Conversion from base type %s to base type %s is not supported.
errorCode = isc_dyn_invalid_dtype_conversion;
}
break;
case blr_timestamp:
if (origFld.dyn_dtype == blr_sql_time)
{
// Cannot change datatype for column %s. Conversion from base type %s to base type %s is not supported.
errorCode = isc_dyn_invalid_dtype_conversion;
}
break;
// If the original field is a date field and the new field is a character field,
// is there enough space in the new field?
case blr_text:
case blr_text2:
case blr_varying:
case blr_varying2:
case blr_cstring:
case blr_cstring2:
if (newFld.dyn_charlen < origLen)
{
// msg 208: New size specified for column %s must be at least %d characters.
errorCode = isc_dyn_char_fld_too_small;
}
break;
default:
// Cannot change datatype for column %s. Conversion from base type %s to base type %s is not supported.
errorCode = isc_dyn_invalid_dtype_conversion;
break;
}
break;
// NUMERIC types
case blr_int64:
case blr_long:
case blr_short:
case blr_d_float:
case blr_double:
case blr_float:
switch (newFld.dyn_dtype)
{
case blr_blob:
case blr_blob_id:
// Cannot change datatype for column %s.
// The operation cannot be performed on BLOB, or ARRAY columns.
errorCode = isc_dyn_dtype_invalid;
break;
case blr_sql_date:
case blr_sql_time:
case blr_timestamp:
// Cannot change datatype for column %s. Conversion from base type %s to base type %s is not supported.
errorCode = isc_dyn_invalid_dtype_conversion;
break;
// If the original field is a numeric field and the new field is a numeric field,
// is there enough space in the new field (do not allow the base type to decrease)
case blr_short:
switch (origFld.dyn_dtype)
{
case blr_short:
errorCode = checkUpdateNumericType(origFld, newFld);
break;
default:
// Cannot change datatype for column %s. Conversion from base type %s to base type %s is not supported.
errorCode = isc_dyn_invalid_dtype_conversion;
break;
}
break;
case blr_long:
switch (origFld.dyn_dtype)
{
case blr_long:
case blr_short:
errorCode = checkUpdateNumericType(origFld, newFld);
break;
default:
// Cannot change datatype for column %s. Conversion from base type %s to base type %s is not supported.
errorCode = isc_dyn_invalid_dtype_conversion;
break;
}
break;
case blr_float:
switch (origFld.dyn_dtype)
{
case blr_float:
case blr_short:
break;
default:
// Cannot change datatype for column %s. Conversion from base type %s to base type %s is not supported.
errorCode = isc_dyn_invalid_dtype_conversion;
break;
}
break;
case blr_int64:
switch (origFld.dyn_dtype)
{
case blr_int64:
case blr_long:
case blr_short:
errorCode = checkUpdateNumericType(origFld, newFld);
break;
default:
// Cannot change datatype for column %s. Conversion from base type %s to base type %s is not supported.
errorCode = isc_dyn_invalid_dtype_conversion;
break;
}
break;
case blr_d_float:
case blr_double:
switch (origFld.dyn_dtype)
{
case blr_double:
case blr_d_float:
case blr_float:
case blr_short:
case blr_long:
break;
default:
// Cannot change datatype for column %s. Conversion from base type %s to base type %s is not supported.
errorCode = isc_dyn_invalid_dtype_conversion;
break;
}
break;
// If the original field is a numeric field and the new field is a character field,
// is there enough space in the new field?
case blr_text:
case blr_varying:
case blr_cstring:
if (newFld.dyn_charlen < origLen)
{
// msg 208: New size specified for column %s must be at least %d characters.
errorCode = isc_dyn_char_fld_too_small;
}
break;
default:
fb_assert(FALSE);
errorCode = ENCODE_ISC_MSG(87, DYN_MSG_FAC); // MODIFY RDB$FIELDS FAILED
break;
}
break;
default:
fb_assert(FALSE);
errorCode = ENCODE_ISC_MSG(87, DYN_MSG_FAC); // MODIFY RDB$FIELDS FAILED
break;
}
if (errorCode == FB_SUCCESS)
return;
switch (errorCode)
{
case isc_dyn_dtype_invalid:
// Cannot change datatype for column %s.The operation cannot be performed on DATE, BLOB, or ARRAY columns.
status_exception::raise(Arg::Gds(errorCode) << origFld.dyn_fld_name.c_str());
break;
case isc_dyn_dtype_conv_invalid:
// Cannot convert column %s from character to non-character data.
status_exception::raise(Arg::Gds(errorCode) << origFld.dyn_fld_name.c_str());
break;
case isc_dyn_char_fld_too_small:
// msg 208: New size specified for column %s must be at least %d characters.
status_exception::raise(
Arg::Gds(errorCode) << origFld.dyn_fld_name.c_str() <<
Arg::Num(origLen));
break;
case isc_dyn_scale_too_big:
{
int code = errorCode;
int diff = newFld.dyn_precision -
(origFld.dyn_precision + origFld.dyn_dsc.dsc_scale);
if (diff < 0)
{
// If new scale becomes negative externally, the message is useless for the user.
// (The scale is always zero or negative for us but externally is non-negative.)
// Let's ask the user to widen the precision, then. Example: numeric(4, 0) -> numeric(1, 1).
code = isc_dyn_precision_too_small;
diff = newFld.dyn_precision - newFld.dyn_dsc.dsc_scale - diff;
}
// scale_too_big: New scale specified for column @1 must be at most @2.
// precision_too_small: New precision specified for column @1 must be at least @2.
status_exception::raise(
Arg::Gds(code) << origFld.dyn_fld_name.c_str() << Arg::Num(diff));
}
break;
case isc_dyn_invalid_dtype_conversion:
{
TEXT orig_type[25], new_type[25];
DSC_get_dtype_name(&origFld.dyn_dsc, orig_type, sizeof(orig_type));
DSC_get_dtype_name(&newFld.dyn_dsc, new_type, sizeof(new_type));
// Cannot change datatype for @1. Conversion from base type @2 to @3 is not supported.
status_exception::raise(
Arg::Gds(errorCode) << origFld.dyn_fld_name.c_str() << orig_type << new_type);
}
break;
default:
// msg 95: "MODIFY RDB$RELATION_FIELDS failed"
status_exception::raise(Arg::PrivateDyn(95));
}
}
// Compare the original field type with the new field type to determine if the original type can be
// changed to the new type.
// The types should be integral, since it tests only numeric/decimal subtypes to ensure the scale is
// not being widened at the expense of the precision, because the old stored values should fit in
// the new definition.
//
// This function returns an error code if the conversion can not be made. If the conversion can be
// made, FB_SUCCESS is returned.
ULONG AlterDomainNode::checkUpdateNumericType(const dyn_fld& origFld, const dyn_fld& newFld)
{
// Since dsc_scale is negative, the sum of precision and scale produces
// the width of the integral part.
if (origFld.dyn_sub_type && newFld.dyn_sub_type &&
origFld.dyn_precision + origFld.dyn_dsc.dsc_scale >
newFld.dyn_precision + newFld.dyn_dsc.dsc_scale)
{
return isc_dyn_scale_too_big;
}
return FB_SUCCESS;
}
// Retrieves the type information for a domain so that it can be compared to a local field before
// modifying the datatype of a field.
void AlterDomainNode::getDomainType(thread_db* tdbb, jrd_tra* transaction, dyn_fld& dynFld)
{
AutoRequest request;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
FLD IN RDB$FIELDS
WITH FLD.RDB$FIELD_NAME EQ dynFld.dyn_fld_source.c_str();
{
DSC_make_descriptor(&dynFld.dyn_dsc, FLD.RDB$FIELD_TYPE, FLD.RDB$FIELD_SCALE,
FLD.RDB$FIELD_LENGTH, FLD.RDB$FIELD_SUB_TYPE, FLD.RDB$CHARACTER_SET_ID,
FLD.RDB$COLLATION_ID);
dynFld.dyn_charbytelen = FLD.RDB$FIELD_LENGTH;
dynFld.dyn_dtype = FLD.RDB$FIELD_TYPE;
dynFld.dyn_precision = FLD.RDB$FIELD_PRECISION;
dynFld.dyn_sub_type = FLD.RDB$FIELD_SUB_TYPE;
dynFld.dyn_charlen = FLD.RDB$CHARACTER_LENGTH;
dynFld.dyn_collation = FLD.RDB$COLLATION_ID;
dynFld.dyn_null_flag = FLD.RDB$NULL_FLAG != 0;
if (!FLD.RDB$DIMENSIONS.NULL && FLD.RDB$DIMENSIONS > 0)
dynFld.dyn_dtype = blr_blob;
}
END_FOR
}
// Updates the field names in an index and forces the index to be rebuilt with the new field names.
void AlterDomainNode::modifyLocalFieldIndex(thread_db* tdbb, jrd_tra* transaction,
const MetaName& relationName, const MetaName& fieldName, const MetaName& newFieldName)
{
AutoRequest request;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
IDX IN RDB$INDICES CROSS IDXS IN RDB$INDEX_SEGMENTS WITH
IDX.RDB$INDEX_NAME EQ IDXS.RDB$INDEX_NAME AND
IDX.RDB$RELATION_NAME EQ relationName.c_str() AND
IDXS.RDB$FIELD_NAME EQ fieldName.c_str()
{
// Change the name of the field in the index
MODIFY IDXS USING
memcpy(IDXS.RDB$FIELD_NAME, newFieldName.c_str(), sizeof(IDXS.RDB$FIELD_NAME));
END_MODIFY
// Set the index name to itself to tell the index to rebuild
MODIFY IDX USING
// This is to fool both gpre and gcc.
char* p = IDX.RDB$INDEX_NAME;
p[MAX_SQL_IDENTIFIER_LEN] = 0;
END_MODIFY
}
END_FOR
}
void AlterDomainNode::print(string& text) const
{
text.printf(
"AlterDomainNode\n"
" %s\n", name.c_str());
}
void AlterDomainNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
Attachment* const attachment = transaction->tra_attachment;
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
AutoCacheRequest request(tdbb, drq_m_fld2, DYN_REQUESTS);
bool found = false;
FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
FLD IN RDB$FIELDS
WITH FLD.RDB$FIELD_NAME EQ name.c_str()
{
found = true;
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
DDL_TRIGGER_ALTER_DOMAIN, name);
MODIFY FLD
if (dropConstraint)
{
FLD.RDB$VALIDATION_BLR.NULL = TRUE;
FLD.RDB$VALIDATION_SOURCE.NULL = TRUE;
}
if (dropDefault)
{
FLD.RDB$DEFAULT_VALUE.NULL = TRUE;
FLD.RDB$DEFAULT_SOURCE.NULL = TRUE;
}
if (setConstraint)
{
if (!FLD.RDB$VALIDATION_BLR.NULL)
{
// msg 160: "Only one constraint allowed for a domain"
status_exception::raise(Arg::PrivateDyn(160));
}
dsql_fld localField(dsqlScratch->getStatement()->getPool());
// Get the attributes of the domain, and set any occurances of
// keyword VALUE to the correct type, length, scale, etc.
if (!METD_get_domain(dsqlScratch->getTransaction(), &localField, name))
{
// Specified domain or source field does not exist
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_dsql_domain_not_found) << name);
}
dsqlScratch->domainValue.dsc_dtype = localField.dtype;
dsqlScratch->domainValue.dsc_length = localField.length;
dsqlScratch->domainValue.dsc_scale = localField.scale;
FLD.RDB$VALIDATION_SOURCE.NULL = FALSE;
attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$VALIDATION_SOURCE,
setConstraint->source);
dsqlScratch->getBlrData().clear();
dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5);
// Increment the context level for this statement, so that the context number for
// any RSE generated for a SELECT within the CHECK clause will be greater than 0.
// In the environment of a domain check constraint, context number 0 is reserved
// for the "blr_fid, 0, 0, 0," which is emitted for a nod_dom_value, corresponding
// to an occurance of the VALUE keyword in the body of the check constraint.
// -- chrisj 1999-08-20
++dsqlScratch->contextNumber;
BoolExprNode* node = doDsqlPass(dsqlScratch, setConstraint->value);
GEN_expr(dsqlScratch, node);
dsqlScratch->appendUChar(blr_eoc);
FLD.RDB$VALIDATION_BLR.NULL = FALSE;
attachment->storeBinaryBlob(tdbb, transaction, &FLD.RDB$VALIDATION_BLR,
dsqlScratch->getBlrData());
}
if (setDefault)
{
if (FLD.RDB$DIMENSIONS)
{
// msg 226: "Default value is not allowed for array type in domain %s"
status_exception::raise(Arg::PrivateDyn(226) << name);
}
FLD.RDB$DEFAULT_SOURCE.NULL = FALSE;
attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$DEFAULT_SOURCE,
setDefault->source);
dsqlScratch->getBlrData().clear();
dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5);
ValueExprNode* node = doDsqlPass(dsqlScratch, setDefault->value);
GEN_expr(dsqlScratch, node);
dsqlScratch->appendUChar(blr_eoc);
FLD.RDB$DEFAULT_VALUE.NULL = FALSE;
attachment->storeBinaryBlob(tdbb, transaction, &FLD.RDB$DEFAULT_VALUE,
dsqlScratch->getBlrData());
}
if (notNullFlag.specified)
{
FLD.RDB$NULL_FLAG.NULL = FALSE;
FLD.RDB$NULL_FLAG = notNullFlag.value;
}
if (type)
{
type->resolve(dsqlScratch);
dyn_fld origDom, newDom;
DSC_make_descriptor(&origDom.dyn_dsc, FLD.RDB$FIELD_TYPE, FLD.RDB$FIELD_SCALE,
FLD.RDB$FIELD_LENGTH, FLD.RDB$FIELD_SUB_TYPE, FLD.RDB$CHARACTER_SET_ID,
FLD.RDB$COLLATION_ID);
origDom.dyn_fld_name = name;
origDom.dyn_charbytelen = FLD.RDB$FIELD_LENGTH;
origDom.dyn_dtype = FLD.RDB$FIELD_TYPE;
origDom.dyn_precision = FLD.RDB$FIELD_PRECISION;
origDom.dyn_sub_type = FLD.RDB$FIELD_SUB_TYPE;
origDom.dyn_charlen = FLD.RDB$CHARACTER_LENGTH;
origDom.dyn_collation = FLD.RDB$COLLATION_ID;
origDom.dyn_null_flag = !FLD.RDB$NULL_FLAG.NULL && FLD.RDB$NULL_FLAG != 0;
// If the original field type is an array, force its blr type to blr_blob
if (FLD.RDB$DIMENSIONS != 0)
origDom.dyn_dtype = blr_blob;
USHORT typeLength = type->length;
switch (type->dtype)
{
case dtype_varying:
typeLength -= sizeof(USHORT);
break;
// Not valid for domains, but may be important for a future refactor.
case dtype_cstring:
--typeLength;
break;
default:
break;
}
DSC_make_descriptor(&newDom.dyn_dsc, blr_dtypes[type->dtype], type->scale,
typeLength, type->subType, type->charSetId, type->collationId);
newDom.dyn_fld_name = name;
newDom.dyn_charbytelen = typeLength;
newDom.dyn_dtype = blr_dtypes[type->dtype];
newDom.dyn_precision = type->precision;
newDom.dyn_sub_type = type->subType;
newDom.dyn_charlen = type->charLength;
newDom.dyn_collation = type->collationId;
newDom.dyn_null_flag = type->notNull;
// Now that we have all of the information needed, let's check to see if the field
// type can be modifed.
checkUpdate(origDom, newDom);
if (!newDom.dyn_dsc.isExact() || newDom.dyn_dsc.dsc_scale != 0)
{
AutoCacheRequest request(tdbb, drq_l_ident_gens, DYN_REQUESTS);
FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
RFR IN RDB$RELATION_FIELDS
WITH RFR.RDB$FIELD_SOURCE = FLD.RDB$FIELD_NAME AND
RFR.RDB$GENERATOR_NAME NOT MISSING
{
// Domain @1 must be of exact number type with zero scale because it's used
// in an identity column.
status_exception::raise(Arg::PrivateDyn(276) << name);
}
END_FOR
}
// If the datatype was changed, update any indexes that involved the domain
AutoRequest request2;
FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
DOM IN RDB$RELATION_FIELDS
WITH DOM.RDB$FIELD_SOURCE EQ name.c_str()
{
modifyLocalFieldIndex(tdbb, transaction, DOM.RDB$RELATION_NAME,
DOM.RDB$FIELD_NAME, DOM.RDB$FIELD_NAME);
}
END_FOR
// Update RDB$FIELDS
updateRdbFields(type,
FLD.RDB$FIELD_TYPE,
FLD.RDB$FIELD_LENGTH,
FLD.RDB$FIELD_SUB_TYPE.NULL, FLD.RDB$FIELD_SUB_TYPE,
FLD.RDB$FIELD_SCALE.NULL, FLD.RDB$FIELD_SCALE,
FLD.RDB$CHARACTER_SET_ID.NULL, FLD.RDB$CHARACTER_SET_ID,
FLD.RDB$CHARACTER_LENGTH.NULL, FLD.RDB$CHARACTER_LENGTH,
FLD.RDB$FIELD_PRECISION.NULL, FLD.RDB$FIELD_PRECISION,
FLD.RDB$COLLATION_ID.NULL, FLD.RDB$COLLATION_ID,
FLD.RDB$SEGMENT_LENGTH.NULL, FLD.RDB$SEGMENT_LENGTH);
}
if (renameTo.hasData())
{
rename(tdbb, transaction, (FLD.RDB$DIMENSIONS.NULL ? 0 : FLD.RDB$DIMENSIONS));
strcpy(FLD.RDB$FIELD_NAME, renameTo.c_str());
}
END_MODIFY
}
END_FOR
if (!found)
{
// msg 89: "Global field not found"
status_exception::raise(Arg::PrivateDyn(89));
}
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_DOMAIN, name);
savePoint.release(); // everything is ok
}
void AlterDomainNode::rename(thread_db* tdbb, jrd_tra* transaction, SSHORT dimensions)
{
// Checks to see if the given domain already exists.
AutoRequest request;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
FLD IN RDB$FIELDS WITH FLD.RDB$FIELD_NAME EQ renameTo.c_str()
{
// msg 204: Cannot rename domain %s to %s. A domain with that name already exists.
status_exception::raise(Arg::PrivateDyn(204) << name << renameTo);
}
END_FOR
// CVC: Let's update the dimensions, too.
if (dimensions != 0)
{
request.reset();
FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
FDIM IN RDB$FIELD_DIMENSIONS
WITH FDIM.RDB$FIELD_NAME EQ name.c_str()
{
MODIFY FDIM USING
strcpy(FDIM.RDB$FIELD_NAME, renameTo.c_str());
END_MODIFY
}
END_FOR
}
request.reset();
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
RFLD IN RDB$RELATION_FIELDS
WITH RFLD.RDB$FIELD_SOURCE EQ name.c_str()
{
MODIFY RFLD USING
strcpy(RFLD.RDB$FIELD_SOURCE, renameTo.c_str());
END_MODIFY
modifyLocalFieldIndex(tdbb, transaction, RFLD.RDB$RELATION_NAME,
RFLD.RDB$FIELD_NAME, RFLD.RDB$FIELD_NAME);
}
END_FOR
}
//----------------------
// Delete the records in RDB$FIELD_DIMENSIONS pertaining to a field.
bool DropDomainNode::deleteDimensionRecords(thread_db* tdbb, jrd_tra* transaction, const MetaName& name)
{
AutoCacheRequest request(tdbb, drq_e_dims, DYN_REQUESTS);
bool found = false;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
X IN RDB$FIELD_DIMENSIONS
WITH X.RDB$FIELD_NAME EQ name.c_str()
{
found = true;
ERASE X;
}
END_FOR
return found;
}
void DropDomainNode::print(string& text) const
{
text.printf(
"DropDomainNode\n"
" name: '%s'\n",
name.c_str());
}
void DropDomainNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
AutoCacheRequest request(tdbb, drq_e_gfields, DYN_REQUESTS);
bool found = false;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
X IN RDB$FIELDS
WITH X.RDB$FIELD_NAME EQ name.c_str()
{
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
DDL_TRIGGER_DROP_DOMAIN, name);
check(tdbb, transaction);
deleteDimensionRecords(tdbb, transaction, name);
ERASE X;
if (!X.RDB$SECURITY_CLASS.NULL)
deleteSecurityClass(tdbb, transaction, X.RDB$SECURITY_CLASS);
found = true;
}
END_FOR
request.reset(tdbb, drq_e_gfld_prvs, DYN_REQUESTS);
FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
PRIV IN RDB$USER_PRIVILEGES
WITH PRIV.RDB$RELATION_NAME EQ name.c_str() AND
PRIV.RDB$OBJECT_TYPE = obj_field
{
ERASE PRIV;
}
END_FOR
if (found)
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_DOMAIN, name);
else
{
// msg 89: "Domain not found"
status_exception::raise(Arg::PrivateDyn(89));
}
savePoint.release(); // everything is ok
}
void DropDomainNode::check(thread_db* tdbb, jrd_tra* transaction)
{
AutoCacheRequest request(tdbb, drq_l_fld_src, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
Y IN RDB$RELATION_FIELDS
WITH Y.RDB$FIELD_SOURCE EQ name.c_str()
{
fb_utils::exact_name_limit(Y.RDB$FIELD_SOURCE, sizeof(Y.RDB$FIELD_SOURCE));
fb_utils::exact_name_limit(Y.RDB$RELATION_NAME, sizeof(Y.RDB$RELATION_NAME));
fb_utils::exact_name_limit(Y.RDB$FIELD_NAME, sizeof(Y.RDB$FIELD_NAME));
// msg 43: "Domain %s is used in table %s (local name %s) and can not be dropped"
status_exception::raise(
Arg::PrivateDyn(43) << Y.RDB$FIELD_SOURCE << Y.RDB$RELATION_NAME << Y.RDB$FIELD_NAME);
}
END_FOR
request.reset(tdbb, drq_l_prp_src, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
X IN RDB$PROCEDURE_PARAMETERS
WITH X.RDB$FIELD_SOURCE EQ name.c_str()
{
fb_utils::exact_name_limit(X.RDB$FIELD_SOURCE, sizeof(X.RDB$FIELD_SOURCE));
fb_utils::exact_name_limit(X.RDB$PROCEDURE_NAME, sizeof(X.RDB$PROCEDURE_NAME));
fb_utils::exact_name_limit(X.RDB$PARAMETER_NAME, sizeof(X.RDB$PARAMETER_NAME));
// msg 239: "Domain %s is used in procedure %s (parameter name %s) and cannot be dropped"
status_exception::raise(
Arg::PrivateDyn(239) << X.RDB$FIELD_SOURCE <<
QualifiedName(X.RDB$PROCEDURE_NAME,
(X.RDB$PACKAGE_NAME.NULL ? NULL : X.RDB$PACKAGE_NAME)).toString().c_str() <<
X.RDB$PARAMETER_NAME);
}
END_FOR
request.reset(tdbb, drq_l_arg_src, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
X IN RDB$FUNCTION_ARGUMENTS
WITH X.RDB$FIELD_SOURCE EQ name.c_str()
{
fb_utils::exact_name_limit(X.RDB$FIELD_SOURCE, sizeof(X.RDB$FIELD_SOURCE));
fb_utils::exact_name_limit(X.RDB$FUNCTION_NAME, sizeof(X.RDB$FUNCTION_NAME));
fb_utils::exact_name_limit(X.RDB$ARGUMENT_NAME, sizeof(X.RDB$ARGUMENT_NAME));
// msg 239: "Domain %s is used in function %s (parameter name %s) and cannot be dropped"
status_exception::raise(
Arg::Gds(isc_dyn_domain_used_function) << X.RDB$FIELD_SOURCE <<
QualifiedName(X.RDB$FUNCTION_NAME,
(X.RDB$PACKAGE_NAME.NULL ? NULL : X.RDB$PACKAGE_NAME)).toString().c_str() <<
X.RDB$ARGUMENT_NAME);
}
END_FOR
}
//----------------------
void CreateAlterExceptionNode::print(string& text) const
{
text.printf(
"CreateAlterExceptionNode\n"
" name: '%s' create: %d alter: %d\n"
" message: '%s'\n",
name.c_str(), create, alter, message.c_str());
}
void CreateAlterExceptionNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
fb_assert(create || alter);
if (message.length() > XCP_MESSAGE_LENGTH)
status_exception::raise(Arg::Gds(isc_dyn_name_longer));
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
if (alter)
{
if (!executeAlter(tdbb, dsqlScratch, transaction))
{
if (create) // create or alter
executeCreate(tdbb, dsqlScratch, transaction);
else
{
// msg 144: "Exception not found"
status_exception::raise(Arg::PrivateDyn(144));
}
}
}
else
executeCreate(tdbb, dsqlScratch, transaction);
savePoint.release(); // everything is ok
}
void CreateAlterExceptionNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
Attachment* const attachment = transaction->getAttachment();
const string& userName = attachment->att_user->usr_user_name;
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
DDL_TRIGGER_CREATE_EXCEPTION, name);
DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_exception);
AutoCacheRequest request(tdbb, drq_s_xcp, DYN_REQUESTS);
int faults = 0;
while (true)
{
try
{
SINT64 id = DYN_UTIL_gen_unique_id(tdbb, drq_g_nxt_xcp_id, "RDB$EXCEPTIONS");
id %= (MAX_SSHORT + 1);
if (!id)
continue;
STORE (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
X IN RDB$EXCEPTIONS
{
X.RDB$EXCEPTION_NUMBER = id;
X.RDB$SYSTEM_FLAG = 0;
strcpy(X.RDB$EXCEPTION_NAME, name.c_str());
X.RDB$OWNER_NAME.NULL = FALSE;
strcpy(X.RDB$OWNER_NAME, userName.c_str());
strcpy(X.RDB$MESSAGE, message.c_str());
}
END_STORE
break;
}
catch (const status_exception& ex)
{
if (ex.value()[1] != isc_unique_key_violation)
throw;
if (++faults > MAX_SSHORT)
throw;
fb_utils::init_status(tdbb->tdbb_status_vector);
}
}
storePrivileges(tdbb, transaction, name, obj_exception, USAGE_PRIVILEGES);
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_EXCEPTION, name);
}
bool CreateAlterExceptionNode::executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
AutoCacheRequest request(tdbb, drq_m_xcp, DYN_REQUESTS);
bool modified = false;
FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
X IN RDB$EXCEPTIONS
WITH X.RDB$EXCEPTION_NAME EQ name.c_str()
{
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
DDL_TRIGGER_ALTER_EXCEPTION, name);
MODIFY X
strcpy(X.RDB$MESSAGE, message.c_str());
modified = true;
END_MODIFY
}
END_FOR
if (modified)
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_EXCEPTION, name);
return modified;
}
//----------------------
void DropExceptionNode::print(string& text) const
{
text.printf(
"DropExceptionNode\n"
" name: '%s'\n",
name.c_str());
}
void DropExceptionNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
AutoCacheRequest request(tdbb, drq_e_xcp, DYN_REQUESTS);
bool found = false;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
X IN RDB$EXCEPTIONS
WITH X.RDB$EXCEPTION_NAME EQ name.c_str()
{
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
DDL_TRIGGER_DROP_EXCEPTION, name);
ERASE X;
if (!X.RDB$SECURITY_CLASS.NULL)
deleteSecurityClass(tdbb, transaction, X.RDB$SECURITY_CLASS);
found = true;
}
END_FOR
request.reset(tdbb, drq_e_xcp_prvs, DYN_REQUESTS);
FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
PRIV IN RDB$USER_PRIVILEGES
WITH PRIV.RDB$RELATION_NAME EQ name.c_str() AND
PRIV.RDB$OBJECT_TYPE = obj_exception
{
ERASE PRIV;
}
END_FOR
if (found)
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_EXCEPTION, name);
else if (!silent)
{
// msg 144: "Exception not found"
status_exception::raise(Arg::PrivateDyn(144));
}
savePoint.release(); // everything is ok
}
//----------------------
void CreateAlterSequenceNode::print(string& text) const
{
text.printf(
"CreateAlterSequenceNode\n"
" name: %s\n",
name.c_str());
}
void CreateAlterSequenceNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
fb_assert(create || alter);
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
if (alter)
{
if (!executeAlter(tdbb, dsqlScratch, transaction))
{
if (create) // create or alter
executeCreate(tdbb, dsqlScratch, transaction);
else
{
// msg 214: "Sequence not found"
status_exception::raise(Arg::PrivateDyn(214) << name);
}
}
}
else
executeCreate(tdbb, dsqlScratch, transaction);
savePoint.release(); // everything is ok
}
void CreateAlterSequenceNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_SEQUENCE, name);
const SINT64 value = valueNode ? getValue() : 0;
const SSHORT id = store(tdbb, transaction, name, fb_sysflag_user, value);
fb_assert(id > 0);
// the store() call above has caused the DFW item to be posted,
// so we just adjust the cached generator value
transaction->getGenIdCache()->put(id, value);
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_SEQUENCE, name);
}
bool CreateAlterSequenceNode::executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
if (legacy)
{
// The only need for this code is that for the sake of backward compatibility
// SET GENERATOR is still described as isc_info_sql_stmt_set_generator and ISQL
// treats it as DML thus executing it in a separate transaction. So we need to ensure
// that the generator created in another transaction can be found here. This is done
// using MET_lookup_generator() which works in the system transaction.
const SLONG id = MET_lookup_generator(tdbb, name);
if (id < 0)
return false;
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_SEQUENCE, name);
const SINT64 value = valueNode ? getValue() : 0;
transaction->getGenIdCache()->put(id, value);
dsc desc;
desc.makeText((USHORT) name.length(), ttype_metadata, (UCHAR*) name.c_str());
DFW_post_work(transaction, dfw_set_generator, &desc, id);
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_SEQUENCE, name);
return true;
}
AutoCacheRequest request(tdbb, drq_l_gens, DYN_REQUESTS);
bool found = false;
FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
X IN RDB$GENERATORS
WITH X.RDB$GENERATOR_NAME EQ name.c_str()
{
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_SEQUENCE, name);
const SLONG id = X.RDB$GENERATOR_ID;
const SINT64 value = valueNode ? getValue() : (!X.RDB$INITIAL_VALUE.NULL ? X.RDB$INITIAL_VALUE : 0);
transaction->getGenIdCache()->put(id, value);
dsc desc;
desc.makeText((USHORT) name.length(), ttype_metadata, (UCHAR*) name.c_str());
DFW_post_work(transaction, dfw_set_generator, &desc, id);
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_SEQUENCE, name);
found = true;
}
END_FOR
return found;
}
SSHORT CreateAlterSequenceNode::store(thread_db* tdbb, jrd_tra* transaction, const MetaName& name,
fb_sysflag sysFlag, SINT64 value)
{
Attachment* const attachment = transaction->tra_attachment;
const string& userName = attachment->att_user->usr_user_name;
DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_generator);
AutoCacheRequest request(tdbb, drq_s_gens, DYN_REQUESTS);
int faults = 0;
SSHORT storedId = -1;
while (true)
{
try
{
SINT64 id = DYN_UTIL_gen_unique_id(tdbb, drq_g_nxt_gen_id, "RDB$GENERATORS");
id %= MAX_SSHORT + 1;
if (id == 0)
continue;
STORE (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
X IN RDB$GENERATORS
{
X.RDB$GENERATOR_ID = id;
X.RDB$SYSTEM_FLAG = (SSHORT) sysFlag;
strcpy(X.RDB$GENERATOR_NAME, name.c_str());
X.RDB$OWNER_NAME.NULL = FALSE;
strcpy(X.RDB$OWNER_NAME, userName.c_str());
X.RDB$INITIAL_VALUE.NULL = FALSE;
X.RDB$INITIAL_VALUE = value;
}
END_STORE
storedId = id;
break;
}
catch (const status_exception& ex)
{
if (ex.value()[1] != isc_unique_key_violation)
throw;
if (++faults > MAX_SSHORT)
throw;
fb_utils::init_status(tdbb->tdbb_status_vector);
}
}
storePrivileges(tdbb, transaction, name, obj_generator, USAGE_PRIVILEGES);
return storedId;
}
SINT64 CreateAlterSequenceNode::getValue() const
{
if (!valueNode)
{
fb_assert(false);
return 0;
}
const ValueExprNode* val = valueNode;
bool negate = false;
const NegateNode* negation = val->as<NegateNode>();
while (negation)
{
negate = !negate;
val = negation->arg;
negation = ExprNode::as<NegateNode>(val);
}
const LiteralNode* const lit = val->as<LiteralNode>();
fb_assert(lit);
SINT64 value = 0;
if (lit->litDesc.dsc_dtype == dtype_int64)
value = *(SINT64*) lit->litDesc.dsc_address;
else if (lit->litDesc.dsc_dtype == dtype_long)
value = *(SLONG*) lit->litDesc.dsc_address;
else
fb_assert(false);
if (negate)
value = -value;
else if (value == MIN_SINT64)
status_exception::raise(Arg::Gds(isc_arith_except) << Arg::Gds(isc_numeric_out_of_range));
return value;
}
//----------------------
void DropSequenceNode::print(string& text) const
{
text.printf(
"DropSequenceNode\n"
" name: %s\n",
name.c_str());
}
void DropSequenceNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
AutoCacheRequest request(tdbb, drq_e_gens, DYN_REQUESTS);
bool found = false;
FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
GEN IN RDB$GENERATORS
WITH GEN.RDB$GENERATOR_NAME EQ name.c_str()
{
if (!GEN.RDB$SYSTEM_FLAG.NULL && GEN.RDB$SYSTEM_FLAG != 0)
{
// msg 272: "Cannot delete system generator @1"
status_exception::raise(Arg::PrivateDyn(272) << name);
}
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
DDL_TRIGGER_DROP_SEQUENCE, name);
ERASE GEN;
if (!GEN.RDB$SECURITY_CLASS.NULL)
deleteSecurityClass(tdbb, transaction, GEN.RDB$SECURITY_CLASS);
found = true;
}
END_FOR
request.reset(tdbb, drq_e_gen_prvs, DYN_REQUESTS);
FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
PRIV IN RDB$USER_PRIVILEGES
WITH PRIV.RDB$RELATION_NAME EQ name.c_str() AND
PRIV.RDB$OBJECT_TYPE = obj_generator
{
ERASE PRIV;
}
END_FOR
if (found)
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_SEQUENCE, name);
else if (!silent)
status_exception::raise(Arg::Gds(isc_gennotdef) << Arg::Str(name));
savePoint.release(); // everything is ok
}
// Delete a record from RDB$GENERATORS, without verifying RDB$SYSTEM_FLAG.
void DropSequenceNode::deleteIdentity(thread_db* tdbb, jrd_tra* transaction, const MetaName& name)
{
AutoCacheRequest request(tdbb, drq_e_ident_gens, DYN_REQUESTS);
FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
GEN IN RDB$GENERATORS
WITH GEN.RDB$GENERATOR_NAME EQ name.c_str()
{
ERASE GEN;
}
END_FOR
}
//----------------------
RelationNode::RelationNode(MemoryPool& p, RelationSourceNode* aDsqlNode)
: DdlNode(p),
dsqlNode(aDsqlNode),
name(p, dsqlNode->dsqlName),
clauses(p)
{
}
void RelationNode::FieldDefinition::modify(thread_db* tdbb, jrd_tra* transaction)
{
AutoRequest request;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
RFR IN RDB$RELATION_FIELDS
WITH RFR.RDB$RELATION_NAME EQ relationName.c_str() AND
RFR.RDB$FIELD_NAME EQ name.c_str()
{
// ASF: This is prepared only to modify view fields!
MODIFY RFR
strcpy(RFR.RDB$FIELD_SOURCE, fieldSource.c_str());
RFR.RDB$COLLATION_ID.NULL = TRUE;
RFR.RDB$GENERATOR_NAME.NULL = TRUE;
RFR.RDB$IDENTITY_TYPE.NULL = TRUE;
RFR.RDB$NULL_FLAG.NULL = TRUE;
RFR.RDB$DEFAULT_SOURCE.NULL = TRUE;
RFR.RDB$DEFAULT_VALUE.NULL = TRUE;
RFR.RDB$FIELD_POSITION.NULL = TRUE;
RFR.RDB$VIEW_CONTEXT.NULL = TRUE;
RFR.RDB$BASE_FIELD.NULL = TRUE;
///RFR.RDB$UPDATE_FLAG.NULL = TRUE;
if (collationId.specified)
{
RFR.RDB$COLLATION_ID.NULL = FALSE;
RFR.RDB$COLLATION_ID = collationId.value;
}
SLONG fieldPos = -1;
if (position.specified)
fieldPos = position.value;
else
{
DYN_UTIL_generate_field_position(tdbb, relationName, &fieldPos);
if (fieldPos >= 0)
++fieldPos;
}
if (fieldPos >= 0)
{
RFR.RDB$FIELD_POSITION.NULL = FALSE;
RFR.RDB$FIELD_POSITION = SSHORT(fieldPos);
}
if (baseField.hasData())
{
RFR.RDB$BASE_FIELD.NULL = FALSE;
strcpy(RFR.RDB$BASE_FIELD, baseField.c_str());
}
if (viewContext.specified)
{
fb_assert(baseField.hasData());
RFR.RDB$VIEW_CONTEXT.NULL = FALSE;
RFR.RDB$VIEW_CONTEXT = viewContext.value;
DYN_UTIL_find_field_source(tdbb, transaction, relationName, viewContext.value,
baseField.c_str(), RFR.RDB$FIELD_SOURCE);
}
END_MODIFY
}
END_FOR
}
void RelationNode::FieldDefinition::store(thread_db* tdbb, jrd_tra* transaction)
{
Attachment* const attachment = transaction->tra_attachment;
AutoCacheRequest request(tdbb, drq_s_lfields, DYN_REQUESTS);
STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
RFR IN RDB$RELATION_FIELDS
{
strcpy(RFR.RDB$FIELD_NAME, name.c_str());
strcpy(RFR.RDB$RELATION_NAME, relationName.c_str());
strcpy(RFR.RDB$FIELD_SOURCE, fieldSource.c_str());
RFR.RDB$SYSTEM_FLAG = 0;
RFR.RDB$COLLATION_ID.NULL = TRUE;
RFR.RDB$GENERATOR_NAME.NULL = TRUE;
RFR.RDB$IDENTITY_TYPE.NULL = TRUE;
RFR.RDB$NULL_FLAG.NULL = TRUE;
RFR.RDB$DEFAULT_SOURCE.NULL = TRUE;
RFR.RDB$DEFAULT_VALUE.NULL = TRUE;
RFR.RDB$FIELD_POSITION.NULL = TRUE;
RFR.RDB$VIEW_CONTEXT.NULL = TRUE;
RFR.RDB$BASE_FIELD.NULL = TRUE;
///RFR.RDB$UPDATE_FLAG.NULL = TRUE;
if (collationId.specified)
{
RFR.RDB$COLLATION_ID.NULL = FALSE;
RFR.RDB$COLLATION_ID = collationId.value;
}
if (identitySequence.hasData())
{
RFR.RDB$GENERATOR_NAME.NULL = FALSE;
strcpy(RFR.RDB$GENERATOR_NAME, identitySequence.c_str());
RFR.RDB$IDENTITY_TYPE.NULL = FALSE;
RFR.RDB$IDENTITY_TYPE = IDENT_TYPE_BY_DEFAULT;
}
if (notNullFlag.specified)
{
RFR.RDB$NULL_FLAG.NULL = FALSE;
RFR.RDB$NULL_FLAG = notNullFlag.value;
}
if (defaultSource.hasData())
{
RFR.RDB$DEFAULT_SOURCE.NULL = FALSE;
attachment->storeMetaDataBlob(tdbb, transaction, &RFR.RDB$DEFAULT_SOURCE,
defaultSource);
}
if (defaultValue.length > 0)
{
RFR.RDB$DEFAULT_VALUE.NULL = FALSE;
attachment->storeBinaryBlob(tdbb, transaction, &RFR.RDB$DEFAULT_VALUE, defaultValue);
}
SLONG fieldPos = -1;
if (position.specified)
fieldPos = position.value;
else
{
DYN_UTIL_generate_field_position(tdbb, relationName, &fieldPos);
if (fieldPos >= 0)
++fieldPos;
}
if (fieldPos >= 0)
{
RFR.RDB$FIELD_POSITION.NULL = FALSE;
RFR.RDB$FIELD_POSITION = SSHORT(fieldPos);
}
if (baseField.hasData())
{
RFR.RDB$BASE_FIELD.NULL = FALSE;
strcpy(RFR.RDB$BASE_FIELD, baseField.c_str());
}
if (viewContext.specified)
{
fb_assert(baseField.hasData());
RFR.RDB$VIEW_CONTEXT.NULL = FALSE;
RFR.RDB$VIEW_CONTEXT = viewContext.value;
DYN_UTIL_find_field_source(tdbb, transaction, relationName, viewContext.value,
baseField.c_str(), RFR.RDB$FIELD_SOURCE);
}
}
END_STORE
}
// Delete local field.
//
// The rules for dropping a regular column:
//
// 1. the column is not referenced in any views.
// 2. the column is not part of any user defined indexes.
// 3. the column is not used in any SQL statements inside of store
// procedures or triggers
// 4. the column is not part of any check-constraints
//
// The rules for dropping a column that was created as primary key:
//
// 1. the column is not defined as any foreign keys
// 2. the column is not defined as part of compound primary keys
//
// The rules for dropping a column that was created as foreign key:
//
// 1. the column is not defined as a compound foreign key. A
// compound foreign key is a foreign key consisted of more
// than one columns.
//
// The RI enforcement for dropping primary key column is done by system
// triggers and the RI enforcement for dropping foreign key column is
// done by code and system triggers. See the functional description of
// deleteKeyConstraint function for detail.
void RelationNode::deleteLocalField(thread_db* tdbb, jrd_tra* transaction,
const MetaName& relationName, const MetaName& fieldName)
{
AutoCacheRequest request(tdbb, drq_l_dep_flds, DYN_REQUESTS);
bool found = false;
// Make sure that column is not referenced in any views.
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
X IN RDB$RELATION_FIELDS CROSS
Y IN RDB$RELATION_FIELDS CROSS
Z IN RDB$VIEW_RELATIONS
WITH X.RDB$RELATION_NAME EQ relationName.c_str() AND
X.RDB$FIELD_NAME EQ fieldName.c_str() AND
X.RDB$FIELD_NAME EQ Y.RDB$BASE_FIELD AND
X.RDB$FIELD_SOURCE EQ Y.RDB$FIELD_SOURCE AND
Y.RDB$RELATION_NAME EQ Z.RDB$VIEW_NAME AND
X.RDB$RELATION_NAME EQ Z.RDB$RELATION_NAME AND
Y.RDB$VIEW_CONTEXT EQ Z.RDB$VIEW_CONTEXT
{
// msg 52: "field %s from relation %s is referenced in view %s"
status_exception::raise(
Arg::PrivateDyn(52) << fieldName << relationName << Y.RDB$RELATION_NAME);
}
END_FOR
// If the column to be dropped is being used as a foreign key
// and the column was not part of any compound foreign key,
// then we can drop the column. But we have to drop the foreign key
// constraint first.
request.reset(tdbb, drq_g_rel_constr_nm, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
IDX IN RDB$INDICES CROSS
IDX_SEG IN RDB$INDEX_SEGMENTS CROSS
REL_CONST IN RDB$RELATION_CONSTRAINTS
WITH IDX.RDB$RELATION_NAME EQ relationName.c_str() AND
REL_CONST.RDB$RELATION_NAME EQ relationName.c_str() AND
IDX_SEG.RDB$FIELD_NAME EQ fieldName.c_str() AND
IDX.RDB$INDEX_NAME EQ IDX_SEG.RDB$INDEX_NAME AND
IDX.RDB$INDEX_NAME EQ REL_CONST.RDB$INDEX_NAME AND
REL_CONST.RDB$CONSTRAINT_TYPE EQ FOREIGN_KEY
{
if (IDX.RDB$SEGMENT_COUNT == 1)
{
deleteKeyConstraint(tdbb, transaction, relationName,
REL_CONST.RDB$CONSTRAINT_NAME, IDX.RDB$INDEX_NAME);
}
else
{
// msg 187: "field %s from relation %s is referenced in index %s"
status_exception::raise(
Arg::PrivateDyn(187) << fieldName << relationName << IDX.RDB$INDEX_NAME);
}
}
END_FOR
// Make sure that column is not referenced in any user-defined indexes.
// NOTE: You still could see the system generated indices even though
// they were already been deleted when dropping column that was used
// as foreign key before "commit".
request.reset(tdbb, drq_e_l_idx, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
IDX IN RDB$INDICES CROSS
IDX_SEG IN RDB$INDEX_SEGMENTS
WITH IDX.RDB$INDEX_NAME EQ IDX_SEG.RDB$INDEX_NAME AND
IDX.RDB$RELATION_NAME EQ relationName.c_str() AND
IDX_SEG.RDB$FIELD_NAME EQ fieldName.c_str() AND
NOT ANY REL_CONST IN RDB$RELATION_CONSTRAINTS
WITH REL_CONST.RDB$RELATION_NAME EQ IDX.RDB$RELATION_NAME AND
REL_CONST.RDB$INDEX_NAME EQ IDX.RDB$INDEX_NAME
{
// msg 187: "field %s from relation %s is referenced in index %s"
status_exception::raise(
Arg::PrivateDyn(187) <<
fieldName << relationName <<
fb_utils::exact_name_limit(IDX.RDB$INDEX_NAME, sizeof(IDX.RDB$INDEX_NAME)));
}
END_FOR
// Delete the automatically created generator for Identity columns.
request.reset(tdbb, drq_e_lfield, DYN_REQUESTS);
found = false;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
RFR IN RDB$RELATION_FIELDS
WITH RFR.RDB$FIELD_NAME EQ fieldName.c_str() AND
RFR.RDB$RELATION_NAME EQ relationName.c_str()
{
if (!RFR.RDB$GENERATOR_NAME.NULL)
DropSequenceNode::deleteIdentity(tdbb, transaction, RFR.RDB$GENERATOR_NAME);
ERASE RFR;
if (!RFR.RDB$SECURITY_CLASS.NULL &&
!strncmp(RFR.RDB$SECURITY_CLASS, SQL_SECCLASS_PREFIX, SQL_SECCLASS_PREFIX_LEN))
{
deleteSecurityClass(tdbb, transaction, RFR.RDB$SECURITY_CLASS);
}
found = true;
DropRelationNode::deleteGlobalField(tdbb, transaction, RFR.RDB$FIELD_SOURCE);
}
END_FOR
request.reset(tdbb, drq_e_fld_prvs, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
PRIV IN RDB$USER_PRIVILEGES
WITH PRIV.RDB$RELATION_NAME EQ relationName.c_str() AND
PRIV.RDB$FIELD_NAME EQ fieldName.c_str() AND
PRIV.RDB$OBJECT_TYPE = obj_relation
{
ERASE PRIV;
}
END_FOR
if (!found)
{
// msg 176: "column %s does not exist in table/view %s"
status_exception::raise(Arg::PrivateDyn(176) << fieldName << relationName);
}
}
void RelationNode::defineField(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction, AddColumnClause* clause, SSHORT position,
const ObjectsArray<MetaName>* pkCols)
{
dsql_fld* field = clause->field;
// Add the field to the relation being defined for parsing purposes.
bool permanent = false;
dsql_rel* relation = dsqlScratch->relation;
if (relation != NULL)
{
if (!(relation->rel_flags & REL_new_relation))
{
dsql_fld* permField = FB_NEW(dsqlScratch->getAttachment()->dbb_pool) dsql_fld(
dsqlScratch->getAttachment()->dbb_pool);
*permField = *field;
field = permField;
permanent = true;
}
field->fld_next = relation->rel_fields;
relation->rel_fields = field;
}
try
{
// Check for constraints.
ObjectsArray<Constraint> constraints;
bool notNullFlag = false;
if (clause->identity)
notNullFlag = true; // identity columns are implicitly not null
for (ObjectsArray<AddConstraintClause>::iterator ptr = clause->constraints.begin();
ptr != clause->constraints.end(); ++ptr)
{
makeConstraint(tdbb, dsqlScratch, transaction, &*ptr, constraints, &notNullFlag);
}
if (!notNullFlag && pkCols)
{
// Let's see if the field appears in a "primary_key (a, b, c)" relation constraint.
for (size_t i = 0; !notNullFlag && i < pkCols->getCount(); ++i)
{
if (field->fld_name == (*pkCols)[i].c_str())
notNullFlag = true;
}
}
FieldDefinition fieldDefinition(*tdbb->getDefaultPool());
fieldDefinition.relationName = name;
fieldDefinition.name = field->fld_name;
fieldDefinition.notNullFlag = notNullFlag;
if (position >= 0)
fieldDefinition.position = position;
if (field->typeOfName.hasData())
{
// Get the domain information.
if (!METD_get_domain(transaction, field, field->typeOfName))
{
// Specified domain or source field does not exist
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_dsql_domain_not_found) << field->typeOfName);
}
fieldDefinition.fieldSource = field->typeOfName;
}
else
{
string computedSource;
BlrDebugWriter::BlrData computedValue;
if (clause->computed)
{
field->flags |= FLD_computed;
defineComputed(dsqlScratch, dsqlNode, field, clause->computed,
computedSource, computedValue);
}
field->collate = clause->collate;
field->resolve(dsqlScratch);
// Generate a domain.
storeGlobalField(tdbb, transaction, fieldDefinition.fieldSource, field,
computedSource, computedValue);
}
if ((relation->rel_flags & REL_external) &&
(field->dtype == dtype_blob || field->dtype == dtype_array || field->dimensions))
{
const char* typeName = (field->dtype == dtype_blob ? "BLOB" : "ARRAY");
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_dsql_type_not_supp_ext_tab) << typeName << name << field->fld_name);
}
if (clause->collate.hasData())
DDL_resolve_intl_type(dsqlScratch, field, clause->collate);
if (clause->identity)
{
dsc desc;
MET_get_domain(tdbb, fieldDefinition.fieldSource, &desc, NULL);
if (!desc.isExact() || desc.dsc_scale != 0)
{
// Identity column @1 of table @2 must be exact numeric with zero scale.
status_exception::raise(Arg::PrivateDyn(273) << field->fld_name << name);
}
DYN_UTIL_generate_generator_name(tdbb, fieldDefinition.identitySequence);
CreateAlterSequenceNode::store(tdbb, transaction, fieldDefinition.identitySequence,
fb_sysflag_identity_generator, 0);
}
BlrDebugWriter::BlrData defaultValue;
if (clause->defaultValue)
{
if (defineDefault(tdbb, dsqlScratch, field, clause->defaultValue,
fieldDefinition.defaultSource, defaultValue) &&
notNullFlag)
{
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
Arg::Gds(isc_bad_default_value) <<
Arg::Gds(isc_invalid_clause) << "default null not null");
}
}
fieldDefinition.defaultValue = defaultValue;
if (clause->collate.hasData())
fieldDefinition.collationId = field->collationId;
fieldDefinition.store(tdbb, transaction);
// Define the field constraints.
for (ObjectsArray<Constraint>::iterator constraint(constraints.begin());
constraint != constraints.end();
++constraint)
{
if (constraint->type != Constraint::TYPE_FK)
constraint->columns.add(field->fld_name);
defineConstraint(tdbb, dsqlScratch, transaction, *constraint);
}
}
catch (const Exception&)
{
clearPermanentField(relation, permanent);
throw;
}
clearPermanentField(relation, permanent);
}
// Define a DEFAULT clause. Return true for DEFAULT NULL.
bool RelationNode::defineDefault(thread_db* /*tdbb*/, DsqlCompilerScratch* dsqlScratch,
dsql_fld* /*field*/, ValueSourceClause* clause, string& source, BlrDebugWriter::BlrData& value)
{
ValueExprNode* input = doDsqlPass(dsqlScratch, clause->value);
// Generate the blr expression.
dsqlScratch->getBlrData().clear();
dsqlScratch->getDebugData().clear();
dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5);
GEN_expr(dsqlScratch, input);
dsqlScratch->appendUChar(blr_eoc);
// Generate the source text.
source = clause->source;
value.assign(dsqlScratch->getBlrData());
return ExprNode::is<NullNode>(input);
}
// Make a constraint object from a legacy node.
void RelationNode::makeConstraint(thread_db* /*tdbb*/, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction, AddConstraintClause* clause, ObjectsArray<Constraint>& constraints,
bool* notNull)
{
switch (clause->constraintType)
{
case AddConstraintClause::CTYPE_NOT_NULL:
case AddConstraintClause::CTYPE_PK:
if (notNull && !*notNull)
{
*notNull = true;
Constraint& constraint = constraints.add();
constraint.type = Constraint::TYPE_NOT_NULL;
if (clause->constraintType == AddConstraintClause::CTYPE_NOT_NULL)
constraint.name = clause->name;
}
if (clause->constraintType == AddConstraintClause::CTYPE_NOT_NULL)
break;
// AddConstraintClause::CTYPE_PK falls into
case AddConstraintClause::CTYPE_UNIQUE:
{
Constraint& constraint = constraints.add();
constraint.type = clause->constraintType == AddConstraintClause::CTYPE_PK ?
Constraint::TYPE_PK : Constraint::TYPE_UNIQUE;
constraint.name = clause->name;
constraint.index = clause->index;
constraint.columns = clause->columns;
break;
}
case AddConstraintClause::CTYPE_FK:
{
Constraint& constraint = constraints.add();
constraint.type = Constraint::TYPE_FK;
constraint.name = clause->name;
constraint.columns = clause->columns;
constraint.refRelation = clause->refRelation;
constraint.refColumns = clause->refColumns;
// If there is a referenced table name but no referenced field names, the
// primary key of the referenced table designates the referenced fields.
if (clause->refColumns.isEmpty())
{
Array<NestConst<FieldNode> > refColumns;
METD_get_primary_key(transaction, clause->refRelation, refColumns);
// If there is NEITHER an explicitly referenced field name, NOR does
// the referenced table have a primary key to serve as the implicitly
// referenced field, fail.
if (refColumns.isEmpty())
{
// "REFERENCES table" without "(column)" requires PRIMARY
// KEY on referenced table
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_reftable_requires_pk));
}
else
{
const NestConst<FieldNode>* ptr = refColumns.begin();
for (const NestConst<FieldNode>* const end = refColumns.end(); ptr != end; ++ptr)
constraint.refColumns.add((*ptr)->dsqlName);
}
}
if (constraint.refColumns.getCount() != constraint.columns.getCount())
{
// Foreign key field count does not match primary key.
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_key_field_count_err));
}
// Define the foreign key index and the triggers that may be needed
// for referential integrity action.
constraint.index = clause->index;
if (clause->refAction)
{
if (clause->refAction->updateAction != 0)
{
switch (clause->refAction->updateAction)
{
case RefActionClause::ACTION_CASCADE:
constraint.refUpdateAction = RI_ACTION_CASCADE;
defineUpdateCascadeTrigger(dsqlScratch, constraint);
break;
case RefActionClause::ACTION_SET_DEFAULT:
constraint.refUpdateAction = RI_ACTION_DEFAULT;
defineSetDefaultTrigger(dsqlScratch, constraint, true);
break;
case RefActionClause::ACTION_SET_NULL:
constraint.refUpdateAction = RI_ACTION_NULL;
defineSetNullTrigger(dsqlScratch, constraint, true);
break;
default:
fb_assert(0);
// fall into
case RefActionClause::ACTION_NONE:
constraint.refUpdateAction = RI_ACTION_NONE;
break;
}
}
if (clause->refAction->deleteAction != 0)
{
switch (clause->refAction->deleteAction)
{
case RefActionClause::ACTION_CASCADE:
constraint.refDeleteAction = RI_ACTION_CASCADE;
defineDeleteCascadeTrigger(dsqlScratch, constraint);
break;
case RefActionClause::ACTION_SET_DEFAULT:
constraint.refDeleteAction = RI_ACTION_DEFAULT;
defineSetDefaultTrigger(dsqlScratch, constraint, false);
break;
case RefActionClause::ACTION_SET_NULL:
constraint.refDeleteAction = RI_ACTION_NULL;
defineSetNullTrigger(dsqlScratch, constraint, false);
break;
default:
fb_assert(0);
// fall into
case RefActionClause::ACTION_NONE:
constraint.refDeleteAction = RI_ACTION_NONE;
break;
}
}
}
break;
}
case AddConstraintClause::CTYPE_CHECK:
{
Constraint& constraint = constraints.add();
constraint.type = Constraint::TYPE_CHECK;
constraint.name = clause->name;
defineCheckConstraint(dsqlScratch, constraint, clause->check);
break;
}
}
}
// Define a constraint.
void RelationNode::defineConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction, Constraint& constraint)
{
if (constraint.name.isEmpty())
DYN_UTIL_generate_constraint_name(tdbb, constraint.name);
AutoCacheRequest request(tdbb, drq_s_rel_con, DYN_REQUESTS);
MetaName referredIndexName;
STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
CRT IN RDB$RELATION_CONSTRAINTS
{
CRT.RDB$INDEX_NAME.NULL = TRUE;
strcpy(CRT.RDB$CONSTRAINT_NAME, constraint.name.c_str());
strcpy(CRT.RDB$RELATION_NAME, name.c_str());
switch (constraint.type)
{
case Constraint::TYPE_CHECK:
strcpy(CRT.RDB$CONSTRAINT_TYPE, CHECK_CNSTRT);
break;
case Constraint::TYPE_NOT_NULL:
strcpy(CRT.RDB$CONSTRAINT_TYPE, NOT_NULL_CNSTRT);
break;
case Constraint::TYPE_PK:
strcpy(CRT.RDB$CONSTRAINT_TYPE, PRIMARY_KEY);
break;
case Constraint::TYPE_UNIQUE:
strcpy(CRT.RDB$CONSTRAINT_TYPE, UNIQUE_CNSTRT);
break;
case Constraint::TYPE_FK:
strcpy(CRT.RDB$CONSTRAINT_TYPE, FOREIGN_KEY);
break;
default:
fb_assert(false);
}
// These constraints require creation of an index.
switch (constraint.type)
{
case Constraint::TYPE_PK:
case Constraint::TYPE_UNIQUE:
case Constraint::TYPE_FK:
{
CreateIndexNode::Definition definition;
definition.relation = name;
definition.type = (constraint.type == Constraint::TYPE_PK ?
isc_dyn_def_primary_key :
(constraint.type == Constraint::TYPE_FK ? isc_dyn_def_foreign_key : 0));
definition.unique = constraint.type != Constraint::TYPE_FK;
if (constraint.index->descending)
definition.descending = true;
definition.columns = constraint.columns;
definition.refRelation = constraint.refRelation;
definition.refColumns = constraint.refColumns;
CreateIndexNode::store(tdbb, transaction, constraint.index->name,
definition, &referredIndexName);
CRT.RDB$INDEX_NAME.NULL = FALSE;
strcpy(CRT.RDB$INDEX_NAME, constraint.index->name.c_str());
checkForeignKeyTempScope(tdbb, transaction, name, referredIndexName);
// Check that we have references permissions on the table and
// fields that the index:referredIndexName is on.
SCL_check_index(tdbb, referredIndexName, 0, SCL_references);
break;
}
}
}
END_STORE
if (constraint.type == Constraint::TYPE_NOT_NULL)
{
fb_assert(constraint.columns.getCount() == 1);
DYN_UTIL_store_check_constraints(tdbb, transaction, constraint.name,
*constraint.columns.begin());
}
// Define the automatically generated triggers.
for (ObjectsArray<TriggerDefinition>::iterator trigger(constraint.triggers.begin());
trigger != constraint.triggers.end();
++trigger)
{
trigger->store(tdbb, dsqlScratch, transaction);
DYN_UTIL_store_check_constraints(tdbb, transaction, constraint.name, trigger->name);
}
if (constraint.type == Constraint::TYPE_NOT_NULL || constraint.type == Constraint::TYPE_CHECK)
return;
// Make sure unique field names were specified for UNIQUE/PRIMARY/FOREIGN
// All fields must have the NOT NULL attribute specified for UNIQUE/PRIMARY.
request.reset(tdbb, drq_c_unq_nam2, DYN_REQUESTS);
int allCount = 0;
int uniqueCount = 0;
ObjectsArray<MetaName> fieldList;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
IDS IN RDB$INDEX_SEGMENTS CROSS
RFR IN RDB$RELATION_FIELDS CROSS
FLX IN RDB$FIELDS
WITH IDS.RDB$INDEX_NAME EQ constraint.index->name.c_str() AND
RFR.RDB$RELATION_NAME EQ name.c_str() AND
RFR.RDB$FIELD_NAME EQ IDS.RDB$FIELD_NAME AND
FLX.RDB$FIELD_NAME EQ RFR.RDB$FIELD_SOURCE
REDUCED TO IDS.RDB$FIELD_NAME, IDS.RDB$INDEX_NAME, FLX.RDB$NULL_FLAG
SORTED BY ASCENDING IDS.RDB$FIELD_NAME
{
if ((FLX.RDB$NULL_FLAG.NULL || !FLX.RDB$NULL_FLAG) &&
(RFR.RDB$NULL_FLAG.NULL || !RFR.RDB$NULL_FLAG) &&
constraint.type == Constraint::TYPE_PK)
{
// msg 123: "Field: %s not defined as NOT NULL - can't be used in PRIMARY KEY
// constraint definition"
status_exception::raise(Arg::PrivateDyn(123) << MetaName(RFR.RDB$FIELD_NAME));
}
++uniqueCount;
fieldList.add() = IDS.RDB$FIELD_NAME;
}
END_FOR
request.reset(tdbb, drq_n_idx_seg, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
IDS IN RDB$INDEX_SEGMENTS
WITH IDS.RDB$INDEX_NAME EQ constraint.index->name.c_str()
{
++allCount;
}
END_FOR
if (uniqueCount != allCount)
{
// msg 124: "A column name is repeated in the definition of constraint: %s"
status_exception::raise(Arg::PrivateDyn(124) << constraint.name);
}
if (constraint.type == Constraint::TYPE_FK)
{
request.reset(tdbb, drq_s_ref_con, DYN_REQUESTS);
STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
REF IN RDB$REF_CONSTRAINTS
{
AutoCacheRequest request2(tdbb, drq_l_intg_con, DYN_REQUESTS);
FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
CRT IN RDB$RELATION_CONSTRAINTS
WITH CRT.RDB$INDEX_NAME EQ referredIndexName.c_str() AND
(CRT.RDB$CONSTRAINT_TYPE = PRIMARY_KEY OR
CRT.RDB$CONSTRAINT_TYPE = UNIQUE_CNSTRT)
{
fb_utils::exact_name_limit(CRT.RDB$CONSTRAINT_NAME, sizeof(CRT.RDB$CONSTRAINT_NAME));
strcpy(REF.RDB$CONST_NAME_UQ, CRT.RDB$CONSTRAINT_NAME);
strcpy(REF.RDB$CONSTRAINT_NAME, constraint.name.c_str());
REF.RDB$UPDATE_RULE.NULL = FALSE;
strcpy(REF.RDB$UPDATE_RULE, constraint.refUpdateAction);
REF.RDB$DELETE_RULE.NULL = FALSE;
strcpy(REF.RDB$DELETE_RULE, constraint.refDeleteAction);
}
END_FOR
}
END_STORE
}
else
{
// For PRIMARY KEY/UNIQUE constraints, make sure same set of columns
// is not used in another constraint of either type.
request.reset(tdbb, drq_c_dup_con, DYN_REQUESTS);
MetaName indexName;
int listIndex = -1;
bool found = false;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
CRT IN RDB$RELATION_CONSTRAINTS CROSS
IDS IN RDB$INDEX_SEGMENTS OVER RDB$INDEX_NAME
WITH CRT.RDB$RELATION_NAME EQ name.c_str() AND
CRT.RDB$CONSTRAINT_NAME NE constraint.name.c_str() AND
(CRT.RDB$CONSTRAINT_TYPE EQ PRIMARY_KEY OR
CRT.RDB$CONSTRAINT_TYPE EQ UNIQUE_CNSTRT)
SORTED BY CRT.RDB$INDEX_NAME, DESCENDING IDS.RDB$FIELD_NAME
{
if (indexName != CRT.RDB$INDEX_NAME)
{
if (listIndex >= 0)
found = false;
if (found)
break;
listIndex = fieldList.getCount() - 1;
indexName = CRT.RDB$INDEX_NAME;
found = true;
}
if (listIndex >= 0)
{
if (fieldList[listIndex--] != IDS.RDB$FIELD_NAME)
found = false;
}
else
found = false;
}
END_FOR
if (listIndex >= 0)
found = false;
if (found)
{
// msg 126: "Same set of columns cannot be used in more than one PRIMARY KEY
// and/or UNIQUE constraint definition"
status_exception::raise(Arg::PrivateDyn(126));
}
}
}
// Generate triggers to implement the CHECK clause, either at the field or table level.
void RelationNode::defineCheckConstraint(DsqlCompilerScratch* dsqlScratch, Constraint& constraint,
BoolSourceClause* clause)
{
// Create the INSERT trigger.
defineCheckConstraintTrigger(dsqlScratch, constraint, clause, PRE_STORE_TRIGGER);
// Create the UPDATE trigger.
defineCheckConstraintTrigger(dsqlScratch, constraint, clause, PRE_MODIFY_TRIGGER);
}
// Define a check constraint trigger.
void RelationNode::defineCheckConstraintTrigger(DsqlCompilerScratch* dsqlScratch,
Constraint& constraint, BoolSourceClause* clause, FB_UINT64 triggerType)
{
thread_db* tdbb = JRD_get_thread_data();
MemoryPool& pool = *tdbb->getDefaultPool();
AutoSetRestore<bool> autoCheckConstraintTrigger(&dsqlScratch->checkConstraintTrigger, true);
Constraint::BlrWriter& blrWriter = constraint.blrWritersHolder.add();
blrWriter.init(dsqlScratch);
// Specify that the trigger should abort if the condition is not met.
NestConst<CompoundStmtNode> actionNode = FB_NEW(pool) CompoundStmtNode(pool);
ExceptionNode* exceptionNode = FB_NEW(pool) ExceptionNode(pool, CHECK_CONSTRAINT_EXCEPTION);
exceptionNode->exception->type = ExceptionItem::GDS_CODE;
actionNode->statements.add(exceptionNode);
// Generate the trigger blr.
dsqlScratch->getBlrData().clear();
dsqlScratch->getDebugData().clear();
dsqlScratch->appendUChar(blr_begin);
// 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.
dsqlScratch->resetContextStack();
// CVC: I thought I could disable the OLD context here to avoid "ambiguous field name"
// errors in pre_store and pre_modify triggers. Also, what sense can I make from NEW in
// pre_delete? However, we clash at JRD with "no current record for fetch operation".
dsqlNode->alias = OLD_CONTEXT;
dsql_ctx* oldContext = PASS1_make_context(dsqlScratch, dsqlNode);
oldContext->ctx_flags |= CTX_system;
dsqlNode->alias = NEW_CONTEXT;
dsql_ctx* newContext = PASS1_make_context(dsqlScratch, dsqlNode);
newContext->ctx_flags |= CTX_system;
// Generate the condition for firing the trigger.
NotBoolNode* notNode = FB_NEW(pool) NotBoolNode(pool, clause->value);
BoolExprNode* condition = notNode->dsqlPass(dsqlScratch);
dsqlScratch->appendUChar(blr_if);
GEN_expr(dsqlScratch, condition);
// Generate the action statement for the trigger.
Node::doDsqlPass(dsqlScratch, actionNode)->genBlr(dsqlScratch);
dsqlScratch->appendUChar(blr_end); // of if (as there's no ELSE branch)
dsqlScratch->appendUChar(blr_end); // of begin
dsqlScratch->appendUChar(blr_eoc); // end of the blr
dsqlScratch->resetContextStack();
// Move the blr to the constraint blrWriter.
blrWriter.getBlrData().join(dsqlScratch->getBlrData());
TriggerDefinition& trigger = constraint.triggers.add();
trigger.systemFlag = fb_sysflag_check_constraint;
trigger.relationName = name;
trigger.type = triggerType;
trigger.source = clause->source;
trigger.blrData = blrWriter.getBlrData();
}
// Define "on delete|update set default" trigger (for referential integrity) along with its blr.
void RelationNode::defineSetDefaultTrigger(DsqlCompilerScratch* dsqlScratch,
Constraint& constraint, bool onUpdate)
{
fb_assert(constraint.columns.getCount() == constraint.refColumns.getCount());
fb_assert(constraint.columns.hasData());
Constraint::BlrWriter& blrWriter = constraint.blrWritersHolder.add();
blrWriter.init(dsqlScratch);
generateUnnamedTriggerBeginning(constraint, onUpdate, blrWriter);
const int BLOB_BUFFER_SIZE = 4096; // to read in blr blob for default values
UCHAR defaultVal[BLOB_BUFFER_SIZE];
for (ObjectsArray<MetaName>::const_iterator column(constraint.columns.begin());
column != constraint.columns.end();
++column)
{
blrWriter.appendUChar(blr_assignment);
// ASF: This is wrong way to do the thing. See CORE-3073.
// Here stuff the default value as blr_literal .... or blr_null
// if this column does not have an applicable default.
// The default is determined in many cases:
// (1) the info. for the column is in memory. (This is because
// the column is being created in this ddl statement).
// (1-a) the table has a column level default. We get this by
// searching the dsql parse tree.
// (1-b) the table does not have a column level default, but
// has a domain default. We get the domain name from the dsql
// parse tree and call METD_get_domain_default to read the
// default from the system tables.
// (2) The default-info for this column is not in memory (This is
// because this is an alter table ddl statement). The table
// already exists; therefore we get the column and/or domain
// default value from the system tables by calling:
// METD_get_col_default().
bool foundDefault = false;
bool searchForDefault = true;
// Search the parse tree to find the column
for (NestConst<Clause>* ptr = clauses.begin(); ptr != clauses.end(); ++ptr)
{
if ((*ptr)->type != Clause::TYPE_ADD_COLUMN)
continue;
AddColumnClause* clause = static_cast<AddColumnClause*>(ptr->getObject());
if (*column != clause->field->fld_name)
continue;
// Now we have the right column in the parse tree. case (1) above
if (clause->defaultValue)
{
// case (1-a) above: There is a column level default
dsqlScratch->getBlrData().clear();
dsqlScratch->getDebugData().clear();
GEN_expr(dsqlScratch, clause->defaultValue->value);
foundDefault = true;
searchForDefault = false;
// Move the blr to the constraint blrWriter.
blrWriter.getBlrData().join(dsqlScratch->getBlrData());
}
else
{
if (!clause->field->typeOfName.hasData())
break;
// case: (1-b): Domain name is available. Column level default
// is not declared. So get the domain default.
const USHORT defaultLen = METD_get_domain_default(dsqlScratch->getTransaction(),
clause->field->typeOfName, &foundDefault, defaultVal, sizeof(defaultVal));
searchForDefault = false;
if (foundDefault)
stuffDefaultBlr(ByteChunk(defaultVal, defaultLen), blrWriter);
else
{
// Neither column level nor domain level default exists.
blrWriter.appendUChar(blr_null);
}
}
break;
}
if (searchForDefault)
{
// case 2: See if the column/domain has already been created.
const USHORT defaultLen = METD_get_col_default(dsqlScratch->getTransaction(),
name.c_str(), column->c_str(), &foundDefault, defaultVal, sizeof(defaultVal));
if (foundDefault)
stuffDefaultBlr(ByteChunk(defaultVal, defaultLen), blrWriter);
else
blrWriter.appendUChar(blr_null);
}
// The context for the foreign key relation.
blrWriter.appendUChar(blr_field);
blrWriter.appendUChar(2);
blrWriter.appendNullString(0, column->c_str());
}
blrWriter.appendUChar(blr_end);
if (onUpdate)
blrWriter.appendUCharRepeated(blr_end, 3);
blrWriter.appendUChar(blr_eoc); // end of the blr
TriggerDefinition& trigger = constraint.triggers.add();
trigger.systemFlag = fb_sysflag_referential_constraint;
trigger.fkTrigger = true;
trigger.relationName = constraint.refRelation;
trigger.type = (onUpdate ? POST_MODIFY_TRIGGER : POST_ERASE_TRIGGER);
trigger.blrData = blrWriter.getBlrData();
}
// Define "on delete/update set null" trigger (for referential integrity).
// The trigger blr is the same for both the delete and update cases. Only difference is its
// TRIGGER_TYPE (ON DELETE or ON UPDATE).
// When onUpdate parameter == true is an update trigger.
void RelationNode::defineSetNullTrigger(DsqlCompilerScratch* dsqlScratch, Constraint& constraint,
bool onUpdate)
{
fb_assert(constraint.columns.getCount() == constraint.refColumns.getCount());
fb_assert(constraint.columns.hasData());
Constraint::BlrWriter& blrWriter = constraint.blrWritersHolder.add();
blrWriter.init(dsqlScratch);
generateUnnamedTriggerBeginning(constraint, onUpdate, blrWriter);
for (ObjectsArray<MetaName>::const_iterator column(constraint.columns.begin());
column != constraint.columns.end();
++column)
{
blrWriter.appendUChar(blr_assignment);
blrWriter.appendUChar(blr_null);
blrWriter.appendUChar(blr_field);
blrWriter.appendUChar(2);
blrWriter.appendNullString(0, column->c_str());
}
blrWriter.appendUChar(blr_end);
if (onUpdate)
blrWriter.appendUCharRepeated(blr_end, 3);
blrWriter.appendUChar(blr_eoc); // end of the blr
TriggerDefinition& trigger = constraint.triggers.add();
trigger.systemFlag = fb_sysflag_referential_constraint;
trigger.fkTrigger = true;
trigger.relationName = constraint.refRelation;
trigger.type = (onUpdate ? POST_MODIFY_TRIGGER : POST_ERASE_TRIGGER);
trigger.blrData = blrWriter.getBlrData();
}
// Define "on delete cascade" trigger (for referential integrity) along with the trigger blr.
void RelationNode::defineDeleteCascadeTrigger(DsqlCompilerScratch* dsqlScratch,
Constraint& constraint)
{
fb_assert(constraint.columns.getCount() == constraint.refColumns.getCount());
fb_assert(constraint.columns.hasData());
Constraint::BlrWriter& blrWriter = constraint.blrWritersHolder.add();
blrWriter.init(dsqlScratch);
blrWriter.appendUChar(blr_for);
blrWriter.appendUChar(blr_rse);
// The context for the prim. key relation.
blrWriter.appendUChar(1);
blrWriter.appendUChar(blr_relation);
blrWriter.appendNullString(0, name.c_str());
// The context for the foreign key relation.
blrWriter.appendUChar(2);
// Generate the blr for: foreign_key == primary_key.
stuffMatchingBlr(constraint, blrWriter);
blrWriter.appendUChar(blr_erase);
blrWriter.appendUChar(2);
blrWriter.appendUChar(blr_eoc); // end of the blr
TriggerDefinition& trigger = constraint.triggers.add();
trigger.systemFlag = fb_sysflag_referential_constraint;
trigger.fkTrigger = true;
trigger.relationName = constraint.refRelation;
trigger.type = POST_ERASE_TRIGGER;
trigger.blrData = blrWriter.getBlrData();
}
// Define "on update cascade" trigger (for referential integrity) along with the trigger blr.
void RelationNode::defineUpdateCascadeTrigger(DsqlCompilerScratch* dsqlScratch,
Constraint& constraint)
{
fb_assert(constraint.columns.getCount() == constraint.refColumns.getCount());
fb_assert(constraint.columns.hasData());
Constraint::BlrWriter& blrWriter = constraint.blrWritersHolder.add();
blrWriter.init(dsqlScratch);
generateUnnamedTriggerBeginning(constraint, true, blrWriter);
for (ObjectsArray<MetaName>::const_iterator column(constraint.columns.begin()),
refColumn(constraint.refColumns.begin());
column != constraint.columns.end();
++column, ++refColumn)
{
blrWriter.appendUChar(blr_assignment);
blrWriter.appendUChar(blr_field);
blrWriter.appendUChar(1);
blrWriter.appendNullString(0, refColumn->c_str());
blrWriter.appendUChar(blr_field);
blrWriter.appendUChar(2);
blrWriter.appendNullString(0, column->c_str());
}
blrWriter.appendUCharRepeated(blr_end, 4);
blrWriter.appendUChar(blr_eoc); // end of the blr
TriggerDefinition& trigger = constraint.triggers.add();
trigger.systemFlag = fb_sysflag_referential_constraint;
trigger.fkTrigger = true;
trigger.relationName = constraint.refRelation;
trigger.type = POST_MODIFY_TRIGGER;
trigger.blrData = blrWriter.getBlrData();
}
// Common code factored out.
void RelationNode::generateUnnamedTriggerBeginning(Constraint& constraint, bool onUpdate,
BlrDebugWriter& blrWriter)
{
// For ON UPDATE TRIGGER only: generate the trigger firing condition:
// If prim_key.old_value != prim_key.new value.
// Note that the key could consist of multiple columns.
if (onUpdate)
{
stuffTriggerFiringCondition(constraint, blrWriter);
blrWriter.appendUCharRepeated(blr_begin, 2);
}
blrWriter.appendUChar(blr_for);
blrWriter.appendUChar(blr_rse);
// The context for the prim. key relation.
blrWriter.appendUChar(1);
blrWriter.appendUChar(blr_relation);
blrWriter.appendNullString(0, name.c_str());
// The context for the foreign key relation.
blrWriter.appendUChar(2);
// Generate the blr for: foreign_key == primary_key.
stuffMatchingBlr(constraint, blrWriter);
blrWriter.appendUChar(blr_modify);
blrWriter.appendUChar(2);
blrWriter.appendUChar(2);
blrWriter.appendUChar(blr_begin);
}
// The defaultBlr passed is of the form:
// blr_version4 blr_literal ..... blr_eoc.
// Strip the blr_version4 and blr_eoc verbs and stuff the remaining blr in the blr stream in the
// statement.
void RelationNode::stuffDefaultBlr(const ByteChunk& defaultBlr, BlrDebugWriter& blrWriter)
{
fb_assert(defaultBlr.length > 2 && defaultBlr.data[defaultBlr.length - 1] == blr_eoc);
fb_assert(*defaultBlr.data == blr_version4 || *defaultBlr.data == blr_version5);
blrWriter.appendBytes(defaultBlr.data + 1, defaultBlr.length - 2);
}
// Generate blr to express: foreign_key == primary_key
// ie., for_key.column_1 = prim_key.column_1 and
// for_key.column_2 = prim_key.column_2 and .... so on.
void RelationNode::stuffMatchingBlr(Constraint& constraint, BlrDebugWriter& blrWriter)
{
// count of foreign key columns
fb_assert(constraint.refColumns.getCount() == constraint.columns.getCount());
fb_assert(constraint.refColumns.getCount() != 0);
blrWriter.appendUChar(blr_boolean);
size_t numFields = 0;
for (ObjectsArray<MetaName>::const_iterator column(constraint.columns.begin()),
refColumn(constraint.refColumns.begin());
column != constraint.columns.end();
++column, ++refColumn, ++numFields)
{
if (numFields + 1 < constraint.columns.getCount())
blrWriter.appendUChar(blr_and);
blrWriter.appendUChar(blr_eql);
blrWriter.appendUChar(blr_field);
blrWriter.appendUChar(2);
blrWriter.appendNullString(0, column->c_str());
blrWriter.appendUChar(blr_field);
blrWriter.appendUChar(0);
blrWriter.appendNullString(0, refColumn->c_str());
};
blrWriter.appendUChar(blr_end);
}
// Generate blr to express: if (old.primary_key != new.primary_key).
// Do a column by column comparison.
void RelationNode::stuffTriggerFiringCondition(const Constraint& constraint, BlrDebugWriter& blrWriter)
{
blrWriter.appendUChar(blr_if);
size_t numFields = 0;
for (ObjectsArray<MetaName>::const_iterator column(constraint.refColumns.begin());
column != constraint.refColumns.end();
++column, ++numFields)
{
if (numFields + 1 < constraint.refColumns.getCount())
blrWriter.appendUChar(blr_or);
blrWriter.appendUChar(blr_neq);
blrWriter.appendUChar(blr_field);
blrWriter.appendUChar(0);
blrWriter.appendNullString(0, column->c_str());
blrWriter.appendUChar(blr_field);
blrWriter.appendUChar(1);
blrWriter.appendNullString(0, column->c_str());
}
}
//----------------------
void CreateRelationNode::print(string& text) const
{
text.printf(
"CreateRelationNode\n"
" name: '%s'\n",
name.c_str());
}
void CreateRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
saveRelation(tdbb, dsqlScratch, name, false, true);
if (externalFile)
{
fb_assert(dsqlScratch->relation);
dsqlScratch->relation->rel_flags |= REL_external;
}
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_TABLE, name);
DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_relation);
checkRelationTempScope(tdbb, transaction, name, relationType);
AutoCacheRequest request(tdbb, drq_s_rels2, DYN_REQUESTS);
STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
REL IN RDB$RELATIONS
{
strcpy(REL.RDB$RELATION_NAME, name.c_str());
REL.RDB$SYSTEM_FLAG = 0;
REL.RDB$FLAGS = REL_sql;
REL.RDB$RELATION_TYPE = relationType;
REL.RDB$VIEW_BLR.NULL = TRUE;
REL.RDB$VIEW_SOURCE.NULL = TRUE;
REL.RDB$EXTERNAL_FILE.NULL = TRUE;
if (externalFile)
{
if (externalFile->length() >= sizeof(REL.RDB$EXTERNAL_FILE))
status_exception::raise(Arg::Gds(isc_dyn_name_longer));
REL.RDB$EXTERNAL_FILE.NULL = FALSE;
strcpy(REL.RDB$EXTERNAL_FILE, externalFile->c_str());
if (ISC_check_if_remote(externalFile->c_str(), false))
status_exception::raise(Arg::PrivateDyn(163));
// Check for any path, present in filename.
// If miss it, file will be searched in External Tables Dirs,
// that's why no expand_filename required.
PathName path, file;
PathUtils::splitLastComponent(path, file, externalFile->c_str());
if (path.hasData()) // path component present in filename
{
ISC_expand_filename(REL.RDB$EXTERNAL_FILE, strlen(REL.RDB$EXTERNAL_FILE),
REL.RDB$EXTERNAL_FILE, sizeof(REL.RDB$EXTERNAL_FILE), false);
}
REL.RDB$RELATION_TYPE = rel_external;
}
}
END_STORE
storePrivileges(tdbb, transaction, name, obj_relation, ALL_PRIVILEGES);
ObjectsArray<Constraint> constraints;
const ObjectsArray<MetaName>* pkCols = findPkColumns();
SSHORT position = 0;
for (NestConst<Clause>* i = clauses.begin(); i != clauses.end(); ++i)
{
switch ((*i)->type)
{
case Clause::TYPE_ADD_COLUMN:
defineField(tdbb, dsqlScratch, transaction,
static_cast<AddColumnClause*>(i->getObject()), position, pkCols);
++position;
break;
case Clause::TYPE_ADD_CONSTRAINT:
makeConstraint(tdbb, dsqlScratch, transaction,
static_cast<AddConstraintClause*>(i->getObject()), constraints);
break;
default:
fb_assert(false);
break;
}
}
for (ObjectsArray<Constraint>::iterator constraint(constraints.begin());
constraint != constraints.end();
++constraint)
{
defineConstraint(tdbb, dsqlScratch, transaction, *constraint);
}
dsqlScratch->relation->rel_flags &= ~REL_creating;
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_TABLE, name);
savePoint.release(); // everything is ok
// Update DSQL cache
METD_drop_relation(transaction, name);
MET_dsql_cache_release(tdbb, SYM_relation, name);
}
// Starting from the elements in a table definition, locate the PK columns if given in a
// separate table constraint declaration.
const ObjectsArray<MetaName>* CreateRelationNode::findPkColumns()
{
for (const NestConst<Clause>* i = clauses.begin(); i != clauses.end(); ++i)
{
if ((*i)->type == Clause::TYPE_ADD_CONSTRAINT)
{
const AddConstraintClause* clause = static_cast<const AddConstraintClause*>(i->getObject());
if (clause->constraintType == AddConstraintClause::CTYPE_PK)
return &clause->columns;
}
}
return NULL;
}
//----------------------
void AlterRelationNode::print(string& text) const
{
text.printf(
"AlterRelationNode\n"
" name: '%s'\n",
name.c_str());
}
void AlterRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
saveRelation(tdbb, dsqlScratch, name, false, false);
if (!dsqlScratch->relation)
{
//// TODO: <Missing arg #1 - possibly status vector overflow>
/***
char linecol[64];
sprintf(linecol, "At line %d, column %d.", (int) dsqlNode->line, (int) dsqlNode->column);
***/
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
Arg::Gds(isc_dsql_relation_err) <<
Arg::Gds(isc_random) << name /***<<
Arg::Gds(isc_random) << linecol***/);
}
// If there is an error, get rid of the cached data.
try
{
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_TABLE, name);
ObjectsArray<Constraint> constraints;
for (NestConst<Clause>* i = clauses.begin(); i != clauses.end(); ++i)
{
switch ((*i)->type)
{
case Clause::TYPE_ADD_COLUMN:
defineField(tdbb, dsqlScratch, transaction,
static_cast<AddColumnClause*>(i->getObject()), -1, NULL);
break;
case Clause::TYPE_ALTER_COL_TYPE:
modifyField(tdbb, dsqlScratch, transaction,
static_cast<AlterColTypeClause*>(i->getObject()));
break;
case Clause::TYPE_ALTER_COL_NAME:
{
const AlterColNameClause* clause =
static_cast<const AlterColNameClause*>(i->getObject());
AutoRequest request;
bool found = false;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
RFL IN RDB$RELATION_FIELDS
WITH RFL.RDB$FIELD_NAME EQ clause->fromName.c_str() AND
RFL.RDB$RELATION_NAME EQ name.c_str()
{
found = true;
MODIFY RFL
checkViewDependency(tdbb, transaction, name, clause->fromName);
checkSpTrigDependency(tdbb, transaction, name, clause->fromName);
if (!fieldExists(tdbb, transaction, name, clause->toName))
{
strcpy(RFL.RDB$FIELD_NAME, clause->toName.c_str());
AlterDomainNode::modifyLocalFieldIndex(tdbb, transaction, name,
clause->fromName, clause->toName);
}
else
{
// msg 205: Cannot rename field %s to %s. A field with that name
// already exists in table %s.
status_exception::raise(
Arg::PrivateDyn(205) << clause->fromName << clause->toName << name);
}
END_MODIFY
}
END_FOR
if (!found)
{
// msg 176: "column %s does not exist in table/view %s"
status_exception::raise(Arg::PrivateDyn(176) << clause->fromName << name);
}
break;
}
case Clause::TYPE_ALTER_COL_NULL:
{
const AlterColNullClause* clause =
static_cast<const AlterColNullClause*>(i->getObject());
//// FIXME: This clause allows inconsistencies like setting a field with a
//// not null CONSTRAINT as nullable, or setting a field based on a not null
//// domain as nullable (while ISQL shows it as not null).
AutoRequest request;
bool found = false;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
RFL IN RDB$RELATION_FIELDS
WITH RFL.RDB$FIELD_NAME EQ clause->name.c_str() AND
RFL.RDB$RELATION_NAME EQ name.c_str()
{
found = true;
MODIFY RFL
if (!clause->notNullFlag && !RFL.RDB$GENERATOR_NAME.NULL)
{
// msg 274: Identity column @1 of table @2 cannot be changed to NULLable
status_exception::raise(Arg::PrivateDyn(274) << clause->name << name);
}
RFL.RDB$NULL_FLAG = SSHORT(clause->notNullFlag);
END_MODIFY
}
END_FOR
if (!found)
{
// msg 176: "column %s does not exist in table/view %s"
status_exception::raise(Arg::PrivateDyn(176) << clause->name << name);
}
break;
}
case Clause::TYPE_ALTER_COL_POS:
{
const AlterColPosClause* clause =
static_cast<const AlterColPosClause*>(i->getObject());
// CVC: Since now the parser accepts pos=1..N, let's subtract one here.
const SSHORT pos = clause->newPos - 1;
AutoRequest request;
bool found = false;
SSHORT oldPos;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
RFL IN RDB$RELATION_FIELDS
WITH RFL.RDB$FIELD_NAME EQ clause->name.c_str() AND
RFL.RDB$RELATION_NAME EQ name.c_str()
{
found = true;
oldPos = RFL.RDB$FIELD_POSITION;
}
END_FOR
if (!found)
{
// msg 176: "column %s does not exist in table/view %s"
status_exception::raise(Arg::PrivateDyn(176) << clause->name << name);
}
if (pos != oldPos)
modifyLocalFieldPosition(tdbb, transaction, name, clause->name, pos, oldPos);
break;
}
case Clause::TYPE_DROP_COLUMN:
{
// Fix for bug 8054:
// [CASCADE | RESTRICT] syntax is available in IB4.5, but not
// required until v5.0.
//
// Option CASCADE causes an error: unsupported DSQL construct.
// Option RESTRICT is default behaviour.
const DropColumnClause* clause =
static_cast<const DropColumnClause*>(i->getObject());
if (clause->cascade)
{
// Unsupported DSQL construct
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_dsql_construct_err));
}
deleteLocalField(tdbb, transaction, name, clause->name);
break;
}
case Clause::TYPE_ADD_CONSTRAINT:
makeConstraint(tdbb, dsqlScratch, transaction,
static_cast<AddConstraintClause*>(i->getObject()), constraints);
break;
case Clause::TYPE_DROP_CONSTRAINT:
{
const MetaName& constraintName =
static_cast<const DropConstraintClause*>(i->getObject())->name;
AutoCacheRequest request(tdbb, drq_e_rel_con, DYN_REQUESTS);
bool found = false;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
RC IN RDB$RELATION_CONSTRAINTS
WITH RC.RDB$CONSTRAINT_NAME EQ constraintName.c_str() AND
RC.RDB$RELATION_NAME EQ name.c_str()
{
found = true;
ERASE RC;
}
END_FOR
if (!found)
{
// msg 130: "CONSTRAINT %s does not exist."
status_exception::raise(Arg::PrivateDyn(130) << constraintName);
}
break;
}
default:
fb_assert(false);
break;
}
}
for (ObjectsArray<Constraint>::iterator constraint(constraints.begin());
constraint != constraints.end();
++constraint)
{
defineConstraint(tdbb, dsqlScratch, transaction, *constraint);
}
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_TABLE, name);
savePoint.release(); // everything is ok
// Update DSQL cache
METD_drop_relation(transaction, name);
MET_dsql_cache_release(tdbb, SYM_relation, name);
}
catch (const Exception&)
{
METD_drop_relation(transaction, name);
dsqlScratch->relation = NULL;
throw;
}
}
// Modify a field, as part of an alter table statement.
//
// If there are dependencies on the field, abort the operation
// unless the dependency is an index. In this case, rebuild the
// index once the operation has completed.
//
// If the original datatype of the field was a domain:
// if the new type is a domain, just make the change to the new domain
// if it exists
//
// if the new type is a base type, just make the change
//
// If the original datatype of the field was a base type:
// if the new type is a base type, just make the change
//
// if the new type is a domain, make the change to the field
// definition and remove the entry for RDB$FIELD_SOURCE from the original
// field. In other words ... clean up after ourselves
//
// The following conversions are not allowed:
// Blob to anything
// Array to anything
// Date to anything
// Char to any numeric
// Varchar to any numeric
// Anything to Blob
// Anything to Array
void AlterRelationNode::modifyField(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction, AlterColTypeClause* clause)
{
Attachment* const attachment = transaction->tra_attachment;
dsql_fld* field = clause->field;
// Add the field to the relation being defined for parsing purposes.
bool permanent = false;
dsql_rel* relation = dsqlScratch->relation;
if (relation)
{
if (!(relation->rel_flags & REL_new_relation))
{
dsql_fld* permField = FB_NEW(dsqlScratch->getAttachment()->dbb_pool)
dsql_fld(dsqlScratch->getAttachment()->dbb_pool);
*permField = *field;
field = permField;
permanent = true;
}
field->fld_next = relation->rel_fields;
relation->rel_fields = field;
}
try
{
bool found = false;
AutoRequest request;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
RFR IN RDB$RELATION_FIELDS CROSS
REL IN RDB$RELATIONS CROSS
FLD IN RDB$FIELDS
WITH REL.RDB$RELATION_NAME = RFR.RDB$RELATION_NAME AND
FLD.RDB$FIELD_NAME = RFR.RDB$FIELD_SOURCE AND
RFR.RDB$RELATION_NAME = name.c_str() AND
RFR.RDB$FIELD_NAME = field->fld_name.c_str()
{
found = true;
bool isView = !REL.RDB$VIEW_BLR.NULL;
if (!isView && (!FLD.RDB$COMPUTED_BLR.NULL != (clause->computed != NULL)))
{
// Cannot add or remove COMPUTED from column @1
status_exception::raise(Arg::PrivateDyn(249) << field->fld_name);
}
dyn_fld origDom;
DSC_make_descriptor(&origDom.dyn_dsc, FLD.RDB$FIELD_TYPE, FLD.RDB$FIELD_SCALE,
FLD.RDB$FIELD_LENGTH, FLD.RDB$FIELD_SUB_TYPE, FLD.RDB$CHARACTER_SET_ID,
FLD.RDB$COLLATION_ID);
origDom.dyn_fld_name = field->fld_name;
origDom.dyn_charbytelen = FLD.RDB$FIELD_LENGTH;
origDom.dyn_dtype = FLD.RDB$FIELD_TYPE;
origDom.dyn_precision = FLD.RDB$FIELD_PRECISION;
origDom.dyn_sub_type = FLD.RDB$FIELD_SUB_TYPE;
origDom.dyn_charlen = FLD.RDB$CHARACTER_LENGTH;
origDom.dyn_collation = FLD.RDB$COLLATION_ID;
origDom.dyn_null_flag = !FLD.RDB$NULL_FLAG.NULL && FLD.RDB$NULL_FLAG != 0;
origDom.dyn_fld_source = RFR.RDB$FIELD_SOURCE;
// If the original field type is an array, force its blr type to blr_blob.
const bool hasDimensions = FLD.RDB$DIMENSIONS != 0;
if (hasDimensions)
origDom.dyn_dtype = blr_blob;
const bool wasInternalDomain = fb_utils::implicit_domain(origDom.dyn_fld_source.c_str()) &&
RFR.RDB$BASE_FIELD.NULL;
string computedSource;
BlrDebugWriter::BlrData computedValue;
if (clause->computed)
{
defineComputed(dsqlScratch, dsqlNode, field, clause->computed, computedSource,
computedValue);
}
if (clause->defaultValue)
{
MODIFY RFR
if (!RFR.RDB$GENERATOR_NAME.NULL)
{
// msg 275: Identity column @1 of table @2 cannot have default value
status_exception::raise(Arg::PrivateDyn(275) << field->fld_name << name);
}
if (hasDimensions)
{
// msg 225: "Default value is not allowed for array type in field %s"
status_exception::raise(Arg::PrivateDyn(225) << field->fld_name);
}
if (clause->computed)
{
// msg 233: "Local column %s is computed, cannot set a default value"
status_exception::raise(Arg::PrivateDyn(233) << field->fld_name);
}
string defaultSource;
BlrDebugWriter::BlrData defaultValue;
defineDefault(tdbb, dsqlScratch, field, clause->defaultValue,
defaultSource, defaultValue);
RFR.RDB$DEFAULT_SOURCE.NULL = FALSE;
attachment->storeMetaDataBlob(tdbb, transaction,
&RFR.RDB$DEFAULT_SOURCE, defaultSource);
RFR.RDB$DEFAULT_VALUE.NULL = FALSE;
attachment->storeBinaryBlob(tdbb, transaction,
&RFR.RDB$DEFAULT_VALUE, defaultValue);
END_MODIFY
}
else if (clause->dropDefault)
{
MODIFY RFR
if (RFR.RDB$DEFAULT_VALUE.NULL)
{
if (FLD.RDB$DEFAULT_VALUE.NULL)
{
// msg 229: "Local column %s doesn't have a default"
status_exception::raise(Arg::PrivateDyn(229) << field->fld_name);
}
else
{
// msg 230: "Local column %s default belongs to domain %s"
status_exception::raise(
Arg::PrivateDyn(230) <<
field->fld_name << MetaName(FLD.RDB$FIELD_NAME));
}
}
else
{
RFR.RDB$DEFAULT_SOURCE.NULL = TRUE;
RFR.RDB$DEFAULT_VALUE.NULL = TRUE;
}
END_MODIFY
}
else
{
// We have the type. Default and type/domain are exclusive for now.
MetaName newDomainName;
dyn_fld newDom;
if (field->typeOfName.hasData())
{
// Case a1: Internal domain -> domain.
// Case a2: Domain -> domain.
newDomainName = field->typeOfName;
if (fb_utils::implicit_domain(newDomainName.c_str()))
{
// msg 224: "Cannot use the internal domain %s as new type for field %s".
status_exception::raise(
Arg::PrivateDyn(224) << newDomainName << field->fld_name);
}
// Get the domain information.
if (!METD_get_domain(dsqlScratch->getTransaction(), field, newDomainName))
{
// Specified domain or source field does not exist.
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_dsql_domain_not_found) << newDomainName);
}
DDL_resolve_intl_type(dsqlScratch, field, NULL);
// If the original definition was a base field type, remove the
// entries from RDB$FIELDS.
if (wasInternalDomain)
{
// Case a1: Internal domain -> domain.
ERASE FLD;
}
}
else
{
// Case b1: Internal domain -> internal domain.
// Case b2: Domain -> internal domain.
// If COMPUTED was specified but the type wasn't, we use the type of
// the computed expression.
if (clause->computed && field->dtype == dtype_unknown)
{
dsc desc;
MAKE_desc(dsqlScratch, &desc, clause->computed->value);
field->dtype = desc.dsc_dtype;
field->length = desc.dsc_length;
field->scale = desc.dsc_scale;
if (field->dtype <= dtype_any_text)
{
field->charSetId = DSC_GET_CHARSET(&desc);
field->collationId = DSC_GET_COLLATE(&desc);
}
else
field->subType = desc.dsc_sub_type;
}
field->resolve(dsqlScratch, true);
if (wasInternalDomain) // Case b1: Internal domain -> internal domain.
{
MODIFY FLD
updateRdbFields(field,
FLD.RDB$FIELD_TYPE,
FLD.RDB$FIELD_LENGTH,
FLD.RDB$FIELD_SUB_TYPE.NULL, FLD.RDB$FIELD_SUB_TYPE,
FLD.RDB$FIELD_SCALE.NULL, FLD.RDB$FIELD_SCALE,
FLD.RDB$CHARACTER_SET_ID.NULL, FLD.RDB$CHARACTER_SET_ID,
FLD.RDB$CHARACTER_LENGTH.NULL, FLD.RDB$CHARACTER_LENGTH,
FLD.RDB$FIELD_PRECISION.NULL, FLD.RDB$FIELD_PRECISION,
FLD.RDB$COLLATION_ID.NULL, FLD.RDB$COLLATION_ID,
FLD.RDB$SEGMENT_LENGTH.NULL, FLD.RDB$SEGMENT_LENGTH);
END_MODIFY
newDom.dyn_fld_source = origDom.dyn_fld_source;
}
else // Case b2: Domain -> internal domain.
storeGlobalField(tdbb, transaction, newDomainName, field);
}
if (!clause->computed && !isView)
{
if (newDomainName.hasData())
newDom.dyn_fld_source = newDomainName;
AlterDomainNode::getDomainType(tdbb, transaction, newDom);
AlterDomainNode::checkUpdate(origDom, newDom);
if (!RFR.RDB$GENERATOR_NAME.NULL)
{
if (!newDom.dyn_dsc.isExact() || newDom.dyn_dsc.dsc_scale != 0)
{
// Identity column @1 of table @2 must be exact numeric with zero scale.
status_exception::raise(Arg::PrivateDyn(273) << field->fld_name << name);
}
}
}
if (newDomainName.hasData())
{
MODIFY RFR USING
RFR.RDB$FIELD_SOURCE.NULL = FALSE;
strcpy(RFR.RDB$FIELD_SOURCE, newDomainName.c_str());
if (clause->computed)
{
RFR.RDB$UPDATE_FLAG.NULL = FALSE;
RFR.RDB$UPDATE_FLAG = 1;
}
RFR.RDB$COLLATION_ID.NULL = TRUE; // CORE-2426
END_MODIFY
}
}
if (clause->computed)
{
// We can alter FLD directly here because if we are setting a computed expression,
// it means the field already was computed. And if it was, it should be the
// "b1 case", where the field source does not change.
// This assumption may change, especially when this function starts dealing
// with views.
MODIFY FLD
FLD.RDB$COMPUTED_SOURCE.NULL = FALSE;
attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$COMPUTED_SOURCE,
computedSource);
FLD.RDB$COMPUTED_BLR.NULL = FALSE;
attachment->storeBinaryBlob(tdbb, transaction, &FLD.RDB$COMPUTED_BLR,
computedValue);
END_MODIFY
}
AutoRequest request2;
FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
PRM IN RDB$PROCEDURE_PARAMETERS
WITH PRM.RDB$RELATION_NAME = name.c_str() AND
PRM.RDB$FIELD_NAME = field->fld_name.c_str()
{
MODIFY PRM USING
strcpy(PRM.RDB$FIELD_SOURCE, RFR.RDB$FIELD_SOURCE);
END_MODIFY
}
END_FOR
request2.reset();
FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
ARG IN RDB$FUNCTION_ARGUMENTS
WITH ARG.RDB$RELATION_NAME = name.c_str() AND
ARG.RDB$FIELD_NAME = field->fld_name.c_str()
{
MODIFY ARG USING
strcpy(ARG.RDB$FIELD_SOURCE, RFR.RDB$FIELD_SOURCE);
END_MODIFY
}
END_FOR
}
END_FOR
if (!found)
{
// msg 176: "column %s does not exist in table/view %s"
status_exception::raise(Arg::PrivateDyn(176) << field->fld_name << name);
}
// Update any indices that exist.
AlterDomainNode::modifyLocalFieldIndex(tdbb, transaction, name,
field->fld_name, field->fld_name);
}
catch (const Exception&)
{
clearPermanentField(relation, permanent);
throw;
}
clearPermanentField(relation, permanent);
}
//----------------------
// Delete a global field if it's not used in others objects.
void DropRelationNode::deleteGlobalField(thread_db* tdbb, jrd_tra* transaction,
const MetaName& globalName)
{
AutoCacheRequest request(tdbb, drq_e_l_gfld, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
FLD IN RDB$FIELDS
WITH FLD.RDB$FIELD_NAME EQ globalName.c_str() AND
FLD.RDB$VALIDATION_SOURCE MISSING AND
FLD.RDB$NULL_FLAG MISSING AND
FLD.RDB$DEFAULT_SOURCE MISSING AND
FLD.RDB$FIELD_NAME STARTING WITH IMPLICIT_DOMAIN_PREFIX AND
(NOT ANY RFR IN RDB$RELATION_FIELDS WITH
RFR.RDB$FIELD_SOURCE EQ FLD.RDB$FIELD_NAME) AND
(NOT ANY PRM IN RDB$PROCEDURE_PARAMETERS WITH
PRM.RDB$FIELD_SOURCE EQ FLD.RDB$FIELD_NAME) AND
(NOT ANY ARG IN RDB$FUNCTION_ARGUMENTS WITH
ARG.RDB$FIELD_SOURCE EQ FLD.RDB$FIELD_NAME)
{
DropDomainNode::deleteDimensionRecords(tdbb, transaction, globalName);
ERASE FLD;
}
END_FOR
}
void DropRelationNode::print(string& text) const
{
text.printf(
"DropRelationNode\n"
" name: '%s'\n",
name.c_str());
}
void DropRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
jrd_rel* rel_drop = MET_lookup_relation(tdbb, name);
if (rel_drop)
MET_scan_relation(tdbb, rel_drop);
const dsql_rel* relation = METD_get_relation(transaction, dsqlScratch, name);
if (!relation && silent)
return;
// Check that DROP TABLE is dropping a table and that DROP VIEW is dropping a view.
if (view)
{
if (!relation || (relation && !(relation->rel_flags & REL_view)))
{
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_dsql_view_not_found) << name);
}
}
else
{
if (!relation || (relation && (relation->rel_flags & REL_view)))
{
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_dsql_table_not_found) << name);
}
}
const int ddlTriggerAction = (view ? DDL_TRIGGER_DROP_VIEW : DDL_TRIGGER_DROP_TABLE);
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
AutoCacheRequest request(tdbb, drq_l_relation, DYN_REQUESTS);
bool found = false;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
R IN RDB$RELATIONS
WITH R.RDB$RELATION_NAME EQ name.c_str()
{
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, ddlTriggerAction, name);
found = true;
}
END_FOR
request.reset(tdbb, drq_e_rel_con2, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
CRT IN RDB$RELATION_CONSTRAINTS
WITH CRT.RDB$RELATION_NAME EQ name.c_str() AND
(CRT.RDB$CONSTRAINT_TYPE EQ PRIMARY_KEY OR
CRT.RDB$CONSTRAINT_TYPE EQ UNIQUE_CNSTRT OR
CRT.RDB$CONSTRAINT_TYPE EQ FOREIGN_KEY)
SORTED BY ASCENDING CRT.RDB$CONSTRAINT_TYPE
{
ERASE CRT;
}
END_FOR
request.reset(tdbb, drq_e_rel_idxs, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
IDX IN RDB$INDICES
WITH IDX.RDB$RELATION_NAME EQ name.c_str()
{
DropIndexNode::deleteSegmentRecords(tdbb, transaction, IDX.RDB$INDEX_NAME);
ERASE IDX;
}
END_FOR
request.reset(tdbb, drq_e_trg_msgs2, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
TM IN RDB$TRIGGER_MESSAGES
CROSS T IN RDB$TRIGGERS
WITH T.RDB$RELATION_NAME EQ name.c_str() AND
TM.RDB$TRIGGER_NAME EQ T.RDB$TRIGGER_NAME
{
ERASE TM;
}
END_FOR
// CVC: Moved this block here to avoid SF Bug #1111570.
request.reset(tdbb, drq_e_rel_con3, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
CRT IN RDB$RELATION_CONSTRAINTS
WITH CRT.RDB$RELATION_NAME EQ name.c_str() AND
(CRT.RDB$CONSTRAINT_TYPE EQ CHECK_CNSTRT OR
CRT.RDB$CONSTRAINT_TYPE EQ NOT_NULL_CNSTRT)
{
ERASE CRT;
}
END_FOR
request.reset(tdbb, drq_e_rel_flds, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
RFR IN RDB$RELATION_FIELDS
WITH RFR.RDB$RELATION_NAME EQ name.c_str()
{
if (!RFR.RDB$GENERATOR_NAME.NULL)
DropSequenceNode::deleteIdentity(tdbb, transaction, RFR.RDB$GENERATOR_NAME);
ERASE RFR;
if (!RFR.RDB$SECURITY_CLASS.NULL &&
!strncmp(RFR.RDB$SECURITY_CLASS, SQL_SECCLASS_PREFIX, SQL_SECCLASS_PREFIX_LEN))
{
deleteSecurityClass(tdbb, transaction, RFR.RDB$SECURITY_CLASS);
}
deleteGlobalField(tdbb, transaction, RFR.RDB$FIELD_SOURCE);
}
END_FOR
request.reset(tdbb, drq_e_view_rels, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
VR IN RDB$VIEW_RELATIONS
WITH VR.RDB$VIEW_NAME EQ name.c_str()
{
ERASE VR;
}
END_FOR
request.reset(tdbb, drq_e_relation, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
R IN RDB$RELATIONS
WITH R.RDB$RELATION_NAME EQ name.c_str()
{
ERASE R;
if (!R.RDB$SECURITY_CLASS.NULL &&
!strncmp(R.RDB$SECURITY_CLASS, SQL_SECCLASS_PREFIX, SQL_SECCLASS_PREFIX_LEN))
{
deleteSecurityClass(tdbb, transaction, R.RDB$SECURITY_CLASS);
}
}
END_FOR
if (!found)
{
// msg 61: "Relation not found"
status_exception::raise(Arg::PrivateDyn(61));
}
// Triggers must be deleted after check constraints
MetaName triggerName;
request.reset(tdbb, drq_e_trigger2, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
X IN RDB$TRIGGERS
WITH X.RDB$RELATION_NAME EQ name.c_str()
{
triggerName = X.RDB$TRIGGER_NAME;
ERASE X;
AutoCacheRequest request2(tdbb, drq_e_trg_prv, DYN_REQUESTS);
FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
PRIV IN RDB$USER_PRIVILEGES
WITH PRIV.RDB$USER EQ triggerName.c_str() AND
PRIV.RDB$USER_TYPE = obj_trigger
{
ERASE PRIV;
}
END_FOR
}
END_FOR
request.reset(tdbb, drq_e_usr_prvs, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
PRIV IN RDB$USER_PRIVILEGES
WITH PRIV.RDB$RELATION_NAME EQ name.c_str() AND
PRIV.RDB$OBJECT_TYPE = obj_relation
{
ERASE PRIV;
}
END_FOR
request.reset(tdbb, drq_e_view_prv, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
PRIV IN RDB$USER_PRIVILEGES
WITH PRIV.RDB$USER EQ name.c_str() AND
PRIV.RDB$USER_TYPE = obj_view
{
ERASE PRIV;
}
END_FOR
if (found)
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, ddlTriggerAction, name);
else
{
// msg 61: "Relation not found"
status_exception::raise(Arg::PrivateDyn(61));
}
savePoint.release(); // everything is ok
METD_drop_relation(transaction, name.c_str());
MET_dsql_cache_release(tdbb, SYM_relation, name);
}
//----------------------
void CreateAlterViewNode::print(string& text) const
{
text.printf(
"CreateAlterViewNode\n"
" name: '%s'\n",
name.c_str());
}
DdlNode* CreateAlterViewNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
source.ltrim("\n\r\t ");
return DdlNode::dsqlPass(dsqlScratch);
}
void CreateAlterViewNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
Attachment* const attachment = transaction->tra_attachment;
const string& userName = attachment->att_user->usr_user_name;
const dsql_rel* modifyingView = NULL;
if (alter)
{
modifyingView = METD_get_relation(dsqlScratch->getTransaction(), dsqlScratch, name);
if (!modifyingView && !create)
status_exception::raise(Arg::Gds(isc_dyn_view_not_found) << name);
}
saveRelation(tdbb, dsqlScratch, name, true, modifyingView == NULL);
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
const int ddlTriggerAction = (modifyingView ? DDL_TRIGGER_ALTER_VIEW : DDL_TRIGGER_CREATE_VIEW);
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, ddlTriggerAction, name);
if (!modifyingView)
DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_relation);
// Compile the SELECT statement into a record selection expression, making sure to bump the
// context number since view contexts start at 1 (except for computed fields) -- note that
// calling PASS1_rse directly rather than PASS1_statement saves the context stack.
dsqlScratch->resetContextStack();
++dsqlScratch->contextNumber;
RseNode* rse = PASS1_rse(dsqlScratch, selectExpr, NULL);
dsqlScratch->getBlrData().clear();
dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5);
GEN_expr(dsqlScratch, rse);
dsqlScratch->appendUChar(blr_eoc);
// Store the blr and source string for the view definition.
if (modifyingView)
{
AutoCacheRequest request(tdbb, drq_m_view, DYN_REQUESTS);
bool found = false;
FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
REL IN RDB$RELATIONS
WITH REL.RDB$RELATION_NAME EQ name.c_str() AND
REL.RDB$VIEW_BLR NOT MISSING
{
found = true;
MODIFY REL
attachment->storeMetaDataBlob(tdbb, transaction, &REL.RDB$VIEW_SOURCE, source);
attachment->storeBinaryBlob(tdbb, transaction, &REL.RDB$VIEW_BLR,
dsqlScratch->getBlrData());
END_MODIFY
}
END_FOR
if (!found)
status_exception::raise(Arg::Gds(isc_dyn_view_not_found) << name);
AutoRequest request2;
FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
VR IN RDB$VIEW_RELATIONS
WITH VR.RDB$VIEW_NAME EQ name.c_str()
{
ERASE VR;
}
END_FOR
request2.reset();
FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
TRG IN RDB$TRIGGERS
WITH TRG.RDB$RELATION_NAME EQ name.c_str() AND
TRG.RDB$SYSTEM_FLAG EQ fb_sysflag_view_check
{
ERASE TRG;
}
END_FOR
}
else
{
AutoCacheRequest request(tdbb, drq_s_rels, DYN_REQUESTS);
STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
REL IN RDB$RELATIONS
{
strcpy(REL.RDB$RELATION_NAME, name.c_str());
REL.RDB$SYSTEM_FLAG = 0;
REL.RDB$FLAGS = REL_sql;
REL.RDB$RELATION_TYPE = SSHORT(rel_view);
attachment->storeMetaDataBlob(tdbb, transaction, &REL.RDB$VIEW_SOURCE, source);
attachment->storeBinaryBlob(tdbb, transaction, &REL.RDB$VIEW_BLR, dsqlScratch->getBlrData());
}
END_STORE
storePrivileges(tdbb, transaction, name, obj_relation, ALL_PRIVILEGES);
}
// Define the view source relations from the statement contexts and union contexts.
while (dsqlScratch->derivedContext.hasData())
dsqlScratch->context->push(dsqlScratch->derivedContext.pop());
while (dsqlScratch->unionContext.hasData())
dsqlScratch->context->push(dsqlScratch->unionContext.pop());
AutoCacheRequest request(tdbb, drq_s_view_rels, DYN_REQUESTS);
for (DsqlContextStack::iterator temp(*dsqlScratch->context); temp.hasData(); ++temp)
{
const dsql_ctx* context = temp.object();
const dsql_rel* relation = context->ctx_relation;
const dsql_prc* procedure = context->ctx_procedure;
if (relation || procedure)
{
const MetaName& refName = relation ? relation->rel_name : procedure->prc_name.identifier;
const char* contextName = context->ctx_alias.hasData() ?
context->ctx_alias.c_str() : refName.c_str();
ViewContextType ctxType;
if (relation)
{
if (!(relation->rel_flags & REL_view))
ctxType = VCT_TABLE;
else
ctxType = VCT_VIEW;
}
else //if (procedure)
ctxType = VCT_PROCEDURE;
STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
VRL IN RDB$VIEW_RELATIONS
{
strcpy(VRL.RDB$VIEW_NAME, name.c_str());
strcpy(VRL.RDB$RELATION_NAME, refName.c_str());
VRL.RDB$CONTEXT_TYPE = SSHORT(ctxType);
VRL.RDB$VIEW_CONTEXT = context->ctx_context;
strcpy(VRL.RDB$CONTEXT_NAME, contextName);
if (procedure && procedure->prc_name.package.hasData())
{
VRL.RDB$PACKAGE_NAME.NULL = FALSE;
strcpy(VRL.RDB$PACKAGE_NAME, procedure->prc_name.package.c_str());
}
else
VRL.RDB$PACKAGE_NAME.NULL = TRUE;
}
END_STORE
}
}
// Check privileges on base tables and views.
request.reset(tdbb, drq_l_view_rels, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
VRL IN RDB$VIEW_RELATIONS CROSS
PREL IN RDB$RELATIONS OVER RDB$RELATION_NAME
WITH VRL.RDB$PACKAGE_NAME MISSING AND
VRL.RDB$VIEW_NAME EQ name.c_str()
{
// CVC: This never matches so it causes unnecessary calls to verify,
// so I included a call to strip trailing blanks.
fb_utils::exact_name_limit(PREL.RDB$OWNER_NAME, sizeof(PREL.RDB$OWNER_NAME));
if (userName != PREL.RDB$OWNER_NAME)
{
SecurityClass::flags_t priv;
// I think this should be the responsability of DFW or the user will find ways to
// circumvent DYN.
priv = SCL_get_mask(tdbb, PREL.RDB$RELATION_NAME, "");
if (!(priv & SCL_select))
{
// msg 32: no permission for %s access to %s %s
status_exception::raise(
Arg::Gds(isc_no_priv) << Arg::Str("SELECT") << // Non-Translatable
// Remember, a view may be based on a view.
"TABLE/VIEW" << // Non-Translatable
// We want to print the name of the base table or view.
MetaName(PREL.RDB$RELATION_NAME));
}
}
}
END_FOR
// If there are field names defined for the view, match them in order with the items from the
// SELECT. Otherwise use all the fields from the rse node that was created from the select
// expression.
const NestConst<ValueExprNode>* ptr = NULL;
const NestConst<ValueExprNode>* end = NULL;
if (viewFields)
{
ptr = viewFields->items.begin();
end = viewFields->items.end();
}
// Go through the fields list, defining or modifying the local fields;
// If an expression is specified rather than a field, define a global
// field for the computed value as well.
ValueListNode* items = rse->dsqlSelectList;
NestConst<ValueExprNode>* itemsPtr = items->items.begin();
SortedArray<dsql_fld*> modifiedFields;
bool updatable = true;
SSHORT position = 0;
for (NestConst<ValueExprNode>* itemsEnd = items->items.end();
itemsPtr < itemsEnd; ++itemsPtr, ++position)
{
ValueExprNode* fieldNode = *itemsPtr;
// Determine the proper field name, replacing the default if necessary.
ValueExprNode* nameNode = fieldNode;
const char* aliasName = NULL;
while (nameNode->is<DsqlAliasNode>() || nameNode->is<DerivedFieldNode>() ||
nameNode->is<DsqlMapNode>())
{
DsqlAliasNode* aliasNode;
DsqlMapNode* mapNode;
DerivedFieldNode* derivedField;
if ((aliasNode = nameNode->as<DsqlAliasNode>()))
{
if (!aliasName)
aliasName = aliasNode->name.c_str();
nameNode = aliasNode->value;
}
else if ((mapNode = nameNode->as<DsqlMapNode>()))
nameNode = mapNode->map->map_node;
else if ((derivedField = nameNode->as<DerivedFieldNode>()))
{
if (!aliasName)
aliasName = derivedField->name.c_str();
nameNode = derivedField->value;
}
}
const dsql_fld* nameField = NULL;
const FieldNode* fieldNameNode = nameNode->as<FieldNode>();
if (fieldNameNode)
nameField = fieldNameNode->dsqlField;
const TEXT* fieldStr = NULL;
if (aliasName)
fieldStr = aliasName;
else if (nameField)
fieldStr = nameField->fld_name.c_str();
// Check if this is a field or an expression.
DsqlAliasNode* aliasNode = fieldNode->as<DsqlAliasNode>();
if (aliasNode)
fieldNode = aliasNode->value;
dsql_fld* field = NULL;
const dsql_ctx* context = NULL;
fieldNameNode = fieldNode->as<FieldNode>();
if (fieldNameNode)
{
field = fieldNameNode->dsqlField;
context = fieldNameNode->dsqlContext;
}
else
updatable = false;
// If this is an expression, check to make sure there is a name specified.
if (!ptr && !fieldStr)
{
// must specify field name for view select expression
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_specify_field_err));
}
// CVC: Small modification here to catch any mismatch between number of
// explicit field names in a view and number of fields in the select expression,
// see comment below. This closes Firebird Bug #223059.
if (ptr)
{
if (ptr < end)
fieldStr = (*ptr)->as<FieldNode>()->dsqlName.c_str();
else
{
// Generate an error when going out of this loop.
++ptr;
break;
}
++ptr;
}
// If not an expression, point to the proper base relation field,
// else make up an SQL field with generated global field for calculations.
dsql_fld* relField = NULL;
if (modifyingView) // if we're modifying a view
{
for (relField = modifyingView->rel_fields; relField; relField = relField->fld_next)
{
if (relField->fld_name == fieldStr)
{
if (modifiedFields.exist(relField))
{
// column @1 appears more than once in ALTER VIEW
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_dsql_col_more_than_once_view) << Arg::Str(fieldStr));
}
modifiedFields.add(relField);
break;
}
}
}
FieldDefinition fieldDefinition(*tdbb->getDefaultPool());
fieldDefinition.relationName = name;
fieldDefinition.name = fieldStr;
fieldDefinition.position = position;
// CVC: Not sure if something should be done now that isc_dyn_view_context is used here,
// but if alter view is going to work, maybe we need here the context type and package, too.
if (field)
{
field->resolve(dsqlScratch);
fieldDefinition.viewContext = context->ctx_context;
fieldDefinition.baseField = field->fld_name;
if (field->dtype <= dtype_any_text)
fieldDefinition.collationId = field->collationId;
if (relField) // modifying a view
{
// We're now modifying a field and it will be based on another one. So if the old
// field was an expression, delete it now.
AutoRequest request2;
FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
RFL IN RDB$RELATION_FIELDS CROSS
FLD IN RDB$FIELDS
WITH RFL.RDB$FIELD_NAME EQ fieldStr AND
RFL.RDB$RELATION_NAME EQ name.c_str() AND
RFL.RDB$BASE_FIELD MISSING AND
FLD.RDB$FIELD_NAME EQ RFL.RDB$FIELD_SOURCE
{
bool wasInternalDomain = fb_utils::implicit_domain(FLD.RDB$FIELD_NAME);
fb_assert(wasInternalDomain);
if (wasInternalDomain)
ERASE FLD;
}
END_FOR
fieldDefinition.modify(tdbb, transaction);
}
else
fieldDefinition.store(tdbb, transaction);
}
else
{
dsqlScratch->getBlrData().clear();
dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5);
GEN_expr(dsqlScratch, fieldNode);
dsqlScratch->appendUChar(blr_eoc);
// Get the type of the expression.
dsc desc;
MAKE_desc(dsqlScratch, &desc, fieldNode);
dsql_fld newField(*tdbb->getDefaultPool());
newField.dtype = desc.dsc_dtype;
newField.length = desc.dsc_length;
newField.scale = desc.dsc_scale;
if (desc.isText() || (desc.isBlob() && desc.getBlobSubType() == isc_blob_text))
{
newField.charSetId = desc.getCharSet();
newField.collationId = desc.getCollation();
}
if (desc.isText())
{
newField.charLength = newField.length;
newField.length *= METD_get_charset_bpc(
dsqlScratch->getTransaction(), newField.charSetId);
}
else
newField.subType = desc.dsc_sub_type;
if (relField) // modifying a view
{
AutoRequest request2;
FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
RFL IN RDB$RELATION_FIELDS CROSS
FLD IN RDB$FIELDS
WITH RFL.RDB$FIELD_NAME EQ fieldStr AND
RFL.RDB$RELATION_NAME EQ name.c_str() AND
RFL.RDB$BASE_FIELD MISSING AND
FLD.RDB$FIELD_NAME EQ RFL.RDB$FIELD_SOURCE
{
bool wasInternalDomain = fb_utils::implicit_domain(FLD.RDB$FIELD_NAME);
fb_assert(wasInternalDomain);
if (wasInternalDomain)
{
fieldDefinition.fieldSource = FLD.RDB$FIELD_NAME;
MODIFY FLD
updateRdbFields(&newField,
FLD.RDB$FIELD_TYPE,
FLD.RDB$FIELD_LENGTH,
FLD.RDB$FIELD_SUB_TYPE.NULL, FLD.RDB$FIELD_SUB_TYPE,
FLD.RDB$FIELD_SCALE.NULL, FLD.RDB$FIELD_SCALE,
FLD.RDB$CHARACTER_SET_ID.NULL, FLD.RDB$CHARACTER_SET_ID,
FLD.RDB$CHARACTER_LENGTH.NULL, FLD.RDB$CHARACTER_LENGTH,
FLD.RDB$FIELD_PRECISION.NULL, FLD.RDB$FIELD_PRECISION,
FLD.RDB$COLLATION_ID.NULL, FLD.RDB$COLLATION_ID,
FLD.RDB$SEGMENT_LENGTH.NULL, FLD.RDB$SEGMENT_LENGTH);
FLD.RDB$COMPUTED_BLR.NULL = FALSE;
attachment->storeBinaryBlob(tdbb, transaction, &FLD.RDB$COMPUTED_BLR,
dsqlScratch->getBlrData());
END_MODIFY
}
}
END_FOR
if (fieldDefinition.fieldSource.isEmpty())
{
storeGlobalField(tdbb, transaction, fieldDefinition.fieldSource, &newField,
"", dsqlScratch->getBlrData());
}
fieldDefinition.modify(tdbb, transaction);
}
else
{
storeGlobalField(tdbb, transaction, fieldDefinition.fieldSource, &newField,
"", dsqlScratch->getBlrData());
fieldDefinition.store(tdbb, transaction);
}
}
if (fieldStr)
saveField(tdbb, dsqlScratch, fieldStr);
}
// CVC: This message was not catching the case when
// #fields < items in select list, see comment above.
if (ptr != end)
{
// number of fields does not match select list
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_num_field_err));
}
if (modifyingView) // modifying a view
{
// Delete the old fields not present in the new definition.
for (dsql_fld* relField = modifyingView->rel_fields; relField; relField = relField->fld_next)
{
if (!modifiedFields.exist(relField))
deleteLocalField(tdbb, transaction, name, relField->fld_name);
}
}
// Setup to define triggers for WITH CHECK OPTION.
if (withCheckOption)
{
if (!updatable)
{
// Only simple column names permitted for VIEW WITH CHECK OPTION
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_col_name_err));
}
RseNode* querySpec = selectExpr->querySpec->as<RseNode>();
fb_assert(querySpec);
if (querySpec->dsqlFrom->items.getCount() != 1 ||
!querySpec->dsqlFrom->items[0]->is<ProcedureSourceNode>())
{
// Only one table allowed for VIEW WITH CHECK OPTION
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_table_view_err));
}
if (!querySpec->dsqlWhere)
{
// No where clause for VIEW WITH CHECK OPTION
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_where_err));
}
if (querySpec->dsqlDistinct || querySpec->dsqlGroup || querySpec->dsqlHaving)
{
// DISTINCT, GROUP or HAVING not permitted for VIEW WITH CHECK OPTION
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_distinct_err));
}
createCheckTrigger(tdbb, dsqlScratch, items);
}
dsqlScratch->resetContextStack();
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, ddlTriggerAction, name);
savePoint.release(); // everything is ok
// Update DSQL cache
METD_drop_relation(transaction, name);
MET_dsql_cache_release(tdbb, SYM_relation, name);
}
// Generate a trigger to implement the WITH CHECK OPTION clause for a VIEW.
void CreateAlterViewNode::createCheckTrigger(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
ValueListNode* items)
{
MemoryPool& pool = *tdbb->getDefaultPool();
// Specify that the trigger should abort if the condition is not met.
ExceptionNode* exceptionNode = FB_NEW(pool) ExceptionNode(pool, CHECK_CONSTRAINT_EXCEPTION);
exceptionNode->exception->type = ExceptionItem::GDS_CODE;
// Create the trigger - PRE_MODIFY_TRIGGER | PRE_STORE_TRIGGER. See parse.y/trigger_type_suffix.
TriggerType triggerType = TriggerType(((1 << 1) | (2 << 3)) - 1);
AutoSetRestore<bool> autoCheckConstraintTrigger(&dsqlScratch->checkConstraintTrigger, true);
RelationSourceNode* relationNode = dsqlNode;
// Generate the trigger blr.
dsqlScratch->getBlrData().clear();
dsqlScratch->getDebugData().clear();
dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5);
dsqlScratch->appendUChar(blr_begin);
dsqlScratch->resetContextStack();
++dsqlScratch->contextNumber; // OLD context
RseNode* querySpec = selectExpr->querySpec->as<RseNode>();
fb_assert(querySpec);
// ASF: We'll now map the view's base table into the trigger's NEW context.
dsql_ctx* newContext;
{ /// scope
ProcedureSourceNode* sourceNode = querySpec->dsqlFrom->items[0]->as<ProcedureSourceNode>();
AutoSetRestore<string> autoAlias(&relationNode->alias, sourceNode->alias);
if (relationNode->alias.isEmpty())
relationNode->alias = sourceNode->dsqlName.identifier.c_str();
newContext = PASS1_make_context(dsqlScratch, relationNode);
newContext->ctx_flags |= CTX_system | CTX_view_with_check;
}
// Replace the view's field names by the base table field names. Save the original names
// to restore after the condition processing.
dsql_fld* field = newContext->ctx_relation->rel_fields;
ObjectsArray<MetaName> savedNames;
// ASF: rel_fields entries are in reverse order.
for (NestConst<ValueExprNode>* ptr = items->items.end();
ptr-- != items->items.begin();
field = field->fld_next)
{
ValueExprNode* valueNode = *ptr;
DsqlAliasNode* aliasNode;
if ((aliasNode = valueNode->as<DsqlAliasNode>()))
valueNode = aliasNode->value;
FieldNode* fieldNode = valueNode->as<FieldNode>();
fb_assert(fieldNode);
savedNames.add(field->fld_name);
dsql_fld* queryField = fieldNode->dsqlField;
field->fld_name = queryField->fld_name;
field->dtype = queryField->dtype;
field->scale = queryField->scale;
field->subType = queryField->subType;
field->length = queryField->length;
field->flags = queryField->flags;
field->charSetId = queryField->charSetId;
field->collationId = queryField->collationId;
}
dsqlScratch->appendUChar(blr_if);
// Process the condition for firing the trigger.
NestConst<BoolExprNode> condition = doDsqlPass(dsqlScratch, querySpec->dsqlWhere);
// Restore the field names. This must happen before the condition's BLR generation.
field = newContext->ctx_relation->rel_fields;
for (ObjectsArray<MetaName>::iterator i = savedNames.begin(); i != savedNames.end(); ++i)
{
field->fld_name = *i;
field = field->fld_next;
}
GEN_expr(dsqlScratch, condition);
dsqlScratch->appendUChar(blr_begin);
dsqlScratch->appendUChar(blr_end);
// Generate the action statement for the trigger.
exceptionNode->dsqlPass(dsqlScratch)->genBlr(dsqlScratch);
dsqlScratch->appendUChar(blr_end); // of begin
dsqlScratch->appendUChar(blr_eoc);
dsqlScratch->resetContextStack();
TriggerDefinition trigger(pool);
trigger.systemFlag = fb_sysflag_view_check;
trigger.relationName = name;
trigger.type = triggerType;
trigger.blrData = dsqlScratch->getBlrData();
trigger.store(tdbb, dsqlScratch, dsqlScratch->getTransaction());
}
//----------------------
// Store an index.
void CreateIndexNode::store(thread_db* tdbb, jrd_tra* transaction, MetaName& name,
const Definition& definition, MetaName* referredIndexName)
{
if (name.isEmpty())
DYN_UTIL_generate_index_name(tdbb, transaction, name, definition.type);
DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_index);
AutoCacheRequest request(tdbb, drq_s_indices, DYN_REQUESTS);
ULONG keyLength = 0;
STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
IDX IN RDB$INDICES
{
IDX.RDB$UNIQUE_FLAG.NULL = TRUE;
IDX.RDB$INDEX_INACTIVE.NULL = TRUE;
IDX.RDB$INDEX_TYPE.NULL = TRUE;
IDX.RDB$FOREIGN_KEY.NULL = TRUE;
IDX.RDB$EXPRESSION_SOURCE.NULL = TRUE;
IDX.RDB$EXPRESSION_BLR.NULL = TRUE;
strcpy(IDX.RDB$INDEX_NAME, name.c_str());
strcpy(IDX.RDB$RELATION_NAME, definition.relation.c_str());
IDX.RDB$RELATION_NAME.NULL = FALSE;
IDX.RDB$SYSTEM_FLAG = 0;
IDX.RDB$SYSTEM_FLAG.NULL = FALSE;
// Check if the table is actually a view.
AutoCacheRequest request2(tdbb, drq_l_view_idx, DYN_REQUESTS);
FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
VREL IN RDB$RELATIONS
WITH VREL.RDB$RELATION_NAME EQ IDX.RDB$RELATION_NAME
{
if (!VREL.RDB$VIEW_BLR.NULL)
{
// msg 181: "attempt to index a view"
status_exception::raise(Arg::PrivateDyn(181));
}
}
END_FOR
if (definition.unique.specified)
{
IDX.RDB$UNIQUE_FLAG.NULL = FALSE;
IDX.RDB$UNIQUE_FLAG = SSHORT(definition.unique.value);
}
if (definition.inactive.specified)
{
IDX.RDB$INDEX_INACTIVE.NULL = FALSE;
IDX.RDB$INDEX_INACTIVE = SSHORT(definition.inactive.value);
}
if (definition.descending.specified)
{
IDX.RDB$INDEX_TYPE.NULL = FALSE;
IDX.RDB$INDEX_TYPE = SSHORT(definition.descending.value);
}
request2.reset(tdbb, drq_l_lfield, DYN_REQUESTS);
for (size_t i = 0; i < definition.columns.getCount(); ++i)
{
for (size_t j = 0; j < i; ++j)
{
if (definition.columns[i] == definition.columns[j])
{
// msg 240 "Field %s cannot be used twice in index %s"
status_exception::raise(
Arg::PrivateDyn(240) << definition.columns[i] << IDX.RDB$INDEX_NAME);
}
}
bool found = false;
FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
F IN RDB$RELATION_FIELDS CROSS
GF IN RDB$FIELDS
WITH GF.RDB$FIELD_NAME EQ F.RDB$FIELD_SOURCE AND
F.RDB$FIELD_NAME EQ definition.columns[i].c_str() AND
IDX.RDB$RELATION_NAME EQ F.RDB$RELATION_NAME
{
ULONG length = 0;
if (GF.RDB$FIELD_TYPE == blr_blob)
{
// msg 116 "attempt to index blob field in index %s"
status_exception::raise(Arg::PrivateDyn(116) << IDX.RDB$INDEX_NAME);
}
else if (!GF.RDB$DIMENSIONS.NULL)
{
// msg 117 "attempt to index array field in index %s"
status_exception::raise(Arg::PrivateDyn(117) << IDX.RDB$INDEX_NAME);
}
else if (!GF.RDB$COMPUTED_BLR.NULL)
{
// msg 179 "attempt to index COMPUTED BY field in index %s"
status_exception::raise(Arg::PrivateDyn(179) << IDX.RDB$INDEX_NAME);
}
else if (GF.RDB$FIELD_TYPE == blr_varying || GF.RDB$FIELD_TYPE == blr_text)
{
// Compute the length of the key segment allowing for international
// information. Note that we we must convert a <character set, collation>
// type to an index type in order to compute the length.
if (!F.RDB$COLLATION_ID.NULL)
{
length = INTL_key_length(tdbb,
INTL_TEXT_TO_INDEX(INTL_CS_COLL_TO_TTYPE(
GF.RDB$CHARACTER_SET_ID, F.RDB$COLLATION_ID)),
GF.RDB$FIELD_LENGTH);
}
else if (!GF.RDB$COLLATION_ID.NULL)
{
length = INTL_key_length(tdbb,
INTL_TEXT_TO_INDEX(INTL_CS_COLL_TO_TTYPE(
GF.RDB$CHARACTER_SET_ID, GF.RDB$COLLATION_ID)),
GF.RDB$FIELD_LENGTH);
}
else
length = GF.RDB$FIELD_LENGTH;
}
else
length = sizeof(double);
if (keyLength)
{
keyLength += ((length + Ods::STUFF_COUNT - 1) / (unsigned) Ods::STUFF_COUNT) *
(Ods::STUFF_COUNT + 1);
}
else
keyLength = length;
found = true;
}
END_FOR
if (!found)
{
// msg 120 "Unknown columns in index %s"
status_exception::raise(Arg::PrivateDyn(120) << IDX.RDB$INDEX_NAME);
}
}
if (!definition.expressionBlr.isEmpty())
{
IDX.RDB$EXPRESSION_BLR.NULL = FALSE;
IDX.RDB$EXPRESSION_BLR = definition.expressionBlr;
}
if (!definition.expressionSource.isEmpty())
{
IDX.RDB$EXPRESSION_SOURCE.NULL = FALSE;
IDX.RDB$EXPRESSION_SOURCE = definition.expressionSource;
}
keyLength = ROUNDUP(keyLength, sizeof(SLONG));
if (keyLength >= MAX_KEY)
{
// msg 118 "key size too big for index %s"
status_exception::raise(Arg::PrivateDyn(118) << IDX.RDB$INDEX_NAME);
}
if (definition.columns.hasData())
{
request2.reset(tdbb, drq_s_idx_segs, DYN_REQUESTS);
SSHORT position = 0;
for (ObjectsArray<MetaName>::const_iterator segment(definition.columns.begin());
segment != definition.columns.end();
++segment)
{
STORE(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
X IN RDB$INDEX_SEGMENTS
{
strcpy(X.RDB$INDEX_NAME, IDX.RDB$INDEX_NAME);
strcpy(X.RDB$FIELD_NAME, segment->c_str());
X.RDB$FIELD_POSITION = position++;
}
END_STORE
}
}
else if (IDX.RDB$EXPRESSION_BLR.NULL)
{
// msg 119 "no keys for index %s"
status_exception::raise(Arg::PrivateDyn(119) << IDX.RDB$INDEX_NAME);
}
if (definition.refColumns.hasData())
{
// If referring columns count <> referred columns return error.
if (definition.columns.getCount() != definition.refColumns.getCount())
{
// msg 133: "Number of referencing columns do not equal number of
// referenced columns
status_exception::raise(Arg::PrivateDyn(133));
}
// Lookup a unique index in the referenced relation with the
// referenced fields mentioned.
request2.reset(tdbb, drq_l_unq_idx, DYN_REQUESTS);
MetaName indexName;
int listIndex = -1;
bool found = false;
FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
RC IN RDB$RELATION_CONSTRAINTS CROSS
IND IN RDB$INDICES OVER RDB$INDEX_NAME CROSS
ISEG IN RDB$INDEX_SEGMENTS OVER RDB$INDEX_NAME
WITH IND.RDB$RELATION_NAME EQ definition.refRelation.c_str() AND
IND.RDB$UNIQUE_FLAG NOT MISSING AND
(RC.RDB$CONSTRAINT_TYPE = PRIMARY_KEY OR
RC.RDB$CONSTRAINT_TYPE = UNIQUE_CNSTRT)
SORTED BY IND.RDB$INDEX_NAME,
DESCENDING ISEG.RDB$FIELD_POSITION
{
if (indexName != IND.RDB$INDEX_NAME)
{
if (listIndex >= 0)
found = false;
if (found)
break;
listIndex = definition.refColumns.getCount() - 1;
indexName = IND.RDB$INDEX_NAME;
found = true;
}
// If there are no more fields or the field name doesn't
// match, then this is not the correct index.
if (listIndex >= 0)
{
fb_utils::exact_name_limit(ISEG.RDB$FIELD_NAME, sizeof(ISEG.RDB$FIELD_NAME));
if (definition.refColumns[listIndex--] != ISEG.RDB$FIELD_NAME)
found = false;
}
else
found = false;
}
END_FOR
if (listIndex >= 0)
found = false;
if (found)
{
IDX.RDB$FOREIGN_KEY.NULL = FALSE;
strcpy(IDX.RDB$FOREIGN_KEY, indexName.c_str());
if (referredIndexName)
*referredIndexName = indexName;
}
else
{
AutoRequest request3;
bool isView = false;
FOR(REQUEST_HANDLE request3 TRANSACTION_HANDLE transaction)
X IN RDB$RELATIONS
WITH X.RDB$RELATION_NAME EQ definition.refRelation.c_str()
{
found = true;
isView = !X.RDB$VIEW_BLR.NULL;
}
END_FOR
if (isView)
{
// msg 242: "attempt to reference a view (%s) in a foreign key"
status_exception::raise(Arg::PrivateDyn(242) << definition.refRelation);
}
if (found)
{
// msg 18: "could not find UNIQUE or PRIMARY KEY constraint in table %s with
// specified columns"
status_exception::raise(Arg::PrivateDyn(18) << definition.refRelation);
}
else
{
// msg 241: "Table %s not found"
status_exception::raise(Arg::PrivateDyn(241) << definition.refRelation);
}
}
}
else if (definition.refRelation.hasData())
{
request2.reset(tdbb, drq_l_primary, DYN_REQUESTS);
FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
IND IN RDB$INDICES CROSS
RC IN RDB$RELATION_CONSTRAINTS OVER RDB$INDEX_NAME
WITH IND.RDB$RELATION_NAME EQ definition.refRelation.c_str() AND
RC.RDB$CONSTRAINT_TYPE EQ PRIMARY_KEY
{
// Number of columns in referred index should be same as number
// of columns in referring index.
fb_assert(IND.RDB$SEGMENT_COUNT >= 0);
if (definition.columns.getCount() != ULONG(IND.RDB$SEGMENT_COUNT))
{
// msg 133: "Number of referencing columns do not equal number of
// referenced columns"
status_exception::raise(Arg::PrivateDyn(133));
}
fb_utils::exact_name_limit(IND.RDB$INDEX_NAME, sizeof(IND.RDB$INDEX_NAME));
IDX.RDB$FOREIGN_KEY.NULL = FALSE;
strcpy(IDX.RDB$FOREIGN_KEY, IND.RDB$INDEX_NAME);
if (referredIndexName)
*referredIndexName = IND.RDB$INDEX_NAME;
}
END_FOR
if (IDX.RDB$FOREIGN_KEY.NULL)
{
// msg 20: "could not find PRIMARY KEY index in specified table %s"
status_exception::raise(Arg::PrivateDyn(20) << definition.refRelation);
}
}
IDX.RDB$SEGMENT_COUNT = SSHORT(definition.columns.getCount());
}
END_STORE
}
void CreateIndexNode::print(string& text) const
{
text.printf(
"CreateIndexNode\n"
" name: '%s'\n",
name.c_str());
}
// Define an index.
void CreateIndexNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
Attachment* const attachment = transaction->tra_attachment;
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_INDEX, name);
CreateIndexNode::Definition definition;
definition.type = isc_dyn_def_idx;
definition.relation = relation->dsqlName;
definition.unique = unique;
definition.descending = descending;
if (columns)
{
const NestConst<ValueExprNode>* ptr = columns->items.begin();
const NestConst<ValueExprNode>* const end = columns->items.end();
for (; ptr != end; ++ptr)
{
MetaName& column = definition.columns.add();
column = (*ptr)->as<FieldNode>()->dsqlName;
}
}
else if (computed)
{
string computedSource;
BlrDebugWriter::BlrData computedValue;
defineComputed(dsqlScratch, relation, NULL, computed, computedSource, computedValue);
attachment->storeMetaDataBlob(tdbb, transaction, &definition.expressionSource,
computedSource);
attachment->storeBinaryBlob(tdbb, transaction, &definition.expressionBlr, computedValue);
}
store(tdbb, transaction, name, definition);
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_INDEX, name);
savePoint.release(); // everything is ok
}
//----------------------
void AlterIndexNode::print(string& text) const
{
text.printf(
"AlterIndexNode\n"
" name: '%s'\n"
" active: '%d'\n",
name.c_str(), active);
}
void AlterIndexNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
AutoCacheRequest request(tdbb, drq_m_index, DYN_REQUESTS);
bool found = false;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
IDX IN RDB$INDICES
WITH IDX.RDB$INDEX_NAME EQ name.c_str()
{
found = true;
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_INDEX, name);
MODIFY IDX
IDX.RDB$INDEX_INACTIVE.NULL = FALSE;
IDX.RDB$INDEX_INACTIVE = active ? FALSE : TRUE;
END_MODIFY
}
END_FOR
if (found)
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_INDEX, name);
else
{
// msg 48: "Index not found"
status_exception::raise(Arg::PrivateDyn(48));
}
savePoint.release(); // everything is ok
}
//----------------------
void SetStatisticsNode::print(string& text) const
{
text.printf(
"SetStatisticsNode\n"
" name: '%s'\n",
name.c_str());
}
void SetStatisticsNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
AutoCacheRequest request(tdbb, drq_m_set_statistics, DYN_REQUESTS);
bool found = false;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
IDX IN RDB$INDICES
WITH IDX.RDB$INDEX_NAME EQ name.c_str()
{
found = true;
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_INDEX, name);
MODIFY IDX
// For V4 index selectivity can be set only to -1.
IDX.RDB$STATISTICS.NULL = FALSE;
IDX.RDB$STATISTICS = -1.0;
END_MODIFY
}
END_FOR
if (found)
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_INDEX, name);
else
{
// msg 48: "Index not found"
status_exception::raise(Arg::PrivateDyn(48));
}
savePoint.release(); // everything is ok
}
//----------------------
// Delete the records in RDB$INDEX_SEGMENTS pertaining to an index.
bool DropIndexNode::deleteSegmentRecords(thread_db* tdbb, jrd_tra* transaction,
const MetaName& name)
{
AutoCacheRequest request(tdbb, drq_e_idx_segs, DYN_REQUESTS);
bool found = false;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
IDXSEG IN RDB$INDEX_SEGMENTS WITH IDXSEG.RDB$INDEX_NAME EQ name.c_str()
{
found = true;
ERASE IDXSEG;
}
END_FOR
return found;
}
void DropIndexNode::print(string& text) const
{
text.printf(
"DropIndexNode\n"
" name: '%s'\n",
name.c_str());
}
void DropIndexNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
AutoCacheRequest request(tdbb, drq_e_indices, DYN_REQUESTS);
bool found = false;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
IDX IN RDB$INDICES
WITH IDX.RDB$INDEX_NAME EQ name.c_str()
{
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
DDL_TRIGGER_DROP_INDEX, name);
ERASE IDX;
if (IDX.RDB$EXPRESSION_BLR.NULL && !deleteSegmentRecords(tdbb, transaction, name))
{
// msg 50: "No segments found for index"
status_exception::raise(Arg::PrivateDyn(50));
}
found = true;
}
END_FOR
if (found)
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_INDEX, name);
else
{
// msg 48: "Index not found"
status_exception::raise(Arg::PrivateDyn(48));
}
savePoint.release(); // everything is ok
}
//----------------------
void CreateFilterNode::print(string& text) const
{
text.printf(
"CreateFilterNode\n"
" name: '%s'\n",
name.c_str());
}
// Define a blob filter.
void CreateFilterNode::execute(thread_db* tdbb, DsqlCompilerScratch* /*dsqlScratch*/, jrd_tra* transaction)
{
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
///executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_DECLARE_FILTER, name);
AutoCacheRequest request(tdbb, drq_s_filters, DYN_REQUESTS);
STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
X IN RDB$FILTERS
{
strcpy(X.RDB$FUNCTION_NAME, name.c_str());
X.RDB$SYSTEM_FLAG = 0;
moduleName.copyTo(X.RDB$MODULE_NAME, sizeof(X.RDB$MODULE_NAME));
entryPoint.copyTo(X.RDB$ENTRYPOINT, sizeof(X.RDB$ENTRYPOINT));
if (inputFilter->name.hasData())
{
if (!METD_get_type(transaction, inputFilter->name, "RDB$FIELD_SUB_TYPE",
&X.RDB$INPUT_SUB_TYPE))
{
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
Arg::Gds(isc_dsql_datatype_err) <<
Arg::Gds(isc_dsql_blob_type_unknown) << inputFilter->name);
}
}
else
X.RDB$INPUT_SUB_TYPE = inputFilter->number;
if (outputFilter->name.hasData())
{
if (!METD_get_type(transaction, outputFilter->name, "RDB$FIELD_SUB_TYPE",
&X.RDB$OUTPUT_SUB_TYPE))
{
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
Arg::Gds(isc_dsql_datatype_err) <<
Arg::Gds(isc_dsql_blob_type_unknown) << outputFilter->name);
}
}
else
X.RDB$OUTPUT_SUB_TYPE = outputFilter->number;
}
END_STORE
///executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DECLARE_FILTER, name);
savePoint.release(); // everything is ok
}
//----------------------
void DropFilterNode::print(string& text) const
{
text.printf(
"DropFilterNode\n"
" name: '%s'\n",
name.c_str());
}
void DropFilterNode::execute(thread_db* tdbb, DsqlCompilerScratch* /*dsqlScratch*/, jrd_tra* transaction)
{
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
AutoCacheRequest request(tdbb, drq_e_filters, DYN_REQUESTS);
bool found = false;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
X IN RDB$FILTERS
WITH X.RDB$FUNCTION_NAME EQ name.c_str()
{
ERASE X;
found = true;
}
END_FOR
if (!found)
{
// msg 37: "Blob Filter %s not found"
status_exception::raise(Arg::PrivateDyn(37) << name);
}
savePoint.release(); // everything is ok
}
//----------------------
void CreateShadowNode::print(string& text) const
{
text.printf(
"CreateShadowNode\n"
" number: '%d'\n",
number);
}
void CreateShadowNode::execute(thread_db* tdbb, DsqlCompilerScratch* /*dsqlScratch*/, jrd_tra* transaction)
{
if (!tdbb->getAttachment()->locksmith())
status_exception::raise(Arg::Gds(isc_adm_task_denied));
// Should be caught by the parser.
if (number == 0)
{
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_dsql_shadow_number_err));
}
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
// If a shadow set identified by the shadow number already exists return error.
AutoCacheRequest request(tdbb, drq_l_shadow, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
FIRST 1 X IN RDB$FILES
WITH X.RDB$SHADOW_NUMBER EQ number
{
// msg 165: "Shadow %ld already exists"
status_exception::raise(Arg::PrivateDyn(165) << Arg::Num(number));
}
END_FOR
SLONG start = 0;
for (NestConst<DbFileClause>* i = files.begin(); i != files.end(); ++i)
{
bool first = i == files.begin();
DbFileClause* file = *i;
if (!first && i[-1]->length == 0 && file->start == 0)
{
// Preceding file did not specify length, so %s must include starting page number
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_dsql_file_length_err) << file->name);
}
defineFile(tdbb, transaction, number, manual && first, conditional && first,
start, file->name.c_str(), file->start, file->length);
}
savePoint.release(); // everything is ok
}
//----------------------
void DropShadowNode::print(string& text) const
{
text.printf(
"DropShadowNode\n"
" number: '%d'\n",
number);
}
void DropShadowNode::execute(thread_db* tdbb, DsqlCompilerScratch* /*dsqlScratch*/, jrd_tra* transaction)
{
if (!tdbb->getAttachment()->locksmith())
status_exception::raise(Arg::Gds(isc_adm_task_denied));
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
AutoCacheRequest request(tdbb, drq_e_shadow, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
FIL IN RDB$FILES
WITH FIL.RDB$SHADOW_NUMBER EQ number
{
ERASE FIL;
}
END_FOR
// ASF: No error is raised if the shadow is not found.
savePoint.release(); // everything is ok
}
//----------------------
void CreateRoleNode::print(string& text) const
{
text.printf(
"CreateRoleNode\n"
" name: '%s'\n",
name.c_str());
}
void CreateRoleNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
MetaName ownerName(tdbb->getAttachment()->att_user->usr_user_name);
ownerName.upper7();
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
DDL_TRIGGER_CREATE_ROLE, name);
if (name == ownerName)
{
// msg 193: "user name @1 could not be used for SQL role"
status_exception::raise(Arg::PrivateDyn(193) << ownerName);
}
if (name == NULL_ROLE)
{
// msg 195: "keyword @1 could not be used as SQL role name"
status_exception::raise(Arg::PrivateDyn(195) << name);
}
if (isItUserName(tdbb, transaction))
{
// msg 193: "user name @1 could not be used for SQL role"
status_exception::raise(Arg::PrivateDyn(193) << name);
}
MetaName dummyName;
if (isItSqlRole(tdbb, transaction, name, dummyName))
{
// msg 194: "SQL role @1 already exists"
status_exception::raise(Arg::PrivateDyn(194) << name);
}
AutoCacheRequest request(tdbb, drq_role_gens, DYN_REQUESTS);
STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
X IN RDB$ROLES
{
strcpy(X.RDB$ROLE_NAME, name.c_str());
strcpy(X.RDB$OWNER_NAME, ownerName.c_str());
X.RDB$SYSTEM_FLAG = 0;
}
END_STORE
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER,
DDL_TRIGGER_CREATE_ROLE, name);
savePoint.release(); // everything is ok
}
// If role name is user name returns true. Otherwise returns false.
bool CreateRoleNode::isItUserName(thread_db* tdbb, jrd_tra* transaction)
{
bool found = false;
// If there is a user with privilege or a grantor on a relation we
// can infer there is a user with this name
AutoCacheRequest request(tdbb, drq_get_user_priv, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
PRIV IN RDB$USER_PRIVILEGES
WITH (PRIV.RDB$USER EQ name.c_str() AND PRIV.RDB$USER_TYPE = obj_user) OR
(PRIV.RDB$GRANTOR EQ name.c_str() AND PRIV.RDB$OBJECT_TYPE = obj_relation)
{
found = true;
}
END_FOR
if (found)
return found;
// We can infer that 'role name' is a user name if it owns any relations
// Note we can only get here if a user creates a table and revokes all
// his privileges on the table
request.reset(tdbb, drq_get_rel_owner, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
REL IN RDB$RELATIONS
WITH REL.RDB$OWNER_NAME EQ name.c_str()
{
found = true;
}
END_FOR
return found;
}
//----------------------
void AlterRoleNode::print(string& text) const
{
text.printf(
"AlterRoleNode\n"
" name: '%s'\n"
" map: '%d'\n",
name.c_str(), map);
}
// It's purpose is to add/drop mapping from OS security name to DB security object.
void AlterRoleNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
// This is FB 2.5 limited implementation!
// Later it should work with new system table, something like RDB$MAPPING.
if (name != ADMIN_ROLE)
status_exception::raise(Arg::Gds(isc_wish_list));
if (!(tdbb->getAttachment() && tdbb->getAttachment()->locksmith()))
status_exception::raise(Arg::Gds(isc_adm_task_denied));
AutoCacheRequest request(tdbb, drq_m_map, DYN_REQUESTS);
bool found = false;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
ROL IN RDB$ROLES
WITH ROL.RDB$ROLE_NAME EQ name.c_str()
{
found = true;
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_ROLE, name);
MODIFY ROL
ROL.RDB$SYSTEM_FLAG = ROLE_FLAG_DBO | (map ? ROLE_FLAG_MAY_TRUST : 0);
END_MODIFY
}
END_FOR
if (found)
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_ROLE, name);
else
{
status_exception::raise(
Arg::Gds(isc_random) << Arg::Str("Missing RDB$ADMIN role in the database"));
}
savePoint.release(); // everything is ok
}
//----------------------
void DropRoleNode::print(string& text) const
{
text.printf(
"DropRoleNode\n"
" name: '%s'\n",
name.c_str());
}
void DropRoleNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
MetaName user(tdbb->getAttachment()->att_user->usr_user_name);
user.upper7();
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
AutoCacheRequest request(tdbb, drq_drop_role, DYN_REQUESTS);
bool found = false;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
ROL IN RDB$ROLES
WITH ROL.RDB$ROLE_NAME EQ name.c_str()
{
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
DDL_TRIGGER_DROP_ROLE, name);
const MetaName roleOwner(ROL.RDB$OWNER_NAME);
if (!ROL.RDB$SYSTEM_FLAG.NULL && ROL.RDB$SYSTEM_FLAG != 0)
{
// msg 284: can not drop system SQL role @1
status_exception::raise(Arg::PrivateDyn(284) << name);
}
if (tdbb->getAttachment()->locksmith() || roleOwner == user)
{
AutoCacheRequest request2(tdbb, drq_del_role_1, DYN_REQUESTS);
// The first OR clause finds all members of the role.
// The 2nd OR clause finds all privileges granted to the role
FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
PRIV IN RDB$USER_PRIVILEGES
WITH (PRIV.RDB$RELATION_NAME EQ name.c_str() AND PRIV.RDB$OBJECT_TYPE = obj_sql_role) OR
(PRIV.RDB$USER EQ name.c_str() AND PRIV.RDB$USER_TYPE = obj_sql_role)
{
ERASE PRIV;
}
END_FOR
ERASE ROL;
}
else
{
// msg 191: "only owner of SQL role or USR_locksmith could drop SQL role"
status_exception::raise(Arg::PrivateDyn(191));
}
found = true;
}
END_FOR
if (found)
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_ROLE, name);
else
{
// msg 155: "Role %s not found"
status_exception::raise(Arg::PrivateDyn(155) << name);
}
savePoint.release(); // everything is ok
}
//----------------------
void CreateAlterUserNode::print(string& text) const
{
text.printf(
"CreateAlterUserNode\n"
" name: '%s'\n",
name.c_str());
}
void CreateAlterUserNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
if (!isCreating && !password && !firstName && !middleName && !lastName && !adminRole.specified)
{
// 283: ALTER USER requires at least one clause to be specified
status_exception::raise(Arg::PrivateDyn(283));
}
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
DynamicUserData* userData = FB_NEW(*transaction->tra_pool) DynamicUserData;
string text = name.c_str();
text.upper();
userData->op = isCreating ? ADD_OPER : MOD_OPER;
userData->user.set(text.c_str());
userData->user.setEntered(1);
if (password)
{
if (password->isEmpty())
{
// 250: Password should not be empty string
status_exception::raise(Arg::PrivateDyn(250));
}
userData->pass.set(password->c_str());
userData->pass.setEntered(1);
}
if (firstName)
{
if (firstName->hasData())
{
userData->first.set(firstName->c_str());
userData->first.setEntered(1);
}
else
{
userData->first.setEntered(0);
userData->first.setSpecified(1);
}
}
if (middleName)
{
if (middleName->hasData())
{
userData->middle.set(middleName->c_str());
userData->middle.setEntered(1);
}
else
{
userData->middle.setEntered(0);
userData->middle.setSpecified(1);
}
}
if (lastName)
{
if (lastName->hasData())
{
userData->last.set(lastName->c_str());
userData->last.setEntered(1);
}
else
{
userData->last.setEntered(0);
userData->last.setSpecified(1);
}
}
if (adminRole.specified)
{
userData->adm.set(adminRole.value);
userData->adm.setEntered(1);
}
const int ddlAction = isCreating ? DDL_TRIGGER_CREATE_USER : DDL_TRIGGER_ALTER_USER;
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, ddlAction, userData->user.get());
const USHORT id = transaction->getUserManagement()->put(userData);
DFW_post_work(transaction, dfw_user_management, NULL, id);
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, ddlAction, userData->user.get());
savePoint.release(); // everything is ok
}
//----------------------
void DropUserNode::print(string& text) const
{
text.printf(
"DropUserNode\n"
" name: '%s'\n",
name.c_str());
}
void DropUserNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
DynamicUserData* userData = FB_NEW(*transaction->tra_pool) DynamicUserData;
string text = name.c_str();
text.upper();
userData->op = DEL_OPER;
userData->user.set(text.c_str());
userData->user.setEntered(1);
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_DROP_USER,
userData->user.get());
const USHORT id = transaction->getUserManagement()->put(userData);
DFW_post_work(transaction, dfw_user_management, NULL, id);
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_USER,
userData->user.get());
savePoint.release(); // everything is ok
}
//----------------------
void GrantRevokeNode::print(string& text) const
{
text.printf(
"GrantRevokeNode\n"
" isGrant: '%d'\n",
isGrant);
}
void GrantRevokeNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
const GranteeClause* usersPtr;
const GranteeClause* usersEnd;
if (!isGrant && roles.isEmpty() && privileges.isEmpty() && !object) // REVOKE ALL ON ALL
{
usersEnd = users.end();
for (usersPtr = users.begin(); usersPtr != usersEnd; ++usersPtr)
grantRevoke(tdbb, transaction, NULL, usersPtr, NULL, NULL, 0);
}
else
{
SSHORT option = 0; // no grant/admin option
if (roles.isEmpty())
{
if (grantAdminOption)
option = 1; // with grant option
usersEnd = users.end();
for (usersPtr = users.begin(); usersPtr != usersEnd; ++usersPtr)
modifyPrivileges(tdbb, transaction, option, usersPtr);
}
else
{
if (grantAdminOption)
option = 2; // with admin option
const GranteeClause* rolesEnd = roles.end();
for (const GranteeClause* rolesPtr = roles.begin(); rolesPtr != rolesEnd; ++rolesPtr)
{
usersEnd = users.end();
for (usersPtr = users.begin(); usersPtr != usersEnd; ++usersPtr)
grantRevoke(tdbb, transaction, rolesPtr, usersPtr, "M", NULL, option);
}
}
}
savePoint.release(); // everything is ok
}
void GrantRevokeNode::modifyPrivileges(thread_db* tdbb, jrd_tra* transaction, SSHORT option,
const GranteeClause* user)
{
string privs;
for (PrivilegeClause* i = privileges.begin(); i != privileges.end(); ++i)
{
if (i->first == 'A')
grantRevoke(tdbb, transaction, object, user, "A", NULL, option);
else if (i->second)
{
char privs0[2] = {i->first, '\0'};
ValueListNode* fields = i->second;
for (NestConst<ValueExprNode>* ptr = fields->items.begin(); ptr != fields->items.end(); ++ptr)
{
grantRevoke(tdbb, transaction, object, user, privs0,
(*ptr)->as<FieldNode>()->dsqlName, option);
}
}
else
privs += i->first;
}
if (privs.hasData())
grantRevoke(tdbb, transaction, object, user, privs.c_str(), NULL, option);
}
// Execute SQL grant/revoke operation.
void GrantRevokeNode::grantRevoke(thread_db* tdbb, jrd_tra* transaction, const GranteeClause* object,
const GranteeClause* userNod, const char* privs,
const MetaName& field, int options)
{
SSHORT userType = userNod->first;
MetaName user(userNod->second);
MetaName dummyName;
switch (userType)
{
case obj_user_or_role:
// This test may become obsolete as we now allow explicit ROLE keyword.
if (isItSqlRole(tdbb, transaction, user, dummyName))
{
userType = obj_sql_role;
if (user == NULL_ROLE)
{
// msg 195: keyword NONE could not be used as SQL role name.
status_exception::raise(Arg::PrivateDyn(195) << user.c_str());
}
}
else
{
userType = obj_user;
user.upper7();
}
break;
case obj_user:
user.upper7();
break;
case obj_sql_role:
if (!isItSqlRole(tdbb, transaction, user, dummyName))
{
// msg 188: Role doesn't exist.
status_exception::raise(Arg::PrivateDyn(188) << user.c_str());
}
if (user == NULL_ROLE)
{
// msg 195: keyword NONE could not be used as SQL role name.
status_exception::raise(Arg::PrivateDyn(195) << user.c_str());
}
break;
}
MetaName grantorRevoker(grantor ?
*grantor : tdbb->getAttachment()->att_user->usr_user_name);
if (grantor && !tdbb->getAttachment()->locksmith())
status_exception::raise(Arg::PrivateDyn(252) << SYSDBA_USER_NAME);
grantorRevoker.upper7();
if (!isGrant && !privs) // REVOKE ALL ON ALL
{
AutoCacheRequest request(tdbb, drq_e_grant3, DYN_REQUESTS);
bool grantErased = false;
bool badGrantor = false;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
PRIV IN RDB$USER_PRIVILEGES
WITH PRIV.RDB$USER = user.c_str() AND
PRIV.RDB$USER_TYPE = userType
{
if (tdbb->getAttachment()->att_user->locksmith() || grantorRevoker == PRIV.RDB$GRANTOR)
{
ERASE PRIV;
grantErased = true;
}
else
badGrantor = true;
}
END_FOR
const char* all = "ALL";
if (badGrantor && !grantErased)
{
// msg 246: @1 is not grantor of @2 on @3 to @4.
status_exception::raise(Arg::PrivateDyn(246) <<
grantorRevoker.c_str() << all << all << user.c_str());
}
if (!grantErased)
{
// msg 247: Warning: @1 on @2 is not granted to @3.
ERR_post_warning(
Arg::Warning(isc_dyn_miss_priv_warning) <<
all << all << Arg::Str(user));
}
return;
}
const SSHORT objType = object->first;
const MetaName objName(object->second);
char privileges[16];
strcpy(privileges, privs);
if (strcmp(privileges, "A") == 0)
strcpy(privileges, ALL_PRIVILEGES);
if (objType == obj_sql_role && objName == NULL_ROLE)
{
if (isGrant)
{
// msg 195: keyword NONE could not be used as SQL role name.
status_exception::raise(Arg::PrivateDyn(195) << objName.c_str());
}
else
{
///CVC: Make this a warning in the future.
///DYN_error_punt(false, 195, objName.c_str());
}
}
char priv[2];
priv[1] = '\0';
if (isGrant)
{
AutoCacheRequest request(tdbb, drq_l_grant1, DYN_REQUESTS);
for (const char* pr = privileges; *pr; ++pr)
{
bool duplicate = false;
priv[0] = *pr;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
PRIV IN RDB$USER_PRIVILEGES
WITH PRIV.RDB$RELATION_NAME EQ objName.c_str() AND
PRIV.RDB$OBJECT_TYPE = objType AND
PRIV.RDB$PRIVILEGE EQ priv AND
PRIV.RDB$USER = user.c_str() AND
PRIV.RDB$USER_TYPE = userType AND
PRIV.RDB$GRANTOR EQ grantorRevoker.c_str() AND
PRIV.RDB$FIELD_NAME EQUIV NULLIF(field.c_str(), '')
{
if (PRIV.RDB$GRANT_OPTION.NULL ||
PRIV.RDB$GRANT_OPTION ||
PRIV.RDB$GRANT_OPTION == options)
{
duplicate = true;
}
else
ERASE PRIV; // has to be 0 and options == 1
}
END_FOR
if (duplicate)
continue;
if (objType == obj_sql_role)
{
checkGrantorCanGrantRole(tdbb, transaction, grantorRevoker, objName);
if (userType == obj_sql_role)
{
// Temporary restriction. This should be removed once GRANT role1 TO rolex is
// supported and this message could be reused for blocking cycles of role grants.
status_exception::raise(Arg::PrivateDyn(192) << user.c_str());
}
}
else
{
// In the case where the object is a view, then the grantor must have
// some kind of grant privileges on the base table(s)/view(s). If the
// grantor is the owner of the view, then we have to explicitely check
// this because the owner of a view by default has grant privileges on
// his own view. If the grantor is not the owner of the view, then the
// base table/view grant privilege checks were made when the grantor
// got its grant privilege on the view and no further checks are
// necessary.
// As long as only locksmith can use GRANTED BY, no need specially checking
// for privileges of current user. AP-2008
if (objType == 0)
{
// Relation or view because we cannot distinguish at this point.
checkGrantorCanGrant(tdbb, transaction,
tdbb->getAttachment()->att_user->usr_user_name.c_str(), priv, objName,
field, true);
}
}
storePrivilege(tdbb, transaction, objName, user, field, pr, userType, objType,
options, grantorRevoker);
}
}
else // REVOKE
{
AutoCacheRequest request(tdbb, (field.hasData() ? drq_e_grant1 : drq_e_grant2), DYN_REQUESTS);
for (const char* pr = privileges; (priv[0] = *pr); ++pr)
{
bool grantErased = false;
bool badGrantor = false;
if (field.hasData())
{
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
PRIV IN RDB$USER_PRIVILEGES
WITH PRIV.RDB$RELATION_NAME EQ objName.c_str() AND
PRIV.RDB$OBJECT_TYPE = objType AND
PRIV.RDB$PRIVILEGE EQ priv AND
PRIV.RDB$USER = user.c_str() AND
PRIV.RDB$USER_TYPE = userType AND
PRIV.RDB$FIELD_NAME EQ field.c_str()
{
if (grantorRevoker == PRIV.RDB$GRANTOR)
{
ERASE PRIV;
grantErased = true;
}
else
badGrantor = true;
}
END_FOR
}
else
{
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
PRIV IN RDB$USER_PRIVILEGES
WITH PRIV.RDB$PRIVILEGE EQ priv AND
PRIV.RDB$RELATION_NAME EQ objName.c_str() AND
PRIV.RDB$OBJECT_TYPE = objType AND
PRIV.RDB$USER EQ user.c_str() AND
PRIV.RDB$USER_TYPE = userType
{
// Revoking a permission at the table level implies revoking the perm. on all
// columns. So for all fields in this table which have been granted the
// privilege, we erase the entries from RDB$USER_PRIVILEGES.
if (grantorRevoker == PRIV.RDB$GRANTOR)
{
ERASE PRIV;
grantErased = true;
}
else
badGrantor = true;
}
END_FOR
}
if (options && grantErased)
{
// Add the privilege without the grant option. There is a modify trigger on the
// rdb$user_privileges which disallows the table from being updated. It would have
// to be changed such that only the grant_option field can be updated.
storePrivilege(tdbb, transaction, objName, user, field, pr, userType, objType,
0, grantorRevoker);
}
if (badGrantor && !grantErased)
{
// msg 246: @1 is not grantor of @2 on @3 to @4.
status_exception::raise(Arg::PrivateDyn(246) <<
grantorRevoker.c_str() << privilegeName(priv[0]) << objName.c_str() <<
user.c_str());
}
if (!grantErased)
{
// msg 247: Warning: @1 on @2 is not granted to @3.
ERR_post_warning(
Arg::Warning(isc_dyn_miss_priv_warning) <<
Arg::Str(privilegeName(priv[0])) << Arg::Str(objName) << Arg::Str(user));
}
}
}
}
// Check if the grantor has grant privilege on the relation/field.
void GrantRevokeNode::checkGrantorCanGrant(thread_db* tdbb, jrd_tra* transaction,
const char* grantor, const char* privilege, const MetaName& relationName,
const MetaName& fieldName, bool topLevel)
{
// Verify that the input relation exists.
AutoCacheRequest request(tdbb, drq_gcg4, DYN_REQUESTS);
bool sqlRelation = false;
bool relationExists = false;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
REL IN RDB$RELATIONS WITH
REL.RDB$RELATION_NAME = relationName.c_str()
{
relationExists = true;
if (!REL.RDB$FLAGS.NULL && (REL.RDB$FLAGS & REL_sql))
sqlRelation = true;
}
END_FOR
if (!relationExists)
{
// table/view .. does not exist
status_exception::raise(Arg::PrivateDyn(175) << relationName.c_str());
}
// Verify the the input field exists.
if (fieldName.hasData())
{
bool fieldExists = false;
request.reset(tdbb, drq_gcg5, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
G_FLD IN RDB$RELATION_FIELDS WITH
G_FLD.RDB$RELATION_NAME = relationName.c_str() AND
G_FLD.RDB$FIELD_NAME = fieldName.c_str()
{
fieldExists = true;
}
END_FOR
if (!fieldExists)
{
// column .. does not exist in table/view ..
status_exception::raise(Arg::PrivateDyn(176) <<
fieldName.c_str() << relationName.c_str());
}
}
// If the current user is locksmith - allow all grants to occur
if (tdbb->getAttachment()->locksmith())
return;
// If this is a non-sql table, then the owner will probably not have any
// entries in the rdb$user_privileges table. Give the owner of a GDML
// table all privileges.
bool grantorIsOwner = false;
request.reset(tdbb, drq_gcg2, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
REL IN RDB$RELATIONS WITH
REL.RDB$RELATION_NAME = relationName.c_str() AND
REL.RDB$OWNER_NAME = UPPERCASE(grantor)
{
grantorIsOwner = true;
}
END_FOR
if (!sqlRelation && grantorIsOwner)
return;
// Remember the grant option for non field-specific user-privileges, and
// the grant option for the user-privileges for the input field.
// -1 = no privilege found (yet)
// 0 = privilege without grant option found
// 1 = privilege with grant option found
SSHORT goRel = -1;
SSHORT goFld = -1;
// Verify that the grantor has the grant option for this relation/field
// in the rdb$user_privileges. If not, then we don't need to look further.
request.reset(tdbb, drq_gcg1, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
PRV IN RDB$USER_PRIVILEGES WITH
PRV.RDB$USER = UPPERCASE(grantor) AND
PRV.RDB$USER_TYPE = obj_user AND
PRV.RDB$RELATION_NAME = relationName.c_str() AND
PRV.RDB$OBJECT_TYPE = obj_relation AND
PRV.RDB$PRIVILEGE = privilege
{
if (PRV.RDB$FIELD_NAME.NULL)
{
if (PRV.RDB$GRANT_OPTION.NULL || !PRV.RDB$GRANT_OPTION)
goRel = 0;
else if (goRel)
goRel = 1;
}
else
{
if (PRV.RDB$GRANT_OPTION.NULL || !PRV.RDB$GRANT_OPTION)
{
if (fieldName.hasData() && fieldName == PRV.RDB$FIELD_NAME)
goFld = 0;
}
else
{
if (fieldName.hasData() && fieldName == PRV.RDB$FIELD_NAME)
goFld = 1;
}
}
}
END_FOR
if (fieldName.hasData())
{
if (goFld == 0)
{
// no grant option for privilege .. on column .. of [base] table/view ..
status_exception::raise(Arg::PrivateDyn(topLevel ? 167 : 168) <<
privilege << fieldName.c_str() << relationName.c_str());
}
if (goFld == -1)
{
if (goRel == 0)
{
// no grant option for privilege .. on [base] table/view .. (for column ..)
status_exception::raise(Arg::PrivateDyn(topLevel ? 169 : 170) <<
privilege << relationName.c_str() << fieldName.c_str());
}
if (goRel == -1)
{
// no .. privilege with grant option on [base] table/view .. (for column ..)
status_exception::raise(Arg::PrivateDyn(topLevel ? 171 : 172) <<
privilege << relationName.c_str() << fieldName.c_str());
}
}
}
else
{
if (goRel == 0)
{
// no grant option for privilege .. on table/view ..
status_exception::raise(Arg::PrivateDyn(173) << privilege << relationName.c_str());
}
if (goRel == -1)
{
// no .. privilege with grant option on table/view ..
status_exception::raise(Arg::PrivateDyn(174) << privilege << relationName.c_str());
}
}
// If the grantor is not the owner of the relation, then we don't need to
// check the base table(s)/view(s) because that check was performed when
// the grantor was given its privileges.
if (!grantorIsOwner)
return;
// Find all the base fields/relations and check for the correct grant privileges on them.
request.reset(tdbb, drq_gcg3, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
G_FLD IN RDB$RELATION_FIELDS CROSS
G_VIEW IN RDB$VIEW_RELATIONS WITH
G_FLD.RDB$RELATION_NAME = relationName.c_str() AND
G_FLD.RDB$BASE_FIELD NOT MISSING AND
G_VIEW.RDB$VIEW_NAME EQ G_FLD.RDB$RELATION_NAME AND
G_VIEW.RDB$VIEW_CONTEXT EQ G_FLD.RDB$VIEW_CONTEXT
{
if (fieldName.hasData())
{
if (fieldName == G_FLD.RDB$FIELD_NAME)
{
checkGrantorCanGrant(tdbb, transaction, grantor, privilege,
G_VIEW.RDB$RELATION_NAME, G_FLD.RDB$BASE_FIELD, false);
}
}
else
{
checkGrantorCanGrant(tdbb, transaction, grantor, privilege,
G_VIEW.RDB$RELATION_NAME, G_FLD.RDB$BASE_FIELD, false);
}
}
END_FOR
}
// Check if the grantor has admin privilege on the role.
void GrantRevokeNode::checkGrantorCanGrantRole(thread_db* tdbb, jrd_tra* transaction,
const MetaName& grantor, const MetaName& roleName)
{
// Fetch the name of the owner of the ROLE.
MetaName owner;
if (isItSqlRole(tdbb, transaction, roleName, owner))
{
// Both SYSDBA and the owner of this ROLE can grant membership
if (tdbb->getAttachment()->locksmith() || owner == grantor)
return;
}
else
{
// 188: role name not exist.
status_exception::raise(Arg::PrivateDyn(188) << roleName.c_str());
}
AutoCacheRequest request(tdbb, drq_get_role_au, DYN_REQUESTS);
bool grantable = false;
bool noAdmin = false;
// The 'grantor' is not the owner of the ROLE, see if they have admin privilege on the role.
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
PRV IN RDB$USER_PRIVILEGES WITH
PRV.RDB$USER = UPPERCASE(grantor.c_str()) AND
PRV.RDB$USER_TYPE = obj_user AND
PRV.RDB$RELATION_NAME EQ roleName.c_str() AND
PRV.RDB$OBJECT_TYPE = obj_sql_role AND
PRV.RDB$PRIVILEGE EQ "M"
{
if (PRV.RDB$GRANT_OPTION == 2)
grantable = true;
else
noAdmin = true;
}
END_FOR
if (!grantable)
{
// 189: user have no admin option.
// 190: user is not a member of the role.
status_exception::raise(Arg::PrivateDyn(noAdmin ? 189 : 190) <<
grantor.c_str() << roleName.c_str());
}
}
void GrantRevokeNode::storePrivilege(thread_db* tdbb, jrd_tra* transaction, const MetaName& object,
const MetaName& user, const MetaName& field, const TEXT* privilege, SSHORT userType,
SSHORT objType, int option, const MetaName& grantor)
{
AutoCacheRequest request(tdbb, drq_s_grant, DYN_REQUESTS);
STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
PRIV IN RDB$USER_PRIVILEGES
PRIV.RDB$FIELD_NAME.NULL = TRUE;
strcpy(PRIV.RDB$RELATION_NAME, object.c_str());
strcpy(PRIV.RDB$USER, user.c_str());
strcpy(PRIV.RDB$GRANTOR, grantor.c_str());
PRIV.RDB$USER_TYPE = userType;
PRIV.RDB$OBJECT_TYPE = objType;
{
if (field.hasData())
{
strcpy(PRIV.RDB$FIELD_NAME, field.c_str());
PRIV.RDB$FIELD_NAME.NULL = FALSE;
setFieldClassName(tdbb, transaction, object, field);
}
PRIV.RDB$PRIVILEGE[0] = privilege[0];
PRIV.RDB$PRIVILEGE[1] = 0;
PRIV.RDB$GRANT_OPTION = option;
}
END_STORE
}
// For field level grants, be sure the field has a unique class name.
void GrantRevokeNode::setFieldClassName(thread_db* tdbb, jrd_tra* transaction,
const MetaName& relation, const MetaName& field)
{
AutoCacheRequest request(tdbb, drq_s_f_class, DYN_REQUESTS);
bool unique = false;
FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
RFR IN RDB$RELATION_FIELDS
WITH RFR.RDB$FIELD_NAME = field.c_str() AND
RFR.RDB$RELATION_NAME = relation.c_str() AND
RFR.RDB$SECURITY_CLASS MISSING
{
MODIFY RFR
while (!unique)
{
sprintf(RFR.RDB$SECURITY_CLASS, "%s%" SQUADFORMAT, SQL_FLD_SECCLASS_PREFIX,
DPM_gen_id(tdbb, MET_lookup_generator(tdbb, "RDB$SECURITY_CLASS"), false, 1));
unique = true;
AutoCacheRequest request2(tdbb, drq_s_u_class, DYN_REQUESTS);
FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
RFR1 IN RDB$RELATION_FIELDS
WITH RFR1.RDB$SECURITY_CLASS = RFR.RDB$SECURITY_CLASS
{
unique = false;
}
END_FOR
}
RFR.RDB$SECURITY_CLASS.NULL = FALSE;
END_MODIFY
}
END_FOR
}
//----------------------
void AlterDatabaseNode::print(string& text) const
{
text.printf(
"AlterDatabaseNode\n");
}
void AlterDatabaseNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
if (!tdbb->getAttachment()->locksmith())
status_exception::raise(Arg::Gds(isc_adm_task_denied));
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
if (cryptPlugin.hasData())
DFW_post_work(transaction, dfw_db_crypt, cryptPlugin.c_str(), 0);
if (clauses & CLAUSE_DECRYPT)
DFW_post_work(transaction, dfw_db_crypt, "", 0);
Attachment* const attachment = transaction->tra_attachment;
SLONG dbAlloc = PageSpace::maxAlloc(tdbb->getDatabase());
SLONG start = create ? createLength + 1 : 0;
AutoCacheRequest request(tdbb, drq_m_database, DYN_REQUESTS);
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
DBB IN RDB$DATABASE
{
MODIFY DBB USING
if (clauses & CLAUSE_DROP_DIFFERENCE)
changeBackupMode(tdbb, transaction, CLAUSE_DROP_DIFFERENCE);
for (NestConst<DbFileClause>* i = files.begin(); i != files.end(); ++i)
{
DbFileClause* file = *i;
start = MAX(start, file->start);
defineFile(tdbb, transaction, 0, false, false, dbAlloc,
file->name.c_str(), start, file->length);
start += file->length;
}
if (differenceFile.hasData())
defineDifference(tdbb, transaction, differenceFile.c_str());
if (setDefaultCharSet.hasData())
{
//// TODO: Validate!
DBB.RDB$CHARACTER_SET_NAME.NULL = FALSE;
strcpy(DBB.RDB$CHARACTER_SET_NAME, setDefaultCharSet.c_str());
}
if (!DBB.RDB$CHARACTER_SET_NAME.NULL && setDefaultCollation.hasData())
{
AlterCharSetNode alterCharSetNode(getPool(), setDefaultCharSet, setDefaultCollation);
alterCharSetNode.execute(tdbb, dsqlScratch, transaction);
}
if (clauses & CLAUSE_BEGIN_BACKUP)
changeBackupMode(tdbb, transaction, CLAUSE_BEGIN_BACKUP);
if (clauses & CLAUSE_END_BACKUP)
changeBackupMode(tdbb, transaction, CLAUSE_END_BACKUP);
END_MODIFY
}
END_FOR
savePoint.release(); // everything is ok
}
// Drop backup difference file for the database, begin or end backup.
void AlterDatabaseNode::changeBackupMode(thread_db* tdbb, jrd_tra* transaction, unsigned clause)
{
AutoCacheRequest request(tdbb, drq_d_difference, DYN_REQUESTS);
bool invalidState = false;
bool found = false;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
X IN RDB$FILES
{
if (X.RDB$FILE_FLAGS & FILE_difference)
{
found = true;
switch (clause)
{
case CLAUSE_DROP_DIFFERENCE:
ERASE X;
break;
case CLAUSE_BEGIN_BACKUP:
if (X.RDB$FILE_FLAGS & FILE_backing_up)
invalidState = true;
else
{
MODIFY X USING
X.RDB$FILE_FLAGS |= FILE_backing_up;
END_MODIFY
}
break;
case CLAUSE_END_BACKUP:
if (X.RDB$FILE_FLAGS & FILE_backing_up)
{
if (X.RDB$FILE_NAME.NULL)
ERASE X;
else
{
MODIFY X USING
X.RDB$FILE_FLAGS &= ~FILE_backing_up;
END_MODIFY
}
}
else
invalidState = true;
break;
}
}
}
END_FOR
if (!found && clause == CLAUSE_BEGIN_BACKUP)
{
request.reset(tdbb, drq_s2_difference, DYN_REQUESTS);
STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
X IN RDB$FILES
{
X.RDB$FILE_FLAGS = FILE_difference | FILE_backing_up;
X.RDB$FILE_START = 0;
}
END_STORE
found = true;
}
if (invalidState)
{
// msg 217: "Database is already in the physical backup mode"
// msg 218: "Database is not in the physical backup mode"
status_exception::raise(Arg::PrivateDyn(clause == CLAUSE_BEGIN_BACKUP ? 217 : 218));
}
if (!found)
{
// msg 218: "Database is not in the physical backup mode"
// msg 215: "Difference file is not defined"
status_exception::raise(Arg::PrivateDyn(clause == CLAUSE_END_BACKUP ? 218 : 215));
}
}
// Define backup difference file.
void AlterDatabaseNode::defineDifference(thread_db* tdbb, jrd_tra* transaction, const PathName& file)
{
AutoCacheRequest request(tdbb, drq_l_difference, DYN_REQUESTS);
bool found = false;
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
FIL IN RDB$FILES
{
if (FIL.RDB$FILE_FLAGS & FILE_difference)
found = true;
}
END_FOR
if (found)
{
// msg 216: "Difference file is already defined"
status_exception::raise(Arg::PrivateDyn(216));
}
request.reset(tdbb, drq_s_difference, DYN_REQUESTS);
STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
FIL IN RDB$FILES
{
if (file.length() >= sizeof(FIL.RDB$FILE_NAME))
status_exception::raise(Arg::Gds(isc_dyn_name_longer));
strcpy(FIL.RDB$FILE_NAME, file.c_str());
FIL.RDB$FILE_FLAGS = FILE_difference;
FIL.RDB$FILE_START = 0;
}
END_STORE
}
} // namespace Jrd