mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-31 15:23:03 +01:00
fc12495d0e
1. Never allocate empty statement - always use att->prepare() to create statement interface 2. Separated IStatement into 2 parts - statement itself and resultset. 3. Added stmt->openCursor() (and att->openCursor() for unprepared statements) to create IResultSet. 4. Always use IMessageMetadata (former IParametersMetadata) to pass message fromat info from client, therefore avoiding need in BLR generation in client applications.
10056 lines
279 KiB
Plaintext
10056 lines
279 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->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());
|
|
}
|
|
}
|
|
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$FUNCTION_SOURCE.NULL = TRUE;
|
|
|
|
FUN.RDB$VALID_BLR.NULL = FALSE;
|
|
FUN.RDB$VALID_BLR = TRUE;
|
|
|
|
FUN.RDB$FUNCTION_SOURCE.NULL = !(source.hasData() && (external || package.isEmpty()));
|
|
if (!FUN.RDB$FUNCTION_SOURCE.NULL)
|
|
attachment->storeMetaDataBlob(tdbb, transaction, &FUN.RDB$FUNCTION_SOURCE, source);
|
|
|
|
FUN.RDB$DETERMINISTIC_FLAG.NULL = FALSE;
|
|
FUN.RDB$DETERMINISTIC_FLAG = deterministic ? TRUE : FALSE;
|
|
|
|
if (isUdf())
|
|
{
|
|
FUN.RDB$LEGACY_FLAG.NULL = FALSE;
|
|
FUN.RDB$LEGACY_FLAG = TRUE;
|
|
|
|
FUN.RDB$RETURN_ARGUMENT.NULL = FALSE;
|
|
|
|
dsql_fld* field = returnType->type;
|
|
|
|
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;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FUN.RDB$LEGACY_FLAG.NULL = TRUE;
|
|
FUN.RDB$RETURN_ARGUMENT.NULL = TRUE;
|
|
}
|
|
}
|
|
|
|
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$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->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 = 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 = 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$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;
|
|
}
|
|
|
|
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$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);
|
|
}
|
|
|
|
TRG.RDB$VALID_BLR = TRUE;
|
|
|
|
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);
|
|
|
|
// 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());
|
|
|
|
fb_assert(message.length() < sizeof(X.RDB$MESSAGE));
|
|
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
|
|
fb_assert(message.length() < sizeof(X.RDB$MESSAGE));
|
|
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 CreateSequenceNode::print(string& text) const
|
|
{
|
|
text.printf(
|
|
"CreateSequenceNode\n"
|
|
" name: %s\n",
|
|
name.c_str());
|
|
}
|
|
|
|
void CreateSequenceNode::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_CREATE_SEQUENCE, name);
|
|
store(tdbb, transaction, name, fb_sysflag_user);
|
|
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_SEQUENCE, name);
|
|
|
|
savePoint.release(); // everything is ok
|
|
}
|
|
|
|
void CreateSequenceNode::store(thread_db* tdbb, jrd_tra* transaction, const MetaName& name,
|
|
fb_sysflag sysFlag)
|
|
{
|
|
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;
|
|
|
|
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());
|
|
}
|
|
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_generator, USAGE_PRIVILEGES);
|
|
}
|
|
|
|
|
|
//----------------------
|
|
|
|
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, ¬NullFlag);
|
|
}
|
|
|
|
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);
|
|
|
|
CreateSequenceNode::store(tdbb, transaction, fieldDefinition.identitySequence,
|
|
fb_sysflag_identity_generator);
|
|
}
|
|
|
|
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;
|
|
|
|
MetaName indexName(constraint.index->name);
|
|
CreateIndexNode::store(tdbb, transaction, indexName, definition, &referredIndexName);
|
|
|
|
CRT.RDB$INDEX_NAME.NULL = FALSE;
|
|
strcpy(CRT.RDB$INDEX_NAME, indexName.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_sql_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_read))
|
|
{
|
|
// 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() && !table) // 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, table, 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, table, user, privs0,
|
|
(*ptr)->as<FieldNode>()->dsqlName, option);
|
|
}
|
|
}
|
|
else
|
|
privs += i->first;
|
|
}
|
|
|
|
if (privs.hasData())
|
|
grantRevoke(tdbb, transaction, table, user, privs.c_str(), NULL, option);
|
|
}
|
|
|
|
// Execute SQL grant/revoke operation.
|
|
void GrantRevokeNode::grantRevoke(thread_db* tdbb, jrd_tra* transaction, const GranteeClause* table,
|
|
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;
|
|
}
|
|
|
|
SSHORT objType = table->first;
|
|
|
|
char privileges[16];
|
|
strcpy(privileges, privs);
|
|
if (strcmp(privileges, "A") == 0)
|
|
strcpy(privileges, ALL_PRIVILEGES);
|
|
|
|
MetaName object(table->second);
|
|
|
|
if (objType == obj_sql_role && object == NULL_ROLE)
|
|
{
|
|
if (isGrant)
|
|
{
|
|
// msg 195: keyword NONE could not be used as SQL role name.
|
|
status_exception::raise(Arg::PrivateDyn(195) << object.c_str());
|
|
}
|
|
else
|
|
{
|
|
///CVC: Make this a warning in the future.
|
|
///DYN_error_punt(false, 195, object.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 object.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, object);
|
|
|
|
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, object,
|
|
field, true);
|
|
}
|
|
}
|
|
|
|
storePrivilege(tdbb, transaction, object, 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 object.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 object.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, object, 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]) << object.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(object) << 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())
|
|
{
|
|
string sql;
|
|
sql.printf("alter character set \"%s\" set default collation \"%s\"",
|
|
PreparedStatement::escapeName(DBB.RDB$CHARACTER_SET_NAME).c_str(),
|
|
PreparedStatement::escapeName(setDefaultCollation).c_str());
|
|
|
|
AutoPtr<PreparedStatement> ps(attachment->prepareStatement(tdbb, transaction, sql));
|
|
ps->execute(tdbb, 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
|