mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-31 06:43:02 +01:00
8477 lines
242 KiB
Plaintext
8477 lines
242 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 "../common/common.h"
|
|
#include "../dsql/DdlNodes.h"
|
|
#include "../dsql/BoolNodes.h"
|
|
#include "../dsql/ExprNodes.h"
|
|
#include "../dsql/node.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/tra.h"
|
|
#include "../common/os/path_utils.h"
|
|
#include "../jrd/IntlManager.h"
|
|
#include "../jrd/PreparedStatement.h"
|
|
#include "../jrd/blb_proto.h"
|
|
#include "../jrd/cmp_proto.h"
|
|
#include "../common/dsc_proto.h"
|
|
#include "../jrd/dyn_dl_proto.h"
|
|
#include "../jrd/dyn_ut_proto.h"
|
|
#include "../jrd/exe_proto.h"
|
|
#include "../jrd/intl_proto.h"
|
|
#include "../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 "../common/StatusArg.h"
|
|
|
|
namespace Jrd {
|
|
|
|
using namespace Firebird;
|
|
using namespace Dsql;
|
|
|
|
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 deleteKeyConstraint(thread_db* tdbb, jrd_tra* transaction,
|
|
const MetaName& relationName, const MetaName& constraintName, const MetaName& indexName);
|
|
static bool fieldExists(thread_db* tdbb, jrd_tra* transaction, const MetaName& relationName,
|
|
const MetaName& fieldName);
|
|
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);
|
|
|
|
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::Gds(ENCODE_ISC_MSG(232, DYN_MSG_FAC)) << 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::Gds(ENCODE_ISC_MSG(232, DYN_MSG_FAC)) << 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::Gds(ENCODE_ISC_MSG(206, DYN_MSG_FAC)) << 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::Gds(ENCODE_ISC_MSG(206, DYN_MSG_FAC)) << 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->fld_ranges = NULL;
|
|
relation->rel_fields->fld_character_set = NULL;
|
|
relation->rel_fields->fld_sub_type_name = NULL;
|
|
relation->rel_fields->fld_relation = relation;
|
|
}
|
|
}
|
|
|
|
// 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::Gds(ENCODE_ISC_MSG(130, DYN_MSG_FAC)) << constraintName);
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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, NULL, 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.type == dtype_blob)
|
|
{
|
|
fieldSubTypeNull = FALSE;
|
|
fieldSubType = type.subType;
|
|
|
|
fieldScaleNull = FALSE;
|
|
fieldScale = 0;
|
|
|
|
if (type.subType == isc_blob_text)
|
|
{
|
|
characterSetIdNull = FALSE;
|
|
characterSetId = type.charSetId;
|
|
|
|
collationIdNull = !type.collateSpecified;
|
|
collationId = type.collationId;
|
|
}
|
|
|
|
if (type.segLength != 0)
|
|
{
|
|
segmentLengthNull = FALSE;
|
|
segmentLength = type.segLength;
|
|
}
|
|
}
|
|
else if (type.type <= 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 = !type.collateSpecified;
|
|
collationId = type.collationId;
|
|
}
|
|
else
|
|
{
|
|
fieldScaleNull = FALSE;
|
|
fieldScale = type.scale;
|
|
|
|
if (DTYPE_IS_EXACT(type.type))
|
|
{
|
|
fieldPrecisionNull = FALSE;
|
|
fieldPrecision = type.precision;
|
|
|
|
fieldSubTypeNull = FALSE;
|
|
fieldSubType = type.subType;
|
|
}
|
|
}
|
|
|
|
if (type.type == dtype_varying)
|
|
{
|
|
fb_assert(type.length <= MAX_SSHORT);
|
|
fieldLength = (SSHORT) (type.length - sizeof(USHORT));
|
|
}
|
|
else
|
|
fieldLength = type.length;
|
|
|
|
fieldType = blr_dtypes[type.type];
|
|
}
|
|
|
|
|
|
//----------------------
|
|
|
|
|
|
// 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::executeDdlTrigger(thread_db* tdbb, jrd_tra* transaction, DdlTriggerWhen when,
|
|
int action, const MetaName& objectName, const string& sqlText)
|
|
{
|
|
Attachment* attachment = transaction->tra_attachment;
|
|
|
|
// do nothing if user doesn't want database triggers
|
|
if (attachment->att_flags & ATT_no_db_triggers)
|
|
return;
|
|
|
|
fb_assert(action > 0); // first element is NULL
|
|
DdlTriggerContext context;
|
|
context.ddlEvent = DDL_TRIGGER_ACTION_NAMES[action];
|
|
context.objectName = objectName;
|
|
context.sqlText = sqlText;
|
|
|
|
Stack<DdlTriggerContext>::AutoPushPop autoContext(attachment->ddlTriggersContext, context);
|
|
AutoSavePoint savePoint(tdbb, transaction);
|
|
|
|
EXE_execute_ddl_triggers(tdbb, transaction, when == DTW_BEFORE, action);
|
|
|
|
savePoint.release(); // everything is ok
|
|
}
|
|
|
|
void DdlNode::executeDdlTrigger(thread_db* tdbb, 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 BlrWriter::BlrData& computedValue)
|
|
{
|
|
Attachment* attachment = transaction->tra_attachment;
|
|
|
|
const dsql_nod* elements = field.legacyField->fld_ranges;
|
|
const USHORT dims = elements ? elements->nod_count / 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, NULL, 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$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?
|
|
{
|
|
AutoCacheRequest request(tdbb, drq_s_fld_dym, DYN_REQUESTS);
|
|
|
|
SSHORT position = 0;
|
|
const dsql_nod* const* ptr = elements->nod_arg;
|
|
for (const dsql_nod* const* const end = ptr + elements->nod_count; ptr < end; ++ptr, ++position)
|
|
{
|
|
const dsql_nod* element = *ptr++;
|
|
const SLONG lrange = ExprNode::as<LiteralNode>(element)->getSlong();
|
|
element = *ptr;
|
|
const SLONG hrange = ExprNode::as<LiteralNode>(element)->getSlong();
|
|
|
|
if (lrange >= hrange)
|
|
{
|
|
status_exception::raise(
|
|
Arg::Gds(isc_sqlerr) << Arg::Num(-604) <<
|
|
Arg::Gds(isc_dsql_arr_range_error));
|
|
}
|
|
|
|
STORE (REQUEST_HANDLE request 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
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------
|
|
|
|
|
|
TypeClause::TypeClause(MemoryPool& pool, dsql_fld* aLegacyField, const MetaName& aCollate)
|
|
: fieldSource(pool),
|
|
typeOfTable(pool),
|
|
typeOfName(pool),
|
|
legacyField(aLegacyField),
|
|
collate(pool, aCollate)
|
|
{
|
|
}
|
|
|
|
void TypeClause::resolve(DsqlCompilerScratch* dsqlScratch, bool modifying)
|
|
{
|
|
DDL_resolve_intl_type2(dsqlScratch, legacyField,
|
|
(collate.isEmpty() ? NULL : MAKE_cstring(collate.c_str())), modifying);
|
|
|
|
setup(dsqlScratch);
|
|
}
|
|
|
|
void TypeClause::setup(DsqlCompilerScratch* /*dsqlScratch*/)
|
|
{
|
|
type = legacyField->fld_dtype;
|
|
length = legacyField->fld_length;
|
|
scale = legacyField->fld_scale;
|
|
subType = legacyField->fld_sub_type;
|
|
segLength = legacyField->fld_seg_length;
|
|
precision = legacyField->fld_precision;
|
|
charLength = legacyField->fld_character_length;
|
|
charSetId = legacyField->fld_character_set_id;
|
|
collationId = legacyField->fld_collation_id;
|
|
collateSpecified = collate.hasData();
|
|
textType = legacyField->fld_ttype;
|
|
fullDomain = legacyField->fld_full_domain;
|
|
notNull = legacyField->fld_not_nullable;
|
|
fieldSource = legacyField->fld_source;
|
|
|
|
if (legacyField->fld_type_of_table)
|
|
typeOfTable = legacyField->fld_type_of_table->str_data;
|
|
|
|
typeOfName = legacyField->fld_type_of_name;
|
|
}
|
|
|
|
void TypeClause::print(string& text) const
|
|
{
|
|
text.printf("typeOfTable: '%s' typeOfName: '%s' notNull: %d fieldSource: '%s'",
|
|
typeOfTable.c_str(), typeOfName.c_str(), notNull, fieldSource.c_str());
|
|
}
|
|
|
|
|
|
//----------------------
|
|
|
|
|
|
ParameterClause::ParameterClause(MemoryPool& pool, dsql_fld* field, const MetaName& aCollate,
|
|
dsql_nod* dflt, dsql_nod* aLegacyParameter)
|
|
: TypeClause(pool, field, aCollate),
|
|
name(field->fld_name),
|
|
legacyDefault(dflt),
|
|
legacyParameter(aLegacyParameter)
|
|
{
|
|
}
|
|
|
|
void ParameterClause::print(string& text) const
|
|
{
|
|
string s;
|
|
TypeClause::print(s);
|
|
text.printf("name: '%s' %s", name.c_str(), s.c_str());
|
|
}
|
|
|
|
|
|
//----------------------
|
|
|
|
|
|
void AlterCharSetNode::print(string& text, Array<dsql_nod*>& /*nodes*/) const
|
|
{
|
|
text.printf(
|
|
"AlterCharSetNode\n"
|
|
" charSet: %s\n"
|
|
" defaultCollation: %s\n",
|
|
charSet.c_str(), defaultCollation.c_str());
|
|
}
|
|
|
|
void AlterCharSetNode::execute(thread_db* tdbb, 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, Array<dsql_nod*>& /*nodes*/) const
|
|
{
|
|
text.printf(
|
|
"CommentOnNode\n"
|
|
" objType: %s\n"
|
|
" objName: %s\n"
|
|
" text: %s\n",
|
|
objType, objName.c_str(), text.c_str());
|
|
}
|
|
|
|
// select rdb$relation_name from rdb$relation_fields where rdb$field_name = 'RDB$DESCRIPTION';
|
|
// gives the list of objects that accept descriptions. At FB2 time, the only
|
|
// subobjects with descriptions are relation's fields and procedure's parameters.
|
|
void CommentOnNode::execute(thread_db* tdbb, DsqlCompilerScratch* /*dsqlScratch*/,
|
|
jrd_tra* transaction)
|
|
{
|
|
Attachment* attachment = transaction->tra_attachment;
|
|
|
|
const char* tableClause = NULL;
|
|
const char* columnClause = NULL;
|
|
const char* subColumnClause = NULL;
|
|
const char* addWhereClause = NULL;
|
|
Arg::StatusVector status;
|
|
|
|
switch (objType)
|
|
{
|
|
case ddl_database:
|
|
tableClause = "rdb$database";
|
|
break;
|
|
|
|
case ddl_domain:
|
|
tableClause = "rdb$fields";
|
|
columnClause = "rdb$field_name";
|
|
status << Arg::Gds(isc_dyn_domain_not_found);
|
|
break;
|
|
|
|
case ddl_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 ddl_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 ddl_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 ddl_trigger:
|
|
tableClause = "rdb$triggers";
|
|
columnClause = "rdb$trigger_name";
|
|
status << Arg::Gds(isc_dyn_trig_not_found) << Arg::Str(objName);
|
|
break;
|
|
|
|
case ddl_udf:
|
|
tableClause = "rdb$functions";
|
|
columnClause = "rdb$function_name";
|
|
addWhereClause = "rdb$package_name is null";
|
|
status << Arg::Gds(isc_dyn_func_not_found) << Arg::Str(objName);
|
|
break;
|
|
|
|
case ddl_blob_filter:
|
|
tableClause = "rdb$filters";
|
|
columnClause = "rdb$function_name";
|
|
status << Arg::Gds(isc_dyn_filter_not_found) << Arg::Str(objName);
|
|
break;
|
|
|
|
case ddl_exception:
|
|
tableClause = "rdb$exceptions";
|
|
columnClause = "rdb$exception_name";
|
|
status << Arg::Gds(isc_dyn_exception_not_found) << Arg::Str(objName);
|
|
break;
|
|
|
|
case ddl_generator:
|
|
tableClause = "rdb$generators";
|
|
columnClause = "rdb$generator_name";
|
|
status << Arg::Gds(isc_dyn_gen_not_found) << Arg::Str(objName);
|
|
break;
|
|
|
|
case ddl_index:
|
|
tableClause = "rdb$indices";
|
|
columnClause = "rdb$index_name";
|
|
status << Arg::Gds(isc_dyn_index_not_found) << Arg::Str(objName);
|
|
break;
|
|
|
|
case ddl_role:
|
|
tableClause = "rdb$roles";
|
|
columnClause = "rdb$role_name";
|
|
status << Arg::Gds(isc_dyn_role_not_found) << Arg::Str(objName);
|
|
break;
|
|
|
|
case ddl_charset:
|
|
tableClause = "rdb$character_sets";
|
|
columnClause = "rdb$character_set_name";
|
|
status << Arg::Gds(isc_dyn_charset_not_found) << Arg::Str(objName);
|
|
break;
|
|
|
|
case ddl_collation:
|
|
tableClause = "rdb$collations";
|
|
columnClause = "rdb$collation_name";
|
|
status << Arg::Gds(isc_dyn_collation_not_found) << Arg::Str(objName);
|
|
break;
|
|
|
|
case ddl_package:
|
|
tableClause = "rdb$packages";
|
|
columnClause = "rdb$package_name";
|
|
status << Arg::Gds(isc_dyn_package_not_found) << Arg::Str(objName);
|
|
break;
|
|
|
|
case ddl_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.isEmpty())
|
|
description = attachment->stringToMetaCharSet(tdbb, text, textCharSet);
|
|
|
|
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, Array<dsql_nod*>& /*nodes*/) const
|
|
{
|
|
text.printf(
|
|
"CreateAlterFunctionNode\n"
|
|
" name: '%s' create: %d alter: %d\n",
|
|
name.c_str(), create, alter);
|
|
|
|
if (external)
|
|
{
|
|
string s;
|
|
s.printf(" external -> name: '%s' engine: '%s'\n",
|
|
external->name.c_str(), external->engine.c_str());
|
|
text += s;
|
|
}
|
|
|
|
text += " Parameters:\n";
|
|
|
|
for (size_t i = 0; i < parameters.getCount(); ++i)
|
|
{
|
|
const ParameterClause& parameter = parameters[i];
|
|
|
|
string s;
|
|
parameter.print(s);
|
|
text += " " + s + "\n";
|
|
}
|
|
}
|
|
|
|
DdlNode* CreateAlterFunctionNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_FUNCTION);
|
|
|
|
const dsql_nod* variables = localDeclList;
|
|
if (variables)
|
|
{
|
|
// insure that variable names do not duplicate parameter names
|
|
|
|
SortedArray<MetaName> names;
|
|
|
|
for (size_t i = 0; i < parameters.getCount(); ++i)
|
|
{
|
|
ParameterClause& parameter = parameters[i];
|
|
names.add(parameter.name);
|
|
}
|
|
|
|
const dsql_nod* const* ptr = variables->nod_arg;
|
|
for (const dsql_nod* const* const end = ptr + variables->nod_count; ptr < end; ptr++)
|
|
{
|
|
if ((*ptr)->nod_type == nod_def_field)
|
|
{
|
|
const dsql_fld* field = (dsql_fld*) (*ptr)->nod_arg[e_dfl_field];
|
|
DEV_BLKCHK(field, dsql_type_fld);
|
|
|
|
if (names.exist(field->fld_name))
|
|
{
|
|
status_exception::raise(
|
|
Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
|
|
Arg::Gds(isc_dsql_var_conflict) <<
|
|
Arg::Str(field->fld_name));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
source.ltrim("\n\r\t ");
|
|
|
|
bool hasDefaultParams = false;
|
|
|
|
// compile default expressions
|
|
for (unsigned i = 0; i < parameters.getCount(); ++i)
|
|
{
|
|
ParameterClause& parameter = parameters[i];
|
|
|
|
if (parameter.legacyDefault)
|
|
{
|
|
hasDefaultParams = true;
|
|
parameter.legacyDefault->nod_arg[e_dft_default] =
|
|
PASS1_node(dsqlScratch, parameter.legacyDefault->nod_arg[e_dft_default]);
|
|
}
|
|
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].resolve(dsqlScratch);
|
|
|
|
returnType.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* attachment = transaction->getAttachment();
|
|
|
|
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());
|
|
|
|
FUN.RDB$LEGACY_FLAG.NULL = FALSE;
|
|
FUN.RDB$LEGACY_FLAG = 0;
|
|
|
|
FUN.RDB$RETURN_ARGUMENT.NULL = FALSE;
|
|
FUN.RDB$RETURN_ARGUMENT = 0;
|
|
|
|
if (package.hasData())
|
|
{
|
|
FUN.RDB$PACKAGE_NAME.NULL = FALSE;
|
|
strcpy(FUN.RDB$PACKAGE_NAME, package.c_str());
|
|
|
|
FUN.RDB$PRIVATE_FLAG.NULL = FALSE;
|
|
FUN.RDB$PRIVATE_FLAG = privateScope;
|
|
|
|
FUN.RDB$OWNER_NAME.NULL = FALSE;
|
|
strcpy(FUN.RDB$OWNER_NAME, packageOwner.c_str());
|
|
}
|
|
else
|
|
{
|
|
FUN.RDB$PACKAGE_NAME.NULL = TRUE;
|
|
FUN.RDB$PRIVATE_FLAG.NULL = TRUE;
|
|
|
|
FUN.RDB$OWNER_NAME.NULL = FALSE;
|
|
strcpy(FUN.RDB$OWNER_NAME, attachment->att_user->usr_user_name.c_str());
|
|
}
|
|
}
|
|
END_STORE
|
|
|
|
break;
|
|
}
|
|
catch (const status_exception& ex)
|
|
{
|
|
if (ex.value()[1] != isc_no_dup)
|
|
throw;
|
|
|
|
if (++faults > MAX_SSHORT)
|
|
throw;
|
|
|
|
fb_utils::init_status(tdbb->tdbb_status_vector);
|
|
}
|
|
}
|
|
|
|
if (package.isEmpty())
|
|
{
|
|
for (const TEXT* p = ALL_PROC_PRIVILEGES; *p; p++)
|
|
{
|
|
requestHandle.reset(tdbb, drq_s_fun_usr_prvs, DYN_REQUESTS);
|
|
|
|
STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
|
|
X IN RDB$USER_PRIVILEGES
|
|
{
|
|
strcpy(X.RDB$RELATION_NAME, name.c_str());
|
|
strcpy(X.RDB$USER, attachment->att_user->usr_user_name.c_str());
|
|
X.RDB$USER_TYPE = obj_user;
|
|
X.RDB$OBJECT_TYPE = obj_udf;
|
|
X.RDB$PRIVILEGE[0] = *p;
|
|
X.RDB$PRIVILEGE[1] = 0;
|
|
}
|
|
END_STORE
|
|
}
|
|
}
|
|
|
|
executeAlter(tdbb, dsqlScratch, transaction, false, false);
|
|
}
|
|
|
|
bool CreateAlterFunctionNode::executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
|
|
jrd_tra* transaction, bool secondPass, bool runTriggers)
|
|
{
|
|
Attachment* 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$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 (external)
|
|
{
|
|
if (!secondPass)
|
|
{
|
|
FUN.RDB$ENGINE_NAME.NULL = FALSE;
|
|
strcpy(FUN.RDB$ENGINE_NAME, external->engine.c_str());
|
|
|
|
if (external->name.length() >= sizeof(FUN.RDB$ENTRYPOINT))
|
|
{
|
|
status_exception::raise(
|
|
Arg::Gds(isc_arith_except) <<
|
|
Arg::Gds(isc_string_truncation));
|
|
}
|
|
|
|
FUN.RDB$ENTRYPOINT.NULL = (SSHORT) external->name.isEmpty();
|
|
strcpy(FUN.RDB$ENTRYPOINT, external->name.c_str());
|
|
}
|
|
}
|
|
else if (body)
|
|
{
|
|
if (secondPass)
|
|
{
|
|
FUN.RDB$FUNCTION_BLR.NULL = FALSE;
|
|
attachment->storeBinaryBlob(tdbb, transaction, &FUN.RDB$FUNCTION_BLR,
|
|
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)
|
|
{
|
|
// delete all old arguments
|
|
DropFunctionNode::dropArguments(tdbb, transaction, name, package);
|
|
|
|
// and insert the new ones
|
|
|
|
ParameterClause returnParameter(*tdbb->getDefaultPool(), returnType.legacyField,
|
|
returnType.collate, NULL, NULL);
|
|
returnParameter.resolve(dsqlScratch);
|
|
storeArgument(tdbb, dsqlScratch, transaction, 0, returnParameter);
|
|
|
|
for (unsigned i = 0; i < parameters.getCount(); ++i)
|
|
{
|
|
ParameterClause& parameter = parameters[i];
|
|
storeArgument(tdbb, dsqlScratch, transaction, i + 1, parameter);
|
|
}
|
|
}
|
|
|
|
return modified;
|
|
}
|
|
|
|
void CreateAlterFunctionNode::storeArgument(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
|
|
jrd_tra* transaction, unsigned pos, const ParameterClause& parameter)
|
|
{
|
|
Attachment* attachment = transaction->getAttachment();
|
|
|
|
AutoCacheRequest requestHandle(tdbb, drq_s_func_args2, DYN_REQUESTS);
|
|
|
|
STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
|
|
ARG IN RDB$FUNCTION_ARGUMENTS
|
|
{
|
|
ARG.RDB$FUNCTION_NAME.NULL = FALSE;
|
|
strcpy(ARG.RDB$FUNCTION_NAME, name.c_str());
|
|
|
|
// Avoid the return with name INTERNAL_FIELD_NAME.
|
|
if (parameter.name.hasData() && pos != 0)
|
|
{
|
|
ARG.RDB$ARGUMENT_NAME.NULL = FALSE;
|
|
strcpy(ARG.RDB$ARGUMENT_NAME, parameter.name.c_str());
|
|
}
|
|
else
|
|
ARG.RDB$ARGUMENT_NAME.NULL = TRUE;
|
|
|
|
if (package.hasData())
|
|
{
|
|
ARG.RDB$PACKAGE_NAME.NULL = FALSE;
|
|
strcpy(ARG.RDB$PACKAGE_NAME, package.c_str());
|
|
}
|
|
|
|
ARG.RDB$ARGUMENT_POSITION.NULL = FALSE;
|
|
ARG.RDB$ARGUMENT_POSITION = pos;
|
|
|
|
ARG.RDB$ARGUMENT_MECHANISM.NULL = TRUE;
|
|
ARG.RDB$NULL_FLAG.NULL = TRUE;
|
|
ARG.RDB$RELATION_NAME.NULL = TRUE;
|
|
ARG.RDB$FIELD_NAME.NULL = TRUE;
|
|
ARG.RDB$FIELD_SOURCE.NULL = TRUE;
|
|
ARG.RDB$DEFAULT_VALUE.NULL = TRUE;
|
|
ARG.RDB$DEFAULT_SOURCE.NULL = TRUE;
|
|
|
|
ARG.RDB$MECHANISM.NULL = TRUE;
|
|
ARG.RDB$FIELD_TYPE.NULL = TRUE;
|
|
ARG.RDB$FIELD_LENGTH.NULL = TRUE;
|
|
ARG.RDB$FIELD_PRECISION.NULL = TRUE;
|
|
ARG.RDB$FIELD_SCALE.NULL = TRUE;
|
|
ARG.RDB$CHARACTER_SET_ID.NULL = TRUE;
|
|
ARG.RDB$COLLATION_ID.NULL = TRUE;
|
|
|
|
ARG.RDB$ARGUMENT_MECHANISM.NULL = FALSE;
|
|
ARG.RDB$ARGUMENT_MECHANISM =
|
|
(USHORT) (parameter.fullDomain ? prm_mech_normal : prm_mech_type_of);
|
|
|
|
if (parameter.notNull)
|
|
{
|
|
ARG.RDB$NULL_FLAG.NULL = FALSE;
|
|
ARG.RDB$NULL_FLAG = TRUE;
|
|
}
|
|
|
|
ARG.RDB$FIELD_SOURCE.NULL = FALSE;
|
|
|
|
if (parameter.typeOfTable.isEmpty())
|
|
{
|
|
if (parameter.typeOfName.hasData())
|
|
strcpy(ARG.RDB$FIELD_SOURCE, parameter.typeOfName.c_str());
|
|
else
|
|
{
|
|
MetaName fieldName;
|
|
storeGlobalField(tdbb, transaction, fieldName, parameter);
|
|
strcpy(ARG.RDB$FIELD_SOURCE, fieldName.c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ARG.RDB$RELATION_NAME.NULL = FALSE;
|
|
strcpy(ARG.RDB$RELATION_NAME, parameter.typeOfTable.c_str());
|
|
|
|
ARG.RDB$FIELD_NAME.NULL = FALSE;
|
|
strcpy(ARG.RDB$FIELD_NAME, parameter.typeOfName.c_str());
|
|
|
|
strcpy(ARG.RDB$FIELD_SOURCE, parameter.fieldSource.c_str());
|
|
}
|
|
|
|
// ASF: If we used a collate with a domain or table.column type, write it
|
|
// into RDB$FUNCTION_ARGUMENTS.
|
|
|
|
if (parameter.collateSpecified && parameter.typeOfName.hasData())
|
|
{
|
|
ARG.RDB$COLLATION_ID.NULL = FALSE;
|
|
ARG.RDB$COLLATION_ID = parameter.collationId;
|
|
}
|
|
|
|
// ASF: I moved this block to write defaults on RDB$FUNCTION_ARGUMENTS.
|
|
// It was writing in RDB$FIELDS, but that would require special support
|
|
// for packaged functions signature verification.
|
|
|
|
if (parameter.legacyDefault)
|
|
{
|
|
ARG.RDB$DEFAULT_VALUE.NULL = FALSE;
|
|
ARG.RDB$DEFAULT_SOURCE.NULL = FALSE;
|
|
|
|
dsql_str* defaultString =
|
|
(dsql_str*) parameter.legacyDefault->nod_arg[e_dft_default_source];
|
|
string defaultSource = string(defaultString->str_data, defaultString->str_length);
|
|
attachment->storeMetaDataBlob(tdbb, transaction, &ARG.RDB$DEFAULT_SOURCE, defaultSource);
|
|
|
|
dsqlScratch->getBlrData().clear();
|
|
|
|
if (dsqlScratch->isVersion4())
|
|
dsqlScratch->appendUChar(blr_version4);
|
|
else
|
|
dsqlScratch->appendUChar(blr_version5);
|
|
|
|
GEN_expr(dsqlScratch, parameter.legacyDefault->nod_arg[e_dft_default]);
|
|
|
|
dsqlScratch->appendUChar(blr_eoc);
|
|
|
|
attachment->storeBinaryBlob(tdbb, transaction, &ARG.RDB$DEFAULT_VALUE,
|
|
dsqlScratch->getBlrData());
|
|
}
|
|
}
|
|
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);
|
|
|
|
if (parameters.getCount() != 0)
|
|
{
|
|
fb_assert(parameters.getCount() < size_t(MAX_USHORT / 2));
|
|
dsqlScratch->appendUChar(blr_message);
|
|
dsqlScratch->appendUChar(0);
|
|
dsqlScratch->appendUShort(2 * parameters.getCount());
|
|
|
|
for (unsigned i = 0; i < parameters.getCount(); ++i)
|
|
{
|
|
ParameterClause& parameter = parameters[i];
|
|
dsqlScratch->putDebugArgument(fb_dbg_arg_input, i,
|
|
parameter.name.c_str());
|
|
dsqlScratch->putType(parameter, true);
|
|
|
|
// add slot for null flag (parameter2)
|
|
dsqlScratch->appendUChar(blr_short);
|
|
dsqlScratch->appendUChar(0);
|
|
|
|
dsqlScratch->variables.add(MAKE_variable(parameter.legacyField,
|
|
parameter.name.c_str(), VAR_input, 0, (USHORT) (2 * i), 0));
|
|
}
|
|
}
|
|
|
|
dsqlScratch->appendUChar(blr_message);
|
|
dsqlScratch->appendUChar(1);
|
|
dsqlScratch->appendUShort(2);
|
|
|
|
dsqlScratch->putDebugArgument(fb_dbg_arg_output, 0, "");
|
|
dsqlScratch->putType(returnType, true);
|
|
|
|
// add slot for null flag (parameter2)
|
|
dsqlScratch->appendUChar(blr_short);
|
|
dsqlScratch->appendUChar(0);
|
|
|
|
VariableNode* const var = MAKE_variable(returnType.legacyField, "", VAR_output, 1, 0, 0);
|
|
dsqlScratch->variables.add(var);
|
|
dsqlScratch->outputVariables.add(var);
|
|
|
|
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.fullDomain || parameter.notNull)
|
|
{
|
|
// ASF: To validate input parameters we need only to read its value.
|
|
// Assigning it to null is an easy way to do this.
|
|
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]->dsqlVar;
|
|
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->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);
|
|
dsqlScratch->loopLevel = 0;
|
|
dsqlScratch->cursorNumber = 0;
|
|
|
|
GEN_statement(dsqlScratch, PASS1_statement(dsqlScratch, body));
|
|
|
|
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 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)
|
|
{
|
|
AutoCacheRequest requestHandle2(tdbb, drq_e_arg_gfld, DYN_REQUESTS);
|
|
|
|
FOR (REQUEST_HANDLE requestHandle2 TRANSACTION_HANDLE transaction)
|
|
FLD IN RDB$FIELDS
|
|
WITH FLD.RDB$FIELD_NAME EQ ARG.RDB$FIELD_SOURCE AND
|
|
FLD.RDB$FIELD_NAME STARTING WITH IMPLICIT_DOMAIN_PREFIX
|
|
{
|
|
bool erase = true;
|
|
|
|
AutoCacheRequest requestHandle3(tdbb, drq_e_arg_gfld2, DYN_REQUESTS);
|
|
|
|
FOR (REQUEST_HANDLE requestHandle3 TRANSACTION_HANDLE transaction)
|
|
ARG2 IN RDB$FUNCTION_ARGUMENTS
|
|
WITH ARG2.RDB$FUNCTION_NAME = ARG.RDB$FUNCTION_NAME AND
|
|
ARG2.RDB$PACKAGE_NAME EQUIV
|
|
NULLIF(packageName.c_str(), '') AND
|
|
ARG2.RDB$ARGUMENT_NAME = ARG.RDB$ARGUMENT_NAME
|
|
{
|
|
if (!ARG2.RDB$RELATION_NAME.NULL && !ARG2.RDB$FIELD_NAME.NULL)
|
|
erase = false;
|
|
}
|
|
END_FOR
|
|
|
|
if (erase)
|
|
ERASE FLD;
|
|
}
|
|
END_FOR
|
|
}
|
|
|
|
ERASE ARG;
|
|
}
|
|
END_FOR
|
|
}
|
|
|
|
void DropFunctionNode::print(string& text, Array<dsql_nod*>& /*nodes*/) const
|
|
{
|
|
text.printf(
|
|
"DropFunctionNode\n"
|
|
" name: '%s'\n",
|
|
name.c_str());
|
|
}
|
|
|
|
DdlNode* DropFunctionNode::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, Array<dsql_nod*>& /*nodes*/) const
|
|
{
|
|
text.printf(
|
|
"CreateAlterProcedureNode\n"
|
|
" name: '%s' create: %d alter: %d\n",
|
|
name.c_str(), create, alter);
|
|
|
|
if (external)
|
|
{
|
|
string s;
|
|
s.printf(" external -> name: '%s' engine: '%s'\n",
|
|
external->name.c_str(), external->engine.c_str());
|
|
text += s;
|
|
}
|
|
|
|
text += " Parameters:\n";
|
|
|
|
for (size_t i = 0; i < parameters.getCount(); ++i)
|
|
{
|
|
const ParameterClause& parameter = parameters[i];
|
|
|
|
string s;
|
|
parameter.print(s);
|
|
text += " " + s + "\n";
|
|
}
|
|
|
|
text += " Returns:\n";
|
|
|
|
for (size_t i = 0; i < returns.getCount(); ++i)
|
|
{
|
|
const ParameterClause& parameter = returns[i];
|
|
|
|
string s;
|
|
parameter.print(s);
|
|
text += " " + s + "\n";
|
|
}
|
|
}
|
|
|
|
DdlNode* CreateAlterProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
|
|
{
|
|
dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_PROCEDURE);
|
|
|
|
const dsql_nod* variables = localDeclList;
|
|
if (variables)
|
|
{
|
|
// insure that variable names do not duplicate parameter names
|
|
|
|
SortedArray<MetaName> names;
|
|
|
|
for (size_t i = 0; i < parameters.getCount(); ++i)
|
|
{
|
|
ParameterClause& parameter = parameters[i];
|
|
names.add(parameter.name);
|
|
}
|
|
|
|
for (size_t i = 0; i < returns.getCount(); ++i)
|
|
{
|
|
ParameterClause& parameter = returns[i];
|
|
names.add(parameter.name);
|
|
}
|
|
|
|
const dsql_nod* const* ptr = variables->nod_arg;
|
|
for (const dsql_nod* const* const end = ptr + variables->nod_count; ptr < end; ptr++)
|
|
{
|
|
if ((*ptr)->nod_type == nod_def_field)
|
|
{
|
|
const dsql_fld* field = (dsql_fld*) (*ptr)->nod_arg[e_dfl_field];
|
|
DEV_BLKCHK(field, dsql_type_fld);
|
|
|
|
if (names.exist(field->fld_name))
|
|
{
|
|
status_exception::raise(
|
|
Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
|
|
Arg::Gds(isc_dsql_var_conflict) << Arg::Str(field->fld_name));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
source.ltrim("\n\r\t ");
|
|
|
|
bool hasDefaultParams = false;
|
|
|
|
// compile default expressions
|
|
for (unsigned i = 0; i < parameters.getCount(); ++i)
|
|
{
|
|
ParameterClause& parameter = parameters[i];
|
|
|
|
if (parameter.legacyDefault)
|
|
{
|
|
hasDefaultParams = true;
|
|
parameter.legacyDefault->nod_arg[e_dft_default] =
|
|
PASS1_node(dsqlScratch, parameter.legacyDefault->nod_arg[e_dft_default]);
|
|
}
|
|
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].resolve(dsqlScratch);
|
|
|
|
for (unsigned i = 0; i < returns.getCount(); ++i)
|
|
returns[i].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* attachment = transaction->getAttachment();
|
|
|
|
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, attachment->att_user->usr_user_name.c_str());
|
|
}
|
|
}
|
|
END_STORE
|
|
|
|
break;
|
|
}
|
|
catch (const status_exception& ex)
|
|
{
|
|
if (ex.value()[1] != isc_no_dup)
|
|
throw;
|
|
|
|
if (++faults > MAX_SSHORT)
|
|
throw;
|
|
|
|
fb_utils::init_status(tdbb->tdbb_status_vector);
|
|
}
|
|
}
|
|
|
|
if (package.isEmpty())
|
|
{
|
|
for (const TEXT* p = ALL_PROC_PRIVILEGES; *p; p++)
|
|
{
|
|
requestHandle.reset(tdbb, drq_s_prc_usr_prvs, DYN_REQUESTS);
|
|
|
|
STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
|
|
X IN RDB$USER_PRIVILEGES
|
|
{
|
|
strcpy(X.RDB$RELATION_NAME, name.c_str());
|
|
strcpy(X.RDB$USER, attachment->att_user->usr_user_name.c_str());
|
|
X.RDB$USER_TYPE = obj_user;
|
|
X.RDB$OBJECT_TYPE = obj_procedure;
|
|
X.RDB$PRIVILEGE[0] = *p;
|
|
X.RDB$PRIVILEGE[1] = 0;
|
|
}
|
|
END_STORE
|
|
}
|
|
}
|
|
|
|
executeAlter(tdbb, dsqlScratch, transaction, false, false);
|
|
}
|
|
|
|
bool CreateAlterProcedureNode::executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
|
|
jrd_tra* transaction, bool secondPass, bool runTriggers)
|
|
{
|
|
Attachment* attachment = transaction->getAttachment();
|
|
AutoCacheRequest requestHandle(tdbb, drq_m_prcs2, DYN_REQUESTS);
|
|
bool modified = false;
|
|
|
|
DsqlCompiledStatement* statement = dsqlScratch->getStatement();
|
|
|
|
FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
|
|
P IN RDB$PROCEDURES
|
|
WITH P.RDB$PROCEDURE_NAME EQ name.c_str() AND
|
|
P.RDB$PACKAGE_NAME EQUIV NULLIF(package.c_str(), '')
|
|
{
|
|
if (!P.RDB$SYSTEM_FLAG.NULL && P.RDB$SYSTEM_FLAG)
|
|
status_exception::raise(Arg::Gds(isc_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_arith_except) <<
|
|
Arg::Gds(isc_string_truncation));
|
|
}
|
|
|
|
P.RDB$ENTRYPOINT.NULL = (SSHORT) external->name.isEmpty();
|
|
strcpy(P.RDB$ENTRYPOINT, external->name.c_str());
|
|
}
|
|
}
|
|
else if (body)
|
|
{
|
|
if (secondPass)
|
|
{
|
|
P.RDB$PROCEDURE_BLR.NULL = FALSE;
|
|
attachment->storeBinaryBlob(tdbb, transaction, &P.RDB$PROCEDURE_BLR,
|
|
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 (unsigned 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 (unsigned i = 0; i < returns.getCount(); ++i)
|
|
{
|
|
ParameterClause& parameter = returns[i];
|
|
storeParameter(tdbb, dsqlScratch, transaction, 1, i, parameter, NULL);
|
|
}
|
|
|
|
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 type, unsigned pos, const ParameterClause& parameter,
|
|
const bid* comment)
|
|
{
|
|
Attachment* attachment = transaction->getAttachment();
|
|
|
|
AutoCacheRequest requestHandle(tdbb, drq_s_prms4, DYN_REQUESTS);
|
|
|
|
STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
|
|
PRM IN RDB$PROCEDURE_PARAMETERS
|
|
{
|
|
PRM.RDB$PARAMETER_NAME.NULL = FALSE;
|
|
strcpy(PRM.RDB$PARAMETER_NAME, parameter.name.c_str());
|
|
|
|
PRM.RDB$PROCEDURE_NAME.NULL = FALSE;
|
|
strcpy(PRM.RDB$PROCEDURE_NAME, name.c_str());
|
|
|
|
if (package.hasData())
|
|
{
|
|
PRM.RDB$PACKAGE_NAME.NULL = FALSE;
|
|
strcpy(PRM.RDB$PACKAGE_NAME, package.c_str());
|
|
}
|
|
else
|
|
PRM.RDB$PACKAGE_NAME.NULL = TRUE;
|
|
|
|
PRM.RDB$SYSTEM_FLAG = 0;
|
|
PRM.RDB$SYSTEM_FLAG.NULL = FALSE;
|
|
|
|
PRM.RDB$PARAMETER_NUMBER.NULL = FALSE;
|
|
PRM.RDB$PARAMETER_NUMBER = pos;
|
|
|
|
PRM.RDB$PARAMETER_TYPE.NULL = FALSE;
|
|
PRM.RDB$PARAMETER_TYPE = type;
|
|
|
|
PRM.RDB$PARAMETER_MECHANISM.NULL = FALSE;
|
|
PRM.RDB$PARAMETER_MECHANISM =
|
|
(USHORT) (parameter.fullDomain ? prm_mech_normal : prm_mech_type_of);
|
|
|
|
PRM.RDB$NULL_FLAG.NULL = !parameter.notNull;
|
|
PRM.RDB$NULL_FLAG = parameter.notNull;
|
|
|
|
PRM.RDB$RELATION_NAME.NULL = parameter.typeOfTable.isEmpty();
|
|
PRM.RDB$FIELD_NAME.NULL = PRM.RDB$RELATION_NAME.NULL || parameter.typeOfName.isEmpty();
|
|
|
|
PRM.RDB$FIELD_SOURCE.NULL = FALSE;
|
|
|
|
if (PRM.RDB$RELATION_NAME.NULL)
|
|
{
|
|
if (parameter.typeOfName.hasData())
|
|
strcpy(PRM.RDB$FIELD_SOURCE, parameter.typeOfName.c_str());
|
|
else
|
|
{
|
|
MetaName fieldName;
|
|
storeGlobalField(tdbb, transaction, fieldName, parameter);
|
|
strcpy(PRM.RDB$FIELD_SOURCE, fieldName.c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
strcpy(PRM.RDB$RELATION_NAME, parameter.typeOfTable.c_str());
|
|
strcpy(PRM.RDB$FIELD_NAME, parameter.typeOfName.c_str());
|
|
strcpy(PRM.RDB$FIELD_SOURCE, parameter.fieldSource.c_str());
|
|
}
|
|
|
|
// ASF: If we used a collate with a domain or table.column type, write it
|
|
// in RDB$PROCEDURE_PARAMETERS.
|
|
|
|
PRM.RDB$COLLATION_ID.NULL = !(parameter.collateSpecified && parameter.typeOfName.hasData());
|
|
|
|
if (!PRM.RDB$COLLATION_ID.NULL)
|
|
PRM.RDB$COLLATION_ID = parameter.collationId;
|
|
|
|
// ASF: I moved this block to write defaults on RDB$PROCEDURE_PARAMETERS.
|
|
// It was writing in RDB$FIELDS, but that would require special support
|
|
// for packaged procedures signature verification.
|
|
|
|
PRM.RDB$DEFAULT_VALUE.NULL = !parameter.legacyDefault;
|
|
PRM.RDB$DEFAULT_SOURCE.NULL = !parameter.legacyDefault;
|
|
|
|
if (parameter.legacyDefault)
|
|
{
|
|
dsql_str* defaultString =
|
|
(dsql_str*) parameter.legacyDefault->nod_arg[e_dft_default_source];
|
|
string defaultSource = string(defaultString->str_data, defaultString->str_length);
|
|
attachment->storeMetaDataBlob(tdbb, transaction, &PRM.RDB$DEFAULT_SOURCE, defaultSource);
|
|
|
|
dsqlScratch->getBlrData().clear();
|
|
|
|
if (dsqlScratch->isVersion4())
|
|
dsqlScratch->appendUChar(blr_version4);
|
|
else
|
|
dsqlScratch->appendUChar(blr_version5);
|
|
|
|
GEN_expr(dsqlScratch, parameter.legacyDefault->nod_arg[e_dft_default]);
|
|
|
|
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);
|
|
|
|
if (parameters.getCount() != 0)
|
|
{
|
|
fb_assert(parameters.getCount() < MAX_USHORT / 2);
|
|
dsqlScratch->appendUChar(blr_message);
|
|
dsqlScratch->appendUChar(0);
|
|
dsqlScratch->appendUShort(2 * parameters.getCount());
|
|
|
|
for (unsigned i = 0; i < parameters.getCount(); ++i)
|
|
{
|
|
ParameterClause& parameter = parameters[i];
|
|
dsqlScratch->putDebugArgument(fb_dbg_arg_input, i, parameter.name.c_str());
|
|
dsqlScratch->putType(parameter, true);
|
|
|
|
// add slot for null flag (parameter2)
|
|
dsqlScratch->appendUChar(blr_short);
|
|
dsqlScratch->appendUChar(0);
|
|
|
|
dsqlScratch->variables.add(MAKE_variable(parameter.legacyField,
|
|
parameter.name.c_str(), VAR_input, 0, (USHORT) (2 * i), 0));
|
|
}
|
|
}
|
|
|
|
fb_assert(returns.getCount() < MAX_USHORT / 2);
|
|
dsqlScratch->appendUChar(blr_message);
|
|
dsqlScratch->appendUChar(1);
|
|
dsqlScratch->appendUShort(2 * returns.getCount() + 1);
|
|
|
|
if (returns.getCount() != 0)
|
|
{
|
|
for (unsigned i = 0; i < returns.getCount(); ++i)
|
|
{
|
|
ParameterClause& parameter = returns[i];
|
|
dsqlScratch->putDebugArgument(fb_dbg_arg_output, i, parameter.name.c_str());
|
|
dsqlScratch->putType(parameter, true);
|
|
|
|
// add slot for null flag (parameter2)
|
|
dsqlScratch->appendUChar(blr_short);
|
|
dsqlScratch->appendUChar(0);
|
|
|
|
VariableNode* const var = MAKE_variable(parameter.legacyField,
|
|
parameter.name.c_str(), VAR_output, 1, (USHORT) (2 * i), i);
|
|
|
|
dsqlScratch->variables.add(var);
|
|
dsqlScratch->outputVariables.add(var);
|
|
}
|
|
}
|
|
|
|
// add slot for EOS
|
|
dsqlScratch->appendUChar(blr_short);
|
|
dsqlScratch->appendUChar(0);
|
|
|
|
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.fullDomain || parameter.notNull)
|
|
{
|
|
// ASF: To validate an input parameter we need only to read its value.
|
|
// Assigning it to null is an easy way to do this.
|
|
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<VariableNode*>::const_iterator i = dsqlScratch->outputVariables.begin();
|
|
i != dsqlScratch->outputVariables.end();
|
|
++i)
|
|
{
|
|
dsqlScratch->putLocalVariable((*i)->dsqlVar, 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->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);
|
|
dsqlScratch->loopLevel = 0;
|
|
dsqlScratch->cursorNumber = 0;
|
|
|
|
GEN_statement(dsqlScratch, PASS1_statement(dsqlScratch, body));
|
|
|
|
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(), '')
|
|
{
|
|
if (!PRM.RDB$DESCRIPTION.NULL)
|
|
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, Array<dsql_nod*>& /*nodes*/) 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* 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_arith_except) <<
|
|
Arg::Gds(isc_string_truncation));
|
|
}
|
|
|
|
TRG.RDB$ENTRYPOINT.NULL = (SSHORT) external->name.isEmpty();
|
|
strcpy(TRG.RDB$ENTRYPOINT, external->name.c_str());
|
|
}
|
|
else if (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, Array<dsql_nod*>& /*nodes*/) const
|
|
{
|
|
text.printf(
|
|
"CreateAlterTriggerNode\n"
|
|
" name: '%s' create: %d alter: %d relationName: '%s'\n"
|
|
" type: %d, %d active: %d, %d position: %d, %d\n",
|
|
name.c_str(), create, alter, relationName.c_str(), type.specified, type.value,
|
|
active.specified, active.value, position.specified, position.value);
|
|
|
|
if (external)
|
|
{
|
|
string s;
|
|
s.printf(" external -> name: '%s' engine: '%s'\n",
|
|
external->name.c_str(), external->engine.c_str());
|
|
text += s;
|
|
}
|
|
}
|
|
|
|
DdlNode* CreateAlterTriggerNode::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* 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->resetContextStack();
|
|
|
|
if (relationName.hasData())
|
|
{
|
|
dsql_nod* relationNode = FB_NEW_RPT(getPool(), e_rln_count) dsql_nod;
|
|
///trigger_node->nod_arg[e_trg_table] = relationNode;
|
|
relationNode->nod_type = nod_relation_name;
|
|
relationNode->nod_count = e_rln_count;
|
|
relationNode->nod_arg[e_rln_name] = (dsql_nod*)
|
|
MAKE_string(relationName.c_str(), relationName.length());
|
|
|
|
dsql_nod* const temp = relationNode->nod_arg[e_rln_alias];
|
|
if (hasOldContext(type.value))
|
|
{
|
|
relationNode->nod_arg[e_rln_alias] = (dsql_nod*) MAKE_cstring(OLD_CONTEXT);
|
|
dsql_ctx* oldContext = PASS1_make_context(dsqlScratch, relationNode);
|
|
oldContext->ctx_flags |= CTX_system;
|
|
}
|
|
else
|
|
dsqlScratch->contextNumber++;
|
|
|
|
if (hasNewContext(type.value))
|
|
{
|
|
relationNode->nod_arg[e_rln_alias] = (dsql_nod*) MAKE_cstring(NEW_CONTEXT);
|
|
dsql_ctx* newContext = PASS1_make_context(dsqlScratch, relationNode);
|
|
newContext->ctx_flags |= CTX_system;
|
|
}
|
|
else
|
|
dsqlScratch->contextNumber++;
|
|
|
|
relationNode->nod_arg[e_rln_alias] = temp;
|
|
}
|
|
|
|
// generate the trigger blr
|
|
|
|
if (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;
|
|
GEN_statement(dsqlScratch, PASS1_statement(dsqlScratch, body));
|
|
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, Array<dsql_nod*>& /*nodes*/) 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, Array<dsql_nod*>& /*nodes*/) 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* attachment = transaction->tra_attachment;
|
|
|
|
// 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$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())
|
|
{
|
|
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::Gds(ENCODE_ISC_MSG(223, DYN_MSG_FAC)) <<
|
|
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::Gds(ENCODE_ISC_MSG(222, DYN_MSG_FAC)));
|
|
}
|
|
|
|
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::Gds(ENCODE_ISC_MSG(222, DYN_MSG_FAC)));
|
|
}
|
|
|
|
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::Gds(ENCODE_ISC_MSG(222, DYN_MSG_FAC)));
|
|
}
|
|
|
|
// 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
|
|
|
|
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, Array<dsql_nod*>& /*nodes*/) 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_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;
|
|
}
|
|
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, Array<dsql_nod*>& /*nodes*/) const
|
|
{
|
|
string nameTypeStr;
|
|
nameType.print(nameTypeStr);
|
|
|
|
text =
|
|
"CreateDomainNode\n"
|
|
" " + nameTypeStr + "\n";
|
|
}
|
|
|
|
void CreateDomainNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
|
|
jrd_tra* transaction)
|
|
{
|
|
Attachment* attachment = transaction->tra_attachment;
|
|
|
|
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 dsql_nod* elements = nameType.legacyField->fld_ranges;
|
|
const USHORT dims = elements ? elements->nod_count / 2 : 0;
|
|
|
|
if (nameType.legacyDefault && dims != 0)
|
|
{
|
|
// Default value is not allowed for array type in domain %s
|
|
status_exception::raise(Arg::Gds(ENCODE_ISC_MSG(226, DYN_MSG_FAC)) << nameType.name);
|
|
}
|
|
|
|
nameType.resolve(dsqlScratch);
|
|
|
|
dsqlScratch->domainValue.dsc_dtype = nameType.type;
|
|
dsqlScratch->domainValue.dsc_length = nameType.length;
|
|
dsqlScratch->domainValue.dsc_scale = nameType.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, nameType);
|
|
|
|
if (nameType.legacyDefault || 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.legacyDefault)
|
|
{
|
|
dsql_str* defaultString =
|
|
(dsql_str*) nameType.legacyDefault->nod_arg[e_dft_default_source];
|
|
string defaultSource = string(defaultString->str_data, defaultString->str_length);
|
|
|
|
FLD.RDB$DEFAULT_SOURCE.NULL = FALSE;
|
|
attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$DEFAULT_SOURCE, defaultSource);
|
|
|
|
dsqlScratch->getBlrData().clear();
|
|
dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5);
|
|
|
|
dsql_nod* node = PASS1_node(dsqlScratch, nameType.legacyDefault->nod_arg[e_dft_default]);
|
|
|
|
GEN_hidden_variables(dsqlScratch, true);
|
|
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)
|
|
{
|
|
dsql_str* checkString = (dsql_str*) check->nod_arg[e_cnstr_source];
|
|
string checkSource = string(checkString->str_data, checkString->str_length);
|
|
|
|
FLD.RDB$VALIDATION_SOURCE.NULL = FALSE;
|
|
attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$VALIDATION_SOURCE, checkSource);
|
|
|
|
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;
|
|
|
|
dsql_nod* node = PASS1_node(dsqlScratch, check->nod_arg[e_cnstr_condition]);
|
|
|
|
GEN_hidden_variables(dsqlScratch, true);
|
|
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;
|
|
|
|
// 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:
|
|
{
|
|
// CVC: Because our caller invoked DSC_make_descriptor() on newFld previously,
|
|
// we should have the added bytes for varchar. For cstring, we are done, since
|
|
// DSC_make_descriptor(DSC_string_length) != DSC_string_length(DSC_make_descriptor).
|
|
|
|
const USHORT maxflen = DSC_string_length(&origFld.dyn_dsc);
|
|
|
|
// We can have this assertion since this case is for both string fields.
|
|
const ULONG new_len = DSC_string_length(&newFld.dyn_dsc);
|
|
fb_assert(new_len - maxflen == (ULONG) newFld.dyn_charbytelen - origFld.dyn_charbytelen);
|
|
// if (newFld.dyn_dsc.dsc_length < maxflen)
|
|
if (new_len < maxflen)
|
|
{
|
|
// 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 = 87; // 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:
|
|
{
|
|
const USHORT maxflen = DSC_string_length(&origFld.dyn_dsc);
|
|
|
|
// CVC: Solve bug #910423, missing DSC_string_length call.
|
|
// if (newFld.dyn_dsc.dsc_length < maxflen)
|
|
if (DSC_string_length(&newFld.dyn_dsc) < maxflen)
|
|
{
|
|
// 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:
|
|
{
|
|
const USHORT maxflen = DSC_string_length(&origFld.dyn_dsc);
|
|
|
|
// CVC: Solve bug #910423, missing DSC_string_length call.
|
|
// if (newFld.dyn_dsc.dsc_length < maxflen)
|
|
if (DSC_string_length(&newFld.dyn_dsc) < maxflen)
|
|
{
|
|
// 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 = 87; // MODIFY RDB$FIELDS FAILED
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
fb_assert(FALSE);
|
|
errorCode = 87; // 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(DSC_string_length(&origFld.dyn_dsc)));
|
|
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::Gds(ENCODE_ISC_MSG(95, DYN_MSG_FAC)));
|
|
}
|
|
}
|
|
|
|
// 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, Array<dsql_nod*>& /*nodes*/) const
|
|
{
|
|
text.printf(
|
|
"AlterDomainNode\n"
|
|
" %s\n", name.c_str());
|
|
}
|
|
|
|
void AlterDomainNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
|
|
jrd_tra* transaction)
|
|
{
|
|
Attachment* 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::Gds(ENCODE_ISC_MSG(160, DYN_MSG_FAC)));
|
|
}
|
|
|
|
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.c_str()))
|
|
{
|
|
// 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.fld_dtype;
|
|
dsqlScratch->domainValue.dsc_length = localField.fld_length;
|
|
dsqlScratch->domainValue.dsc_scale = localField.fld_scale;
|
|
|
|
dsql_str* checkString = (dsql_str*) setConstraint->nod_arg[e_cnstr_source];
|
|
string checkSource = string(checkString->str_data, checkString->str_length);
|
|
|
|
FLD.RDB$VALIDATION_SOURCE.NULL = FALSE;
|
|
attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$VALIDATION_SOURCE, checkSource);
|
|
|
|
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;
|
|
|
|
dsql_nod* node = PASS1_node(dsqlScratch, setConstraint->nod_arg[e_cnstr_condition]);
|
|
|
|
GEN_hidden_variables(dsqlScratch, true);
|
|
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::Gds(ENCODE_ISC_MSG(226, DYN_MSG_FAC)) << name);
|
|
}
|
|
|
|
dsql_str* defaultString =
|
|
(dsql_str*) setDefault->nod_arg[e_dft_default_source];
|
|
string defaultSource = string(defaultString->str_data, defaultString->str_length);
|
|
|
|
FLD.RDB$DEFAULT_SOURCE.NULL = FALSE;
|
|
attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$DEFAULT_SOURCE, defaultSource);
|
|
|
|
dsqlScratch->getBlrData().clear();
|
|
dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5);
|
|
|
|
dsql_nod* node = PASS1_node(dsqlScratch, setDefault->nod_arg[e_dft_default]);
|
|
|
|
GEN_hidden_variables(dsqlScratch, true);
|
|
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->type)
|
|
{
|
|
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->type], type->scale,
|
|
typeLength, type->subType, type->charSetId, type->collationId);
|
|
|
|
newDom.dyn_fld_name = name;
|
|
newDom.dyn_charbytelen = typeLength;
|
|
newDom.dyn_dtype = blr_dtypes[type->type];
|
|
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::Gds(ENCODE_ISC_MSG(276, DYN_MSG_FAC)) << 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::Gds(ENCODE_ISC_MSG(89, DYN_MSG_FAC)));
|
|
}
|
|
|
|
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::Gds(ENCODE_ISC_MSG(204, DYN_MSG_FAC)) << 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, Array<dsql_nod*>& /*nodes*/) 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;
|
|
|
|
found = true;
|
|
}
|
|
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::Gds(ENCODE_ISC_MSG(89, DYN_MSG_FAC)));
|
|
}
|
|
|
|
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::Gds(ENCODE_ISC_MSG(43, DYN_MSG_FAC)) << 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::Gds(ENCODE_ISC_MSG(239, DYN_MSG_FAC)) << 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
|
|
|
|
//// FIXME: Check domain usage in functions.
|
|
}
|
|
|
|
|
|
//----------------------
|
|
|
|
|
|
void CreateAlterExceptionNode::print(string& text, Array<dsql_nod*>& /*nodes*/) 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::Gds(ENCODE_ISC_MSG(144, DYN_MSG_FAC)));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
executeCreate(tdbb, dsqlScratch, transaction);
|
|
|
|
savePoint.release(); // everything is ok
|
|
}
|
|
|
|
void CreateAlterExceptionNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
|
|
jrd_tra* transaction)
|
|
{
|
|
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());
|
|
|
|
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_no_dup || ++faults > MAX_SSHORT)
|
|
throw;
|
|
|
|
fb_utils::init_status(tdbb->tdbb_status_vector);
|
|
}
|
|
}
|
|
|
|
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, Array<dsql_nod*>& /*nodes*/) 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;
|
|
found = true;
|
|
}
|
|
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::Gds(ENCODE_ISC_MSG(144, DYN_MSG_FAC)));
|
|
}
|
|
|
|
savePoint.release(); // everything is ok
|
|
}
|
|
|
|
|
|
//----------------------
|
|
|
|
|
|
void CreateSequenceNode::print(string& text, Array<dsql_nod*>& /*nodes*/) const
|
|
{
|
|
text.printf(
|
|
"CreateSequenceNode\n"
|
|
" name: %s\n",
|
|
name.c_str());
|
|
}
|
|
|
|
void CreateSequenceNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
|
|
jrd_tra* 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);
|
|
}
|
|
|
|
void CreateSequenceNode::store(thread_db* tdbb, jrd_tra* transaction, const MetaName& name,
|
|
fb_sysflag sysFlag)
|
|
{
|
|
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());
|
|
}
|
|
END_STORE
|
|
|
|
break;
|
|
}
|
|
catch (const status_exception& ex)
|
|
{
|
|
if (ex.value()[1] != isc_no_dup || ++faults > MAX_SSHORT)
|
|
throw;
|
|
|
|
fb_utils::init_status(tdbb->tdbb_status_vector);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------
|
|
|
|
|
|
// Delete a record from RDB$GENERATORS. Return true if succeeded.
|
|
bool DropSequenceNode::deleteGenerator(thread_db* tdbb, jrd_tra* transaction, const MetaName& name)
|
|
{
|
|
AutoCacheRequest request(tdbb, drq_e_ident_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()
|
|
{
|
|
ERASE GEN;
|
|
found = true;
|
|
}
|
|
END_FOR
|
|
|
|
return found;
|
|
}
|
|
|
|
|
|
//----------------------
|
|
|
|
|
|
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, NULL, 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* 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, NULL, 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::Gds(ENCODE_ISC_MSG(52, DYN_MSG_FAC)) <<
|
|
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::Gds(ENCODE_ISC_MSG(187, DYN_MSG_FAC)) <<
|
|
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::Gds(ENCODE_ISC_MSG(187, DYN_MSG_FAC)) <<
|
|
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::deleteGenerator(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::Gds(ENCODE_ISC_MSG(176, DYN_MSG_FAC)) << fieldName << relationName);
|
|
}
|
|
}
|
|
|
|
void RelationNode::storePrivileges(thread_db* tdbb, jrd_tra* transaction)
|
|
{
|
|
Attachment* attachment = transaction->tra_attachment;
|
|
|
|
AutoCacheRequest request(tdbb, drq_s_usr_prvs, DYN_REQUESTS);
|
|
|
|
for (const TEXT* p = ALL_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, attachment->att_user->usr_user_name.c_str());
|
|
X.RDB$USER_TYPE = obj_user;
|
|
X.RDB$OBJECT_TYPE = obj_relation;
|
|
X.RDB$PRIVILEGE[0] = *p;
|
|
X.RDB$PRIVILEGE[1] = 0;
|
|
X.RDB$GRANT_OPTION = 1;
|
|
}
|
|
END_STORE
|
|
}
|
|
}
|
|
|
|
void RelationNode::defineField(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
|
|
jrd_tra* transaction, const dsql_nod* element, SSHORT position, const dsql_nod* pkCols)
|
|
{
|
|
dsql_fld* field = (dsql_fld*) element->nod_arg[Dsql::e_dfl_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 (element->nod_arg[Dsql::e_dfl_identity])
|
|
notNullFlag = true; // identity columns are implicitly not null
|
|
|
|
if (element->nod_arg[Dsql::e_dfl_constraint])
|
|
{
|
|
const dsql_nod* node = element->nod_arg[Dsql::e_dfl_constraint];
|
|
const dsql_nod* const* const endPtr = node->nod_arg + node->nod_count;
|
|
|
|
for (const dsql_nod* const* ptr = node->nod_arg; ptr != endPtr; ++ptr)
|
|
{
|
|
if ((*ptr)->nod_type == Dsql::nod_rel_constraint)
|
|
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 (int i = 0; !notNullFlag && i < pkCols->nod_count; ++i)
|
|
{
|
|
const dsql_str* pkFieldName = (dsql_str*) pkCols->nod_arg[i]->nod_arg[Dsql::e_fln_name];
|
|
|
|
if (field->fld_name == pkFieldName->str_data)
|
|
notNullFlag = true;
|
|
}
|
|
}
|
|
|
|
FieldDefinition fieldDefinition(*tdbb->getDefaultPool());
|
|
fieldDefinition.relationName = name;
|
|
fieldDefinition.name = field->fld_name;
|
|
fieldDefinition.notNullFlag = notNullFlag;
|
|
|
|
if (position >= 0)
|
|
fieldDefinition.position = position;
|
|
|
|
const dsql_nod* domainNode = element->nod_arg[Dsql::e_dfl_domain];
|
|
|
|
if (domainNode)
|
|
{
|
|
const dsql_nod* node1 = domainNode->nod_arg[Dsql::e_dom_name];
|
|
const dsql_str* domainName = (dsql_str*) node1->nod_arg[Dsql::e_fln_name];
|
|
|
|
// Get the domain information.
|
|
if (!METD_get_domain(transaction, field, domainName->str_data))
|
|
{
|
|
// 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) << domainName->str_data);
|
|
}
|
|
|
|
fieldDefinition.fieldSource = domainName->str_data;
|
|
}
|
|
else
|
|
{
|
|
string computedSource;
|
|
BlrWriter::BlrData computedValue;
|
|
|
|
if (element->nod_arg[e_dfl_computed])
|
|
{
|
|
field->fld_flags |= FLD_computed;
|
|
|
|
defineComputed(tdbb, dsqlScratch, field, element->nod_arg[e_dfl_computed],
|
|
computedSource, computedValue);
|
|
}
|
|
|
|
// Don't use the collate in the generated domain.
|
|
TypeClause fieldType(*tdbb->getDefaultPool(), field, NULL);
|
|
fieldType.resolve(dsqlScratch);
|
|
|
|
// Generate a domain.
|
|
storeGlobalField(tdbb, transaction, fieldDefinition.fieldSource, fieldType,
|
|
computedSource, computedValue);
|
|
}
|
|
|
|
if ((relation->rel_flags & REL_external) &&
|
|
(field->fld_dtype == dtype_blob || field->fld_dtype == dtype_array ||
|
|
field->fld_dimensions))
|
|
{
|
|
const char* typeName = (field->fld_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 (element->nod_arg[Dsql::e_dfl_collate])
|
|
DDL_resolve_intl_type(dsqlScratch, field, (dsql_str*) element->nod_arg[Dsql::e_dfl_collate]);
|
|
|
|
if (element->nod_arg[Dsql::e_dfl_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::Gds(ENCODE_ISC_MSG(273, DYN_MSG_FAC)) << field->fld_name << name);
|
|
}
|
|
|
|
DYN_UTIL_generate_generator_name(tdbb, fieldDefinition.identitySequence);
|
|
|
|
CreateSequenceNode::store(tdbb, transaction, fieldDefinition.identitySequence,
|
|
fb_sysflag_identity_generator);
|
|
}
|
|
|
|
BlrWriter::BlrData defaultValue;
|
|
|
|
if (element->nod_arg[Dsql::e_dfl_default])
|
|
{
|
|
if (defineDefault(tdbb, dsqlScratch, field, element->nod_arg[Dsql::e_dfl_default],
|
|
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 (element->nod_arg[Dsql::e_dfl_collate])
|
|
fieldDefinition.collationId = field->fld_collation_id;
|
|
|
|
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 COMPUTED BY clause.
|
|
void RelationNode::defineComputed(thread_db* /*tdbb*/, DsqlCompilerScratch* dsqlScratch,
|
|
dsql_fld* field, dsql_nod* node, string& source, BlrWriter::BlrData& value)
|
|
{
|
|
AutoSetRestore2<dsql_nod*, DsqlCompiledStatement> autoDdlNode(dsqlScratch->getStatement(),
|
|
&DsqlCompiledStatement::getDdlNode, &DsqlCompiledStatement::setDdlNode, node);
|
|
|
|
// Get the table node and set up correct context.
|
|
DDL_reset_context_stack(dsqlScratch);
|
|
|
|
// Save the size of the field if it is specified.
|
|
dsc saveDesc;
|
|
saveDesc.dsc_dtype = 0;
|
|
|
|
if (field && field->fld_dtype)
|
|
{
|
|
fb_assert(field->fld_dtype <= MAX_UCHAR);
|
|
saveDesc.dsc_dtype = (UCHAR) field->fld_dtype;
|
|
saveDesc.dsc_length = field->fld_length;
|
|
fb_assert(field->fld_scale <= MAX_SCHAR);
|
|
saveDesc.dsc_scale = (SCHAR) field->fld_scale;
|
|
saveDesc.dsc_sub_type = field->fld_sub_type;
|
|
|
|
field->fld_dtype = 0;
|
|
field->fld_length = 0;
|
|
field->fld_scale = 0;
|
|
field->fld_sub_type = 0;
|
|
}
|
|
|
|
PASS1_make_context(dsqlScratch, dsqlNode);
|
|
|
|
dsql_nod* input = PASS1_node(dsqlScratch, node->nod_arg[Dsql::e_cmp_expr]);
|
|
|
|
// 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_hidden_variables(dsqlScratch, true);
|
|
GEN_expr(dsqlScratch, input);
|
|
dsqlScratch->appendUChar(blr_eoc);
|
|
|
|
if (saveDesc.dsc_dtype)
|
|
{
|
|
// Restore the field size/type overrides.
|
|
field->fld_dtype = saveDesc.dsc_dtype;
|
|
field->fld_length = saveDesc.dsc_length;
|
|
field->fld_scale = saveDesc.dsc_scale;
|
|
|
|
if (field->fld_dtype <= dtype_any_text)
|
|
{
|
|
field->fld_character_set_id = DSC_GET_CHARSET(&saveDesc);
|
|
field->fld_collation_id= DSC_GET_COLLATE(&saveDesc);
|
|
}
|
|
else
|
|
field->fld_sub_type = saveDesc.dsc_sub_type;
|
|
}
|
|
else if (field)
|
|
{
|
|
// Use size calculated.
|
|
field->fld_dtype = desc.dsc_dtype;
|
|
field->fld_length = desc.dsc_length;
|
|
field->fld_scale = desc.dsc_scale;
|
|
|
|
if (field->fld_dtype <= dtype_any_text)
|
|
{
|
|
field->fld_character_set_id = DSC_GET_CHARSET(&desc);
|
|
field->fld_collation_id = DSC_GET_COLLATE(&desc);
|
|
}
|
|
else
|
|
field->fld_sub_type = desc.dsc_sub_type;
|
|
}
|
|
|
|
DDL_reset_context_stack(dsqlScratch);
|
|
|
|
// Generate the source text.
|
|
const dsql_str* sourceStr = (dsql_str*) node->nod_arg[Dsql::e_cmp_text];
|
|
fb_assert(sourceStr->str_length <= MAX_USHORT);
|
|
source = string(sourceStr->str_data, sourceStr->str_length);
|
|
|
|
value.assign(dsqlScratch->getBlrData());
|
|
}
|
|
|
|
// Define a DEFAULT clause. Return true for DEFAULT NULL.
|
|
bool RelationNode::defineDefault(thread_db* /*tdbb*/, DsqlCompilerScratch* dsqlScratch,
|
|
dsql_fld* /*field*/, dsql_nod* node, string& source, BlrWriter::BlrData& value)
|
|
{
|
|
dsql_nod* input = PASS1_node(dsqlScratch, node->nod_arg[Dsql::e_dft_default]);
|
|
|
|
// Generate the blr expression.
|
|
|
|
dsqlScratch->getBlrData().clear();
|
|
dsqlScratch->getDebugData().clear();
|
|
dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5);
|
|
|
|
GEN_hidden_variables(dsqlScratch, true);
|
|
GEN_expr(dsqlScratch, input);
|
|
dsqlScratch->appendUChar(blr_eoc);
|
|
|
|
// Generate the source text.
|
|
const dsql_str* sourceStr = (dsql_str*) node->nod_arg[Dsql::e_dft_default_source];
|
|
fb_assert(sourceStr->str_length <= MAX_USHORT);
|
|
source = string(sourceStr->str_data, sourceStr->str_length);
|
|
|
|
value.assign(dsqlScratch->getBlrData());
|
|
|
|
return input->nod_type == Dsql::nod_not_null;
|
|
}
|
|
|
|
// Make a constraint object from a legacy node.
|
|
void RelationNode::makeConstraint(thread_db* /*tdbb*/, DsqlCompilerScratch* dsqlScratch,
|
|
jrd_tra* transaction, const dsql_nod* node, ObjectsArray<Constraint>& constraints, bool* notNull)
|
|
{
|
|
const dsql_str* string = (dsql_str*) node->nod_arg[Dsql::e_rct_name];
|
|
dsql_nod* constraintNode = node->nod_arg[Dsql::e_rct_type];
|
|
|
|
switch (constraintNode->nod_type)
|
|
{
|
|
case Dsql::nod_not_null:
|
|
case Dsql::nod_primary:
|
|
if (notNull && !*notNull)
|
|
{
|
|
*notNull = true;
|
|
|
|
Constraint& constraint = constraints.add();
|
|
constraint.type = Constraint::TYPE_NOT_NULL;
|
|
if (string && constraintNode->nod_type == Dsql::nod_not_null)
|
|
constraint.name = string->str_data;
|
|
}
|
|
|
|
if (constraintNode->nod_type == Dsql::nod_not_null)
|
|
break;
|
|
// nod_primary falls into
|
|
|
|
case Dsql::nod_unique:
|
|
{
|
|
Constraint& constraint = constraints.add();
|
|
constraint.type = constraintNode->nod_type == Dsql::nod_primary ?
|
|
Constraint::TYPE_PK : Constraint::TYPE_UNIQUE;
|
|
if (string)
|
|
constraint.name = string->str_data;
|
|
|
|
const dsql_nod* index = constraintNode->nod_arg[Dsql::e_pri_index];
|
|
fb_assert(index);
|
|
|
|
string = (dsql_str*) index->nod_arg[Dsql::e_idx_name];
|
|
constraint.indexName = (string ? string->str_data : constraint.name.c_str());
|
|
|
|
if (index->nod_arg[Dsql::e_idx_asc_dsc])
|
|
constraint.descending = true;
|
|
|
|
const dsql_nod* columns = constraintNode->nod_arg[Dsql::e_pri_columns];
|
|
if (columns)
|
|
{
|
|
const dsql_nod* const* ptr = columns->nod_arg;
|
|
|
|
for (const dsql_nod* const* const end = ptr + columns->nod_count; ptr < end; ++ptr)
|
|
{
|
|
const dsql_str* fieldName = (dsql_str*) (*ptr)->nod_arg[Dsql::e_fln_name];
|
|
constraint.columns.add(fieldName->str_data);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case Dsql::nod_foreign:
|
|
{
|
|
Constraint& constraint = constraints.add();
|
|
constraint.type = Constraint::TYPE_FK;
|
|
if (string)
|
|
constraint.name = string->str_data;
|
|
|
|
dsql_nod* columns = constraintNode->nod_arg[Dsql::e_for_columns];
|
|
dsql_nod* refRelationNode = constraintNode->nod_arg[Dsql::e_for_reftable];
|
|
|
|
const dsql_str* refRelation = (dsql_str*) refRelationNode->nod_arg[Dsql::e_rln_name];
|
|
constraint.refRelation = refRelation->str_data;
|
|
|
|
// If there is a referenced table name but no referenced field names, the
|
|
// primary key of the referenced table designates the referenced fields.
|
|
dsql_nod* refColumns = constraintNode->nod_arg[Dsql::e_for_refcolumns];
|
|
if (!refColumns)
|
|
{
|
|
refColumns = METD_get_primary_key(transaction, refRelation);
|
|
|
|
// 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)
|
|
{
|
|
// "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));
|
|
}
|
|
}
|
|
|
|
if (refColumns && columns->nod_count != refColumns->nod_count)
|
|
{
|
|
// 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.
|
|
|
|
const dsql_nod* index = constraintNode->nod_arg[Dsql::e_for_index];
|
|
fb_assert(index);
|
|
|
|
string = (dsql_str*) index->nod_arg[Dsql::e_idx_name];
|
|
constraint.indexName = (string ? string->str_data : constraint.name.c_str());
|
|
|
|
if (index->nod_arg[Dsql::e_idx_asc_dsc])
|
|
constraint.descending = true;
|
|
|
|
if (columns)
|
|
{
|
|
const dsql_nod* const* ptr = columns->nod_arg;
|
|
|
|
for (const dsql_nod* const* const end = ptr + columns->nod_count; ptr < end; ++ptr)
|
|
{
|
|
const dsql_str* fieldName = (dsql_str*) (*ptr)->nod_arg[Dsql::e_fln_name];
|
|
constraint.columns.add(fieldName->str_data);
|
|
}
|
|
}
|
|
|
|
if (refColumns)
|
|
{
|
|
const dsql_nod* const* ptr = refColumns->nod_arg;
|
|
|
|
for (const dsql_nod* const* const end = ptr + refColumns->nod_count; ptr < end; ++ptr)
|
|
{
|
|
const dsql_str* fieldName = (dsql_str*) (*ptr)->nod_arg[Dsql::e_fln_name];
|
|
constraint.refColumns.add(fieldName->str_data);
|
|
}
|
|
}
|
|
|
|
if (constraintNode->nod_arg[Dsql::e_for_action])
|
|
{
|
|
dsql_nod* forActionNode = constraintNode->nod_arg[Dsql::e_for_action];
|
|
fb_assert(forActionNode->nod_type == Dsql::nod_ref_upd_del);
|
|
|
|
dsql_nod* refUpdActionNode = forActionNode->nod_arg[Dsql::e_ref_upd];
|
|
if (refUpdActionNode)
|
|
{
|
|
fb_assert(refUpdActionNode->nod_type == Dsql::nod_ref_trig_action);
|
|
|
|
switch (refUpdActionNode->nod_flags)
|
|
{
|
|
case REF_ACTION_CASCADE:
|
|
constraint.refUpdateAction = RI_ACTION_CASCADE;
|
|
defineUpdateCascadeTrigger(dsqlScratch, constraint);
|
|
break;
|
|
|
|
case REF_ACTION_SET_DEFAULT:
|
|
constraint.refUpdateAction = RI_ACTION_DEFAULT;
|
|
defineSetDefaultTrigger(dsqlScratch, constraint, true);
|
|
break;
|
|
|
|
case REF_ACTION_SET_NULL:
|
|
constraint.refUpdateAction = RI_ACTION_NULL;
|
|
defineSetNullTrigger(dsqlScratch, constraint, true);
|
|
break;
|
|
|
|
default:
|
|
fb_assert(0);
|
|
// fall into
|
|
|
|
case REF_ACTION_NONE:
|
|
constraint.refUpdateAction = RI_ACTION_NONE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
dsql_nod* refDelActionNode = forActionNode->nod_arg[Dsql::e_ref_del];
|
|
if (refDelActionNode)
|
|
{
|
|
fb_assert(refDelActionNode->nod_type == Dsql::nod_ref_trig_action);
|
|
|
|
switch (refDelActionNode->nod_flags)
|
|
{
|
|
case REF_ACTION_CASCADE:
|
|
constraint.refDeleteAction = RI_ACTION_CASCADE;
|
|
defineDeleteCascadeTrigger(dsqlScratch, constraint);
|
|
break;
|
|
|
|
case REF_ACTION_SET_DEFAULT:
|
|
constraint.refDeleteAction = RI_ACTION_DEFAULT;
|
|
defineSetDefaultTrigger(dsqlScratch, constraint, false);
|
|
break;
|
|
|
|
case REF_ACTION_SET_NULL:
|
|
constraint.refDeleteAction = RI_ACTION_NULL;
|
|
defineSetNullTrigger(dsqlScratch, constraint, false);
|
|
break;
|
|
|
|
default:
|
|
fb_assert(0);
|
|
// fall into
|
|
|
|
case REF_ACTION_NONE:
|
|
constraint.refDeleteAction = RI_ACTION_NONE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case Dsql::nod_def_constraint:
|
|
{
|
|
Constraint& constraint = constraints.add();
|
|
constraint.type = Constraint::TYPE_CHECK;
|
|
if (string)
|
|
constraint.name = string->str_data;
|
|
|
|
defineCheckConstraint(dsqlScratch, constraint, constraintNode);
|
|
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, NULL, 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.descending)
|
|
definition.descending = true;
|
|
definition.columns = constraint.columns;
|
|
definition.refRelation = constraint.refRelation;
|
|
definition.refColumns = constraint.refColumns;
|
|
|
|
CreateIndexNode::store(tdbb, transaction, constraint.indexName, definition,
|
|
&referredIndexName);
|
|
|
|
CRT.RDB$INDEX_NAME.NULL = FALSE;
|
|
strcpy(CRT.RDB$INDEX_NAME, constraint.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.indexName.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::Gds(ENCODE_ISC_MSG(123, DYN_MSG_FAC)) << 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.indexName.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::Gds(ENCODE_ISC_MSG(124, DYN_MSG_FAC)) << 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::Gds(ENCODE_ISC_MSG(126, DYN_MSG_FAC)));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Generate triggers to implement the CHECK clause, either at the field or table level.
|
|
void RelationNode::defineCheckConstraint(DsqlCompilerScratch* dsqlScratch, Constraint& constraint,
|
|
dsql_nod* node)
|
|
{
|
|
// Create the INSERT trigger.
|
|
defineCheckConstraintTrigger(dsqlScratch, constraint, node, PRE_STORE_TRIGGER);
|
|
|
|
// Create the UPDATE trigger.
|
|
defineCheckConstraintTrigger(dsqlScratch, constraint, node, PRE_MODIFY_TRIGGER);
|
|
}
|
|
|
|
// Define a check constraint trigger.
|
|
void RelationNode::defineCheckConstraintTrigger(DsqlCompilerScratch* dsqlScratch,
|
|
Constraint& constraint, dsql_nod* node, 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.
|
|
dsql_nod* actionNode = MAKE_node(Dsql::nod_list, 1);
|
|
actionNode->nod_arg[0] = MAKE_node(Dsql::nod_gdscode, 1);
|
|
actionNode->nod_arg[0]->nod_arg[0] = (dsql_nod*) MAKE_cstring("check_constraint");
|
|
|
|
// 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.
|
|
|
|
DDL_reset_context_stack(dsqlScratch);
|
|
|
|
// 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->nod_arg[Dsql::e_rln_alias] = (dsql_nod*) MAKE_cstring(OLD_CONTEXT);
|
|
dsql_ctx* oldContext = PASS1_make_context(dsqlScratch, dsqlNode);
|
|
oldContext->ctx_flags |= CTX_system;
|
|
|
|
dsqlNode->nod_arg[Dsql::e_rln_alias] = (dsql_nod*) MAKE_cstring(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, node->nod_arg[Dsql::e_cnstr_condition]);
|
|
|
|
dsql_nod* condition = MAKE_node(Dsql::nod_class_exprnode, 1);
|
|
condition->nod_arg[0] = reinterpret_cast<dsql_nod*>(notNode);
|
|
condition = PASS1_node(dsqlScratch, condition);
|
|
|
|
GEN_hidden_variables(dsqlScratch, false);
|
|
dsqlScratch->appendUChar(blr_if);
|
|
GEN_expr(dsqlScratch, condition);
|
|
|
|
// Generate the action statement for the trigger.
|
|
GEN_statement(dsqlScratch, PASS1_statement(dsqlScratch, actionNode));
|
|
|
|
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
|
|
|
|
DDL_reset_context_stack(dsqlScratch);
|
|
|
|
// 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 = ((dsql_str*) node->nod_arg[Dsql::e_cnstr_source])->str_data;
|
|
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 (const dsql_nod* const* ptr = elements.begin(); ptr != elements.end(); ++ptr)
|
|
{
|
|
const dsql_nod* elem = *ptr;
|
|
|
|
if (elem->nod_type != Dsql::nod_def_field)
|
|
continue;
|
|
|
|
const dsql_fld* field = (dsql_fld*) elem->nod_arg[Dsql::e_dfl_field];
|
|
if (*column != field->fld_name)
|
|
continue;
|
|
|
|
// Now we have the right column in the parse tree. case (1) above
|
|
|
|
dsql_nod* default_node = elem->nod_arg[Dsql::e_dfl_default];
|
|
if (default_node)
|
|
{
|
|
// case (1-a) above: There is a column level default
|
|
fb_assert(default_node->nod_type == Dsql::nod_def_default);
|
|
|
|
dsqlScratch->getBlrData().clear();
|
|
dsqlScratch->getDebugData().clear();
|
|
|
|
GEN_hidden_variables(dsqlScratch, true);
|
|
GEN_expr(dsqlScratch, default_node->nod_arg[Dsql::e_dft_default]);
|
|
|
|
foundDefault = true;
|
|
searchForDefault = false;
|
|
|
|
// Move the blr to the constraint blrWriter.
|
|
blrWriter.getBlrData().join(dsqlScratch->getBlrData());
|
|
}
|
|
else
|
|
{
|
|
const TEXT* domainName;
|
|
const dsql_str* domainNameStr;
|
|
const dsql_nod* tmpNode;
|
|
|
|
const dsql_nod* domainNode = elem->nod_arg[Dsql::e_dfl_domain];
|
|
if (!domainNode ||
|
|
!(tmpNode = domainNode->nod_arg[Dsql::e_dom_name]) ||
|
|
!(domainNameStr = (dsql_str*) tmpNode->nod_arg[Dsql::e_fln_name]) ||
|
|
!(domainName = domainNameStr->str_data))
|
|
{
|
|
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(),
|
|
domainName, &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,
|
|
BlrWriter& 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, BlrWriter& 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, BlrWriter& 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, BlrWriter& 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, Array<dsql_nod*>& /*nodes*/) 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, false))
|
|
status_exception::raise(Arg::Gds(ENCODE_ISC_MSG(163, DYN_MSG_FAC)));
|
|
|
|
// 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);
|
|
|
|
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);
|
|
|
|
ObjectsArray<Constraint> constraints;
|
|
const dsql_nod* pkCols = findPkColumns();
|
|
SSHORT position = 0;
|
|
|
|
for (const dsql_nod* const* i = elements.begin(); i != elements.end(); ++i)
|
|
{
|
|
const dsql_nod* element = *i;
|
|
|
|
switch (element->nod_type)
|
|
{
|
|
case Dsql::nod_def_field:
|
|
defineField(tdbb, dsqlScratch, transaction, element, position, pkCols);
|
|
++position;
|
|
break;
|
|
|
|
case Dsql::nod_rel_constraint:
|
|
makeConstraint(tdbb, dsqlScratch, transaction, element, 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 dsql_nod* CreateRelationNode::findPkColumns()
|
|
{
|
|
for (const dsql_nod* const* i = elements.begin(); i != elements.end(); ++i)
|
|
{
|
|
const dsql_nod* element = *i;
|
|
|
|
if (element->nod_type == Dsql::nod_rel_constraint)
|
|
{
|
|
const dsql_nod* node = element->nod_arg[Dsql::e_rct_type];
|
|
if (node->nod_type == Dsql::nod_primary)
|
|
return node->nod_arg[Dsql::e_pri_columns];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//----------------------
|
|
|
|
|
|
void AlterRelationNode::print(string& text, Array<dsql_nod*>& /*nodes*/) 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)
|
|
{
|
|
TEXT linecol[64];
|
|
sprintf(linecol, "At line %d, column %d.",
|
|
(int) dsqlNode->nod_line, (int) dsqlNode->nod_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 (const dsql_nod* const* i = elements.begin(); i != elements.end(); ++i)
|
|
{
|
|
const dsql_nod* element = *i;
|
|
|
|
switch (element->nod_type)
|
|
{
|
|
case Dsql::nod_def_field:
|
|
defineField(tdbb, dsqlScratch, transaction, element, -1, NULL);
|
|
break;
|
|
|
|
case Dsql::nod_mod_field_type:
|
|
modifyField(tdbb, dsqlScratch, transaction, element);
|
|
break;
|
|
|
|
case Dsql::nod_mod_field_name:
|
|
{
|
|
const dsql_nod* oldField = element->nod_arg[e_mod_fld_name_orig_name];
|
|
MetaName oldFieldName = ((dsql_str*) oldField->nod_arg[e_fln_name])->str_data;
|
|
const dsql_nod* newField = element->nod_arg[e_mod_fld_name_new_name];
|
|
MetaName newFieldName = ((dsql_str*) newField->nod_arg[e_fln_name])->str_data;
|
|
|
|
AutoRequest request;
|
|
bool found = false;
|
|
|
|
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
|
|
RFL IN RDB$RELATION_FIELDS
|
|
WITH RFL.RDB$FIELD_NAME EQ oldFieldName.c_str() AND
|
|
RFL.RDB$RELATION_NAME EQ name.c_str()
|
|
{
|
|
found = true;
|
|
|
|
MODIFY RFL
|
|
checkViewDependency(tdbb, transaction, name, oldFieldName);
|
|
checkSpTrigDependency(tdbb, transaction, name, oldFieldName);
|
|
|
|
if (!fieldExists(tdbb, transaction, name, newFieldName))
|
|
{
|
|
strcpy(RFL.RDB$FIELD_NAME, newFieldName.c_str());
|
|
AlterDomainNode::modifyLocalFieldIndex(tdbb, transaction, name,
|
|
oldFieldName, newFieldName);
|
|
}
|
|
else
|
|
{
|
|
// msg 205: Cannot rename field %s to %s. A field with that name
|
|
// already exists in table %s.
|
|
status_exception::raise(
|
|
Arg::Gds(ENCODE_ISC_MSG(205, DYN_MSG_FAC)) <<
|
|
oldFieldName << newFieldName << name);
|
|
}
|
|
END_MODIFY
|
|
}
|
|
END_FOR
|
|
|
|
if (!found)
|
|
{
|
|
// msg 176: "column %s does not exist in table/view %s"
|
|
status_exception::raise(
|
|
Arg::Gds(ENCODE_ISC_MSG(176, DYN_MSG_FAC)) << oldFieldName << name);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case Dsql::nod_mod_field_null_flag:
|
|
{
|
|
//// 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).
|
|
|
|
const dsql_nod* fieldNode = element->nod_arg[e_mod_fld_null_flag_field];
|
|
MetaName fieldName = ((dsql_str*) fieldNode->nod_arg[e_fln_name])->str_data;
|
|
bool flag = ExprNode::as<LiteralNode>(
|
|
element->nod_arg[e_mod_fld_null_flag_value])->getSlong() != 0;
|
|
|
|
AutoRequest request;
|
|
bool found = false;
|
|
|
|
FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
|
|
RFL IN RDB$RELATION_FIELDS
|
|
WITH RFL.RDB$FIELD_NAME EQ fieldName.c_str() AND
|
|
RFL.RDB$RELATION_NAME EQ name.c_str()
|
|
{
|
|
found = true;
|
|
|
|
MODIFY RFL
|
|
if (!flag && !RFL.RDB$GENERATOR_NAME.NULL)
|
|
{
|
|
// msg 274: Identity column @1 of table @2 cannot be changed to NULLable
|
|
status_exception::raise(
|
|
Arg::Gds(ENCODE_ISC_MSG(274, DYN_MSG_FAC)) << fieldName << name);
|
|
}
|
|
|
|
RFL.RDB$NULL_FLAG = SSHORT(flag);
|
|
END_MODIFY
|
|
}
|
|
END_FOR
|
|
|
|
if (!found)
|
|
{
|
|
// msg 176: "column %s does not exist in table/view %s"
|
|
status_exception::raise(
|
|
Arg::Gds(ENCODE_ISC_MSG(176, DYN_MSG_FAC)) << fieldName << name);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case Dsql::nod_mod_field_pos:
|
|
{
|
|
const dsql_nod* fieldNode = element->nod_arg[e_mod_fld_pos_orig_name];
|
|
MetaName fieldName = ((dsql_str*) fieldNode->nod_arg[e_fln_name])->str_data;
|
|
const dsql_nod* posNode = element->nod_arg[e_mod_fld_pos_new_position];
|
|
|
|
// CVC: Since now the parser accepts pos=1..N, let's subtract one here.
|
|
const SSHORT pos = (SSHORT) ExprNode::as<LiteralNode>(posNode)->getSlong() - 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 fieldName.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::Gds(ENCODE_ISC_MSG(176, DYN_MSG_FAC)) << fieldName << name);
|
|
}
|
|
|
|
if (pos != oldPos)
|
|
modifyLocalFieldPosition(tdbb, transaction, name, fieldName, pos, oldPos);
|
|
|
|
break;
|
|
}
|
|
|
|
case Dsql::nod_del_field:
|
|
{
|
|
// 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 dsql_nod* fieldNode = element->nod_arg[0];
|
|
MetaName fieldName = ((dsql_str*) fieldNode->nod_arg[e_fln_name])->str_data;
|
|
|
|
if ((element->nod_arg[1])->nod_type == nod_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));
|
|
}
|
|
|
|
fb_assert((element->nod_arg[1])->nod_type == nod_restrict);
|
|
deleteLocalField(tdbb, transaction, name, fieldName);
|
|
break;
|
|
}
|
|
|
|
case Dsql::nod_rel_constraint:
|
|
makeConstraint(tdbb, dsqlScratch, transaction, element, constraints);
|
|
break;
|
|
|
|
case Dsql::nod_delete_rel_constraint:
|
|
{
|
|
MetaName constraintName = ((dsql_str*) element->nod_arg[0])->str_data;
|
|
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::Gds(ENCODE_ISC_MSG(130, DYN_MSG_FAC)) << 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, const dsql_nod* element)
|
|
{
|
|
Attachment* attachment = transaction->tra_attachment;
|
|
|
|
dsql_fld* field = (dsql_fld*) element->nod_arg[Dsql::e_mod_fld_type_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
|
|
{
|
|
dsql_nod* domainNode = element->nod_arg[Dsql::e_mod_fld_type_dom_name];
|
|
dsql_nod* computedNode = element->nod_arg[Dsql::e_mod_fld_type_computed];
|
|
dsql_nod* defaultNode = element->nod_arg[Dsql::e_mod_fld_type_default];
|
|
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 != (computedNode != NULL)))
|
|
{
|
|
// Cannot add or remove COMPUTED from column @1
|
|
status_exception::raise(
|
|
Arg::Gds(ENCODE_ISC_MSG(249, DYN_MSG_FAC)) << 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;
|
|
BlrWriter::BlrData computedValue;
|
|
|
|
if (computedNode)
|
|
defineComputed(tdbb, dsqlScratch, field, computedNode, computedSource, computedValue);
|
|
|
|
if (defaultNode)
|
|
{
|
|
MODIFY RFR
|
|
if (defaultNode->nod_type == nod_def_default)
|
|
{
|
|
if (!RFR.RDB$GENERATOR_NAME.NULL)
|
|
{
|
|
// msg 275: Identity column @1 of table @2 cannot have default value
|
|
status_exception::raise(
|
|
Arg::Gds(ENCODE_ISC_MSG(275, DYN_MSG_FAC)) << field->fld_name << name);
|
|
}
|
|
|
|
if (hasDimensions)
|
|
{
|
|
// msg 225: "Default value is not allowed for array type in field %s"
|
|
status_exception::raise(
|
|
Arg::Gds(ENCODE_ISC_MSG(225, DYN_MSG_FAC)) << field->fld_name);
|
|
}
|
|
|
|
if (computedNode)
|
|
{
|
|
// msg 233: "Local column %s is computed, cannot set a default value"
|
|
status_exception::raise(
|
|
Arg::Gds(ENCODE_ISC_MSG(233, DYN_MSG_FAC)) << field->fld_name);
|
|
}
|
|
|
|
string defaultSource;
|
|
BlrWriter::BlrData defaultValue;
|
|
|
|
defineDefault(tdbb, dsqlScratch, field, defaultNode, 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);
|
|
}
|
|
else if (defaultNode->nod_type == nod_del_default)
|
|
{
|
|
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::Gds(ENCODE_ISC_MSG(229, DYN_MSG_FAC)) << field->fld_name);
|
|
}
|
|
else
|
|
{
|
|
// msg 230: "Local column %s default belongs to domain %s"
|
|
status_exception::raise(
|
|
Arg::Gds(ENCODE_ISC_MSG(230, DYN_MSG_FAC)) <<
|
|
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 (domainNode)
|
|
{
|
|
// Case a1: Internal domain -> domain.
|
|
// Case a2: Domain -> domain.
|
|
|
|
const dsql_nod* node1 = domainNode->nod_arg[Dsql::e_dom_name];
|
|
newDomainName = ((dsql_str*) node1->nod_arg[Dsql::e_fln_name])->str_data;
|
|
|
|
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::Gds(ENCODE_ISC_MSG(224, DYN_MSG_FAC)) <<
|
|
newDomainName << field->fld_name);
|
|
}
|
|
|
|
// Get the domain information.
|
|
if (!METD_get_domain(dsqlScratch->getTransaction(), field, newDomainName.c_str()))
|
|
{
|
|
// 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.
|
|
|
|
TypeClause type(*tdbb->getDefaultPool(), field, NULL);
|
|
|
|
// If COMPUTED was specified but the type wasn't, we use the type of
|
|
// the computed expression.
|
|
if (computedNode && field->fld_dtype == dtype_unknown)
|
|
{
|
|
dsc desc;
|
|
MAKE_desc(dsqlScratch, &desc, computedNode);
|
|
|
|
field->fld_dtype = desc.dsc_dtype;
|
|
field->fld_length = desc.dsc_length;
|
|
field->fld_scale = desc.dsc_scale;
|
|
|
|
if (field->fld_dtype <= dtype_any_text)
|
|
{
|
|
field->fld_character_set_id = DSC_GET_CHARSET(&desc);
|
|
field->fld_collation_id = DSC_GET_COLLATE(&desc);
|
|
}
|
|
else
|
|
field->fld_sub_type = desc.dsc_sub_type;
|
|
}
|
|
|
|
type.resolve(dsqlScratch, true);
|
|
|
|
if (wasInternalDomain) // Case b1: Internal domain -> internal domain.
|
|
{
|
|
MODIFY FLD
|
|
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);
|
|
END_MODIFY
|
|
|
|
newDom.dyn_fld_source = origDom.dyn_fld_source;
|
|
}
|
|
else // Case b2: Domain -> internal domain.
|
|
storeGlobalField(tdbb, transaction, newDomainName, type);
|
|
}
|
|
|
|
if (!computedNode && !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::Gds(ENCODE_ISC_MSG(273, DYN_MSG_FAC)) << 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 (computedNode)
|
|
{
|
|
RFR.RDB$UPDATE_FLAG.NULL = FALSE;
|
|
RFR.RDB$UPDATE_FLAG = 1;
|
|
}
|
|
|
|
RFR.RDB$COLLATION_ID.NULL = TRUE; // CORE-2426
|
|
END_MODIFY
|
|
}
|
|
}
|
|
|
|
if (computedNode)
|
|
{
|
|
// 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
|
|
|
|
//// FIXME: Check usage in functions.
|
|
}
|
|
END_FOR
|
|
|
|
if (!found)
|
|
{
|
|
// msg 176: "column %s does not exist in table/view %s"
|
|
status_exception::raise(
|
|
Arg::Gds(ENCODE_ISC_MSG(176, DYN_MSG_FAC)) << 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 PRC IN RDB$PROCEDURE_PARAMETERS WITH
|
|
PRC.RDB$FIELD_SOURCE EQ FLD.RDB$FIELD_NAME)
|
|
{
|
|
DropDomainNode::deleteDimensionRecords(tdbb, transaction, globalName);
|
|
ERASE FLD;
|
|
}
|
|
END_FOR
|
|
}
|
|
|
|
void DropRelationNode::print(string& text, Array<dsql_nod*>& /*nodes*/) const
|
|
{
|
|
text.printf(
|
|
"DropRelationNode\n"
|
|
" name: '%s'\n",
|
|
name.c_str());
|
|
}
|
|
|
|
void DropRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
|
|
jrd_tra* transaction)
|
|
{
|
|
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::deleteGenerator(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::Gds(ENCODE_ISC_MSG(61, DYN_MSG_FAC)));
|
|
}
|
|
|
|
// 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::Gds(ENCODE_ISC_MSG(61, DYN_MSG_FAC)));
|
|
}
|
|
|
|
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, Array<dsql_nod*>& /*nodes*/) const
|
|
{
|
|
text.printf(
|
|
"CreateAlterViewNode\n"
|
|
" name: '%s'\n",
|
|
name.c_str());
|
|
}
|
|
|
|
void CreateAlterViewNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
|
|
jrd_tra* transaction)
|
|
{
|
|
Attachment* attachment = transaction->tra_attachment;
|
|
|
|
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.
|
|
|
|
DDL_reset_context_stack(dsqlScratch);
|
|
++dsqlScratch->contextNumber;
|
|
selectExpr->nod_flags |= NOD_SELECT_EXPR_VIEW_FIELDS;
|
|
dsql_nod* rse = PASS1_rse(dsqlScratch, selectExpr, NULL);
|
|
|
|
dsqlScratch->getBlrData().clear();
|
|
dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5);
|
|
|
|
// ASF: Call GEN_hidden_variables could be a optimization for views to not have
|
|
// blr_dcl_variables inside RSE loops, but this is currently not possible because it will
|
|
// mix the variables from view fields and view body.
|
|
|
|
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);
|
|
}
|
|
|
|
// 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 ? context->ctx_alias : 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 (attachment->att_user->usr_user_name != 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 dsql_nod* const* ptr = NULL;
|
|
const dsql_nod* const* end = NULL;
|
|
|
|
if (viewFields)
|
|
{
|
|
ptr = viewFields->nod_arg;
|
|
end = ptr + viewFields->nod_count;
|
|
}
|
|
|
|
// 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.
|
|
|
|
dsql_nod* items = ExprNode::as<RseNode>(rse)->dsqlSelectList;
|
|
dsql_nod** itemsPtr = items->nod_arg;
|
|
SortedArray<dsql_fld*> modifiedFields;
|
|
bool updatable = true;
|
|
SSHORT position = 0;
|
|
|
|
for (const dsql_nod* const* const itemsEnd = itemsPtr + items->nod_count;
|
|
itemsPtr < itemsEnd; ++itemsPtr, ++position)
|
|
{
|
|
dsql_nod* fieldNode = *itemsPtr;
|
|
|
|
// Determine the proper field name, replacing the default if necessary.
|
|
|
|
const dsql_nod* nameNode = fieldNode;
|
|
const char* aliasName = NULL;
|
|
|
|
while (nameNode->nod_type == Dsql::nod_alias ||
|
|
ExprNode::is<DerivedFieldNode>(nameNode) ||
|
|
ExprNode::is<DsqlMapNode>(nameNode))
|
|
{
|
|
switch (nameNode->nod_type)
|
|
{
|
|
case Dsql::nod_alias:
|
|
if (!aliasName)
|
|
aliasName = ((dsql_str*) nameNode->nod_arg[Dsql::e_alias_alias])->str_data;
|
|
nameNode = nameNode->nod_arg[Dsql::e_alias_value];
|
|
break;
|
|
|
|
case Dsql::nod_class_exprnode:
|
|
{
|
|
const DsqlMapNode* mapNode;
|
|
const DerivedFieldNode* derivedField;
|
|
|
|
if ((mapNode = ExprNode::as<DsqlMapNode>(nameNode)))
|
|
nameNode = mapNode->map->map_node;
|
|
else if ((derivedField = ExprNode::as<DerivedFieldNode>(nameNode)))
|
|
{
|
|
if (!aliasName)
|
|
aliasName = derivedField->name.c_str();
|
|
nameNode = derivedField->dsqlValue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
const dsql_fld* nameField = NULL;
|
|
const FieldNode* fieldNameNode = ExprNode::as<FieldNode>(nameNode);
|
|
|
|
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.
|
|
|
|
if (fieldNode->nod_type == Dsql::nod_alias)
|
|
fieldNode = fieldNode->nod_arg[Dsql::e_alias_value];
|
|
|
|
dsql_fld* field = NULL;
|
|
const dsql_ctx* context = NULL;
|
|
|
|
fieldNameNode = ExprNode::as<FieldNode>(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)
|
|
{
|
|
const dsql_str* fieldName = (dsql_str*) (*ptr)->nod_arg[1];
|
|
fieldStr = fieldName->str_data;
|
|
}
|
|
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)
|
|
{
|
|
TypeClause fieldType(*tdbb->getDefaultPool(), field, NULL);
|
|
fieldType.resolve(dsqlScratch);
|
|
|
|
fieldDefinition.viewContext = context->ctx_context;
|
|
fieldDefinition.baseField = field->fld_name;
|
|
|
|
if (field->fld_dtype <= dtype_any_text)
|
|
fieldDefinition.collationId = field->fld_collation_id;
|
|
|
|
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.fld_dtype = desc.dsc_dtype;
|
|
newField.fld_length = desc.dsc_length;
|
|
newField.fld_scale = desc.dsc_scale;
|
|
|
|
if (desc.isText() || (desc.isBlob() && desc.getBlobSubType() == isc_blob_text))
|
|
{
|
|
newField.fld_character_set_id = desc.getCharSet();
|
|
newField.fld_collation_id = desc.getTextType();
|
|
}
|
|
|
|
if (desc.isText())
|
|
{
|
|
newField.fld_character_length = newField.fld_length;
|
|
newField.fld_length *= METD_get_charset_bpc(
|
|
dsqlScratch->getTransaction(), newField.fld_character_set_id);
|
|
}
|
|
else
|
|
newField.fld_sub_type = desc.dsc_sub_type;
|
|
|
|
TypeClause fieldType(*tdbb->getDefaultPool(), &newField, NULL);
|
|
fieldType.setup(dsqlScratch);
|
|
|
|
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(fieldType,
|
|
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, fieldType,
|
|
"", dsqlScratch->getBlrData());
|
|
}
|
|
|
|
fieldDefinition.modify(tdbb, transaction);
|
|
}
|
|
else
|
|
{
|
|
storeGlobalField(tdbb, transaction, fieldDefinition.fieldSource, fieldType,
|
|
"", 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));
|
|
}
|
|
|
|
dsql_nod* querySpecNod = selectExpr->nod_arg[Dsql::e_sel_query_spec];
|
|
|
|
if (querySpecNod->nod_type == Dsql::nod_list)
|
|
{
|
|
// 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));
|
|
}
|
|
|
|
RseNode* querySpec = ExprNode::as<RseNode>(querySpecNod);
|
|
fb_assert(querySpec);
|
|
|
|
if (querySpec->dsqlFrom->nod_count != 1)
|
|
{
|
|
// 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));
|
|
}
|
|
|
|
createCheckTriggers(tdbb, dsqlScratch, items);
|
|
}
|
|
|
|
DDL_reset_context_stack(dsqlScratch);
|
|
|
|
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 triggers to implement the WITH CHECK OPTION clause for a VIEW.
|
|
void CreateAlterViewNode::createCheckTriggers(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
|
|
dsql_nod* items)
|
|
{
|
|
MemoryPool& pool = *tdbb->getDefaultPool();
|
|
|
|
// Specify that the trigger should abort if the condition is not met.
|
|
dsql_nod* actionNode = MAKE_node(Dsql::nod_list, 1);
|
|
actionNode->nod_arg[0] = MAKE_node(Dsql::nod_gdscode, 1);
|
|
actionNode->nod_arg[0]->nod_arg[0] = (dsql_nod*) MAKE_cstring("check_constraint");
|
|
|
|
// Create the UPDATE trigger.
|
|
|
|
dsql_nod* baseAndNode = NULL;
|
|
dsql_nod* baseRelation = NULL;
|
|
defineUpdateAction(dsqlScratch, &baseAndNode, &baseRelation, items);
|
|
fb_assert(baseAndNode);
|
|
fb_assert(baseRelation);
|
|
|
|
RseNode* rse = FB_NEW(pool) RseNode(pool);
|
|
rse->dsqlWhere = baseAndNode;
|
|
rse->dsqlStreams = MAKE_node(Dsql::nod_list, 1);
|
|
rse->dsqlStreams->nod_arg[0] = baseRelation;
|
|
|
|
dsql_nod* rseNod = MAKE_node(Dsql::nod_class_exprnode, 1);
|
|
rseNod->nod_arg[0] = reinterpret_cast<dsql_nod*>(rse);
|
|
|
|
createCheckTrigger(tdbb, dsqlScratch, rseNod, items, actionNode, PRE_MODIFY_TRIGGER);
|
|
createCheckTrigger(tdbb, dsqlScratch, NULL, items, actionNode, PRE_STORE_TRIGGER);
|
|
}
|
|
|
|
// Define a trigger for a VIEW WITH CHECK OPTION.
|
|
void CreateAlterViewNode::createCheckTrigger(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
|
|
dsql_nod* rse, dsql_nod* items, dsql_nod* actions, TriggerType triggerType)
|
|
{
|
|
MemoryPool& pool = *tdbb->getDefaultPool();
|
|
|
|
AutoSetRestore<bool> autoCheckConstraintTrigger(&dsqlScratch->checkConstraintTrigger, true);
|
|
|
|
const dsql_nod* querySpecNod = selectExpr->nod_arg[Dsql::e_sel_query_spec];
|
|
dsql_nod* relationNode = dsqlNode;
|
|
|
|
// Generate the trigger blr.
|
|
|
|
dsqlScratch->getBlrData().clear();
|
|
dsqlScratch->getDebugData().clear();
|
|
dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5);
|
|
|
|
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.
|
|
|
|
dsql_ctx* savContext = NULL;
|
|
dsql_ctx* context = NULL;
|
|
|
|
if (dsqlScratch->contextNumber)
|
|
{
|
|
// If an alias is specified for the single base table involved,
|
|
// save and then add the context.
|
|
|
|
context = dsqlScratch->context->object();
|
|
|
|
if (context->ctx_alias)
|
|
{
|
|
savContext = FB_NEW(pool) dsql_ctx(pool);
|
|
*savContext = *context;
|
|
}
|
|
}
|
|
|
|
DDL_reset_context_stack(dsqlScratch);
|
|
|
|
dsql_nod* tempAlias = relationNode->nod_arg[Dsql::e_rln_alias];
|
|
|
|
relationNode->nod_arg[Dsql::e_rln_alias] = (dsql_nod*) MAKE_cstring(OLD_CONTEXT);
|
|
dsql_ctx* oldContext = PASS1_make_context(dsqlScratch, relationNode);
|
|
oldContext->ctx_flags |= CTX_system;
|
|
|
|
relationNode->nod_arg[Dsql::e_rln_alias] = (dsql_nod*) MAKE_cstring(NEW_CONTEXT);
|
|
dsql_ctx* newContext = PASS1_make_context(dsqlScratch, relationNode);
|
|
newContext->ctx_flags |= CTX_system;
|
|
|
|
relationNode->nod_arg[Dsql::e_rln_alias] = tempAlias;
|
|
|
|
if (savContext)
|
|
{
|
|
savContext->ctx_context = dsqlScratch->contextNumber++;
|
|
context->ctx_scope_level = dsqlScratch->scopeLevel;
|
|
dsqlScratch->context->push(savContext);
|
|
}
|
|
|
|
// Generate the condition for firing the trigger.
|
|
|
|
const RseNode* querySpec = ExprNode::as<RseNode>(querySpecNod);
|
|
fb_assert(querySpec);
|
|
|
|
dsql_nod* condition;
|
|
|
|
if (triggerType == PRE_MODIFY_TRIGGER)
|
|
{
|
|
dsqlScratch->appendUChar(blr_for);
|
|
|
|
RseNode* rseNode = ExprNode::as<RseNode>(rse);
|
|
|
|
rseNode->dsqlStreams->nod_arg[0] = PASS1_node(dsqlScratch, rseNode->dsqlStreams->nod_arg[0]);
|
|
rseNode->dsqlWhere = PASS1_node(dsqlScratch, rseNode->dsqlWhere);
|
|
|
|
GEN_expr(dsqlScratch, rse);
|
|
|
|
condition = replaceFieldNames(querySpec->dsqlWhere, items, viewFields, false, NEW_CONTEXT);
|
|
}
|
|
else if (triggerType == PRE_STORE_TRIGGER)
|
|
condition = replaceFieldNames(querySpec->dsqlWhere, items, viewFields, true, NEW_CONTEXT);
|
|
else
|
|
{
|
|
fb_assert(false);
|
|
}
|
|
|
|
dsqlScratch->appendUChar(blr_if);
|
|
GEN_expr(dsqlScratch, PASS1_node(dsqlScratch, condition));
|
|
dsqlScratch->appendUChar(blr_begin);
|
|
dsqlScratch->appendUChar(blr_end);
|
|
|
|
// Generate the action statements for the trigger.
|
|
|
|
dsql_nod** ptr = actions->nod_arg;
|
|
|
|
for (const dsql_nod* const* const end = ptr + actions->nod_count; ptr != end; ++ptr)
|
|
GEN_statement(dsqlScratch, PASS1_statement(dsqlScratch, *ptr));
|
|
|
|
dsqlScratch->appendUChar(blr_end); // of begin
|
|
dsqlScratch->appendUChar(blr_eoc);
|
|
|
|
DDL_reset_context_stack(dsqlScratch);
|
|
|
|
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());
|
|
}
|
|
|
|
// Define an action statement which, given a view definition, will map an update to a record from
|
|
// a view of a single relation into the base relation.
|
|
void CreateAlterViewNode::defineUpdateAction(DsqlCompilerScratch* dsqlScratch,
|
|
dsql_nod** baseAndNode, dsql_nod** baseRelation, dsql_nod* items)
|
|
{
|
|
thread_db* tdbb = JRD_get_thread_data();
|
|
MemoryPool& pool = *tdbb->getDefaultPool();
|
|
|
|
// Check whether this is an updatable view definition.
|
|
|
|
RseNode* querySpec = ExprNode::as<RseNode>(selectExpr->nod_arg[Dsql::e_sel_query_spec]);
|
|
dsql_nod* fromList = NULL;
|
|
|
|
if (!querySpec || !(fromList = querySpec->dsqlFrom) || fromList->nod_count != 1)
|
|
{
|
|
// The caller seems throwing proper errors for all the above conditions.
|
|
// But just in case it doesn't, here we have the final attempt to prevent the bad things.
|
|
fb_assert(false);
|
|
}
|
|
|
|
// Use the relation referenced in the select statement for rse.
|
|
|
|
dsql_nod* relationNode = MAKE_node(Dsql::nod_relation_name, (int) Dsql::e_rln_count);
|
|
relationNode->nod_arg[Dsql::e_rln_name] = fromList->nod_arg[0]->nod_arg[Dsql::e_rln_name];
|
|
relationNode->nod_arg[Dsql::e_rln_alias] = (dsql_nod*) MAKE_cstring(TEMP_CONTEXT);
|
|
*baseRelation = relationNode;
|
|
|
|
// Get the list of values and fields to compare to -- if there is no list of fields, get all
|
|
// fields in the base relation that are not computed.
|
|
|
|
dsql_nod* valuesNode = viewFields;
|
|
dsql_nod* fieldsNode = querySpec->dsqlSelectList;
|
|
|
|
if (!fieldsNode)
|
|
{
|
|
const dsql_rel* relation = METD_get_relation(dsqlScratch->getTransaction(),
|
|
dsqlScratch, name);
|
|
DsqlNodStack field_stack;
|
|
|
|
for (const dsql_fld* field = relation->rel_fields; field; field = field->fld_next)
|
|
{
|
|
if (!(field->fld_flags & FLD_computed))
|
|
field_stack.push(MAKE_field_name(field->fld_name.c_str()));
|
|
}
|
|
|
|
fieldsNode = MAKE_list(field_stack);
|
|
}
|
|
|
|
if (!valuesNode)
|
|
valuesNode = fieldsNode;
|
|
|
|
// Generate the list of assignments to fields in the base relation.
|
|
|
|
dsql_nod** ptr = fieldsNode->nod_arg;
|
|
const dsql_nod* const* const end = ptr + fieldsNode->nod_count;
|
|
dsql_nod** ptr2 = valuesNode->nod_arg;
|
|
const dsql_nod* const* const end2 = ptr2 + valuesNode->nod_count;
|
|
int andArg = 0;
|
|
|
|
BinaryBoolNode* andNode = FB_NEW(pool) BinaryBoolNode(pool, blr_and);
|
|
|
|
dsql_nod* andNod = MAKE_node(Dsql::nod_class_exprnode, 1);
|
|
andNod->nod_arg[0] = reinterpret_cast<dsql_nod*>(andNode);
|
|
|
|
for (; (ptr < end) && (ptr2 < end2); ptr++, ptr2++)
|
|
{
|
|
dsql_nod* fieldNode = *ptr;
|
|
if (fieldNode->nod_type == Dsql::nod_alias)
|
|
fieldNode = fieldNode->nod_arg[Dsql::e_alias_value];
|
|
|
|
// Generate the actual comparisons.
|
|
|
|
if (fieldNode->nod_type == Dsql::nod_field_name)
|
|
{
|
|
fieldNode->nod_arg[Dsql::e_fln_context] = (dsql_nod*) MAKE_cstring(TEMP_CONTEXT);
|
|
|
|
dsql_nod* oldValueNode = MAKE_node(Dsql::nod_field_name, (int) Dsql::e_fln_count);
|
|
oldValueNode->nod_arg[Dsql::e_fln_name] = (*ptr2)->nod_arg[Dsql::e_fln_name];
|
|
oldValueNode->nod_arg[Dsql::e_fln_context] = (dsql_nod*) MAKE_cstring(OLD_CONTEXT);
|
|
|
|
ComparativeBoolNode* eqlNode = FB_NEW(pool) ComparativeBoolNode(pool,
|
|
blr_eql, oldValueNode, fieldNode);
|
|
|
|
dsql_nod* eqlNod = MAKE_node(Dsql::nod_class_exprnode, 1);
|
|
eqlNod->nod_arg[0] = reinterpret_cast<dsql_nod*>(eqlNode);
|
|
|
|
BinaryBoolNode* andNode2 = FB_NEW(pool) BinaryBoolNode(
|
|
pool, blr_and, MAKE_node(Dsql::nod_class_exprnode, 1),
|
|
MAKE_node(Dsql::nod_class_exprnode, 1));
|
|
|
|
MissingBoolNode* missingNode = FB_NEW(pool) MissingBoolNode(
|
|
pool, oldValueNode);
|
|
andNode2->dsqlArg1->nod_arg[0] = reinterpret_cast<dsql_nod*>(missingNode);
|
|
|
|
missingNode = FB_NEW(pool) MissingBoolNode(pool, fieldNode);
|
|
andNode2->dsqlArg2->nod_arg[0] = reinterpret_cast<dsql_nod*>(missingNode);
|
|
|
|
ComparativeBoolNode* orNode = FB_NEW(pool) ComparativeBoolNode(
|
|
pool, blr_or, eqlNod, MAKE_node(Dsql::nod_class_exprnode, 1));
|
|
orNode->dsqlArg2->nod_arg[0] = reinterpret_cast<dsql_nod*>(andNode2);
|
|
|
|
dsql_nod* orNod = MAKE_node(Dsql::nod_class_exprnode, 1);
|
|
orNod->nod_arg[0] = reinterpret_cast<dsql_nod*>(orNode);
|
|
|
|
if (andArg == 0)
|
|
{
|
|
++andArg;
|
|
andNode->dsqlArg1 = orNod;
|
|
}
|
|
else if (andArg == 1)
|
|
{
|
|
++andArg;
|
|
andNode->dsqlArg2 = orNod;
|
|
}
|
|
else
|
|
{
|
|
dsql_nod* oldAnd = andNod;
|
|
|
|
andNode = FB_NEW(pool) BinaryBoolNode(pool, blr_and, oldAnd, orNod);
|
|
|
|
andNod = MAKE_node(Dsql::nod_class_exprnode, 1);
|
|
andNod->nod_arg[0] = reinterpret_cast<dsql_nod*>(andNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (andArg == 0)
|
|
andNode->dsqlArg1 = replaceFieldNames(querySpec->dsqlWhere, items, NULL, false, TEMP_CONTEXT);
|
|
else if (andArg == 1)
|
|
andNode->dsqlArg2 = replaceFieldNames(querySpec->dsqlWhere, items, NULL, false, TEMP_CONTEXT);
|
|
else
|
|
{
|
|
dsql_nod* oldAnd = andNod;
|
|
|
|
andNode = FB_NEW(pool) BinaryBoolNode(pool, blr_and, oldAnd,
|
|
replaceFieldNames(querySpec->dsqlWhere, items, NULL, false, TEMP_CONTEXT));
|
|
|
|
andNod = MAKE_node(Dsql::nod_class_exprnode, 1);
|
|
andNod->nod_arg[0] = reinterpret_cast<dsql_nod*>(andNode);
|
|
}
|
|
|
|
*baseAndNode = andNod;
|
|
}
|
|
|
|
// Given an input node tree, find any field name nodes and replace them according to the mapping
|
|
// provided. This is used to create view WITH CHECK OPTION.
|
|
dsql_nod* CreateAlterViewNode::replaceFieldNames(dsql_nod* input, dsql_nod* searchFields,
|
|
dsql_nod* replaceFields, bool nullThem, const char* contextName)
|
|
{
|
|
thread_db* tdbb = JRD_get_thread_data();
|
|
|
|
if (!input || input->getType() != dsql_type_nod)
|
|
return input;
|
|
|
|
Array<dsql_nod**> temp;
|
|
|
|
if (input->nod_type == nod_class_exprnode)
|
|
{
|
|
ExprNode* exprNode = reinterpret_cast<ExprNode*>(input->nod_arg[0]);
|
|
|
|
for (dsql_nod*** i = exprNode->dsqlChildNodes.begin();
|
|
i != exprNode->dsqlChildNodes.end(); ++i)
|
|
{
|
|
if (**i)
|
|
temp.add(*i);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (dsql_nod** ptr = input->nod_arg; ptr != input->nod_arg + input->nod_count; ++ptr)
|
|
temp.add(ptr);
|
|
}
|
|
|
|
dsql_nod*** endo = temp.end();
|
|
|
|
for (dsql_nod*** ptr = temp.begin(); ptr != temp.end(); ++ptr)
|
|
{
|
|
dsql_nod**& ptrNode = *ptr;
|
|
|
|
if (!*ptrNode)
|
|
continue;
|
|
|
|
if ((*ptrNode)->nod_type == Dsql::nod_select_expr)
|
|
{
|
|
// No subqueries 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_subquery_err));
|
|
}
|
|
|
|
if ((*ptrNode)->nod_type == Dsql::nod_field_name)
|
|
{
|
|
// Found a field node, check if it needs to be replaced.
|
|
|
|
const dsql_str* fieldName = (dsql_str*) (*ptrNode)->nod_arg[Dsql::e_fln_name];
|
|
dsql_nod** search = searchFields->nod_arg;
|
|
const dsql_nod* const* const end = search + searchFields->nod_count;
|
|
dsql_nod** replace = NULL;
|
|
|
|
if (replaceFields)
|
|
replace = replaceFields->nod_arg;
|
|
|
|
bool found = false;
|
|
|
|
for (; search < end; ++search, replace += (replaceFields ? 1 : 0))
|
|
{
|
|
const dsql_str* replaceName = NULL;
|
|
if (replaceFields)
|
|
replaceName = (dsql_str*) (*replace)->nod_arg[Dsql::e_fln_name];
|
|
|
|
const FieldNode* fieldNode = ExprNode::as<FieldNode>(*search);
|
|
const dsql_fld* field = fieldNode->dsqlField;
|
|
|
|
if (field->fld_name == fieldName->str_data)
|
|
{
|
|
found = true;
|
|
|
|
if (replaceFields)
|
|
(*ptrNode)->nod_arg[e_fln_name] = (*replace)->nod_arg[Dsql::e_fln_name];
|
|
|
|
(*ptrNode)->nod_arg[Dsql::e_fln_context] = (dsql_nod*) MAKE_cstring(contextName);
|
|
}
|
|
|
|
if (nullThem && replaceFields &&
|
|
strcmp(fieldName->str_data, replaceName->str_data) == 0)
|
|
{
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
if (nullThem && !found)
|
|
{
|
|
(*ptrNode) = MAKE_node(nod_class_exprnode, 1);
|
|
(*ptrNode)->nod_arg[0] = reinterpret_cast<dsql_nod*>(
|
|
FB_NEW(*tdbb->getDefaultPool()) NullNode(*tdbb->getDefaultPool()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Recursively go through the input tree looking for field name nodes.
|
|
replaceFieldNames(*ptrNode, searchFields, replaceFields, nullThem, contextName);
|
|
}
|
|
}
|
|
|
|
return input;
|
|
}
|
|
|
|
|
|
//----------------------
|
|
|
|
|
|
// 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::Gds(ENCODE_ISC_MSG(181, DYN_MSG_FAC)));
|
|
}
|
|
}
|
|
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::Gds(ENCODE_ISC_MSG(240, DYN_MSG_FAC)) <<
|
|
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::Gds(ENCODE_ISC_MSG(116, DYN_MSG_FAC)) << IDX.RDB$INDEX_NAME);
|
|
}
|
|
else if (!GF.RDB$DIMENSIONS.NULL)
|
|
{
|
|
// msg 117 "attempt to index array field in index %s"
|
|
status_exception::raise(
|
|
Arg::Gds(ENCODE_ISC_MSG(117, DYN_MSG_FAC)) << 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::Gds(ENCODE_ISC_MSG(179, DYN_MSG_FAC)) << 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 + STUFF_COUNT - 1) / (unsigned) STUFF_COUNT) *
|
|
(STUFF_COUNT + 1);
|
|
}
|
|
else
|
|
keyLength = length;
|
|
|
|
found = true;
|
|
}
|
|
END_FOR
|
|
|
|
if (!found)
|
|
{
|
|
// msg 120 "Unknown columns in index %s"
|
|
status_exception::raise(
|
|
Arg::Gds(ENCODE_ISC_MSG(120, DYN_MSG_FAC)) << 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::Gds(ENCODE_ISC_MSG(118, DYN_MSG_FAC)) << 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::Gds(ENCODE_ISC_MSG(119, DYN_MSG_FAC)) << 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::Gds(ENCODE_ISC_MSG(133, DYN_MSG_FAC)));
|
|
}
|
|
|
|
// 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::Gds(ENCODE_ISC_MSG(242, DYN_MSG_FAC)) << definition.refRelation);
|
|
}
|
|
|
|
if (found)
|
|
{
|
|
// msg 18: "could not find UNIQUE or PRIMARY KEY constraint in table %s with
|
|
// specified columns"
|
|
status_exception::raise(
|
|
Arg::Gds(ENCODE_ISC_MSG(18, DYN_MSG_FAC)) << definition.refRelation);
|
|
}
|
|
else
|
|
{
|
|
// msg 241: "Table %s not found"
|
|
status_exception::raise(
|
|
Arg::Gds(ENCODE_ISC_MSG(241, DYN_MSG_FAC)) << 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::Gds(ENCODE_ISC_MSG(133, DYN_MSG_FAC)));
|
|
}
|
|
|
|
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::Gds(ENCODE_ISC_MSG(20, DYN_MSG_FAC)) << definition.refRelation);
|
|
}
|
|
}
|
|
|
|
IDX.RDB$SEGMENT_COUNT = SSHORT(definition.columns.getCount());
|
|
}
|
|
END_STORE
|
|
}
|
|
|
|
|
|
//----------------------
|
|
|
|
|
|
// 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;
|
|
}
|
|
|
|
|
|
} // namespace Jrd
|