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

10714 lines
296 KiB
Plaintext
Raw Normal View History

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