mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-29 06:43:03 +01:00
466 lines
15 KiB
C++
466 lines
15 KiB
C++
/*
|
|
* PROGRAM: Dynamic SQL runtime support
|
|
* MODULE: ddl.cpp
|
|
* DESCRIPTION: Utilities for generating ddl
|
|
*
|
|
* 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): ______________________________________.
|
|
*
|
|
* 2001.5.20 Claudio Valderrama: Stop null pointer that leads to a crash,
|
|
* caused by incomplete yacc syntax that allows ALTER DOMAIN dom SET;
|
|
*
|
|
* 2001.07.06 Sean Leyne - Code Cleanup, removed "#ifdef READONLY_DATABASE"
|
|
* conditionals, as the engine now fully supports
|
|
* readonly databases.
|
|
* December 2001 Mike Nordell - Attempt to make it C++
|
|
*
|
|
* 2001.5.20 Claudio Valderrama: Stop null pointer that leads to a crash,
|
|
* caused by incomplete yacc syntax that allows ALTER DOMAIN dom SET;
|
|
* 2001.5.29 Claudio Valderrama: Check for view v/s relation in DROP
|
|
* command will stop a user that uses DROP VIEW and drops a table by
|
|
* accident and vice-versa.
|
|
* 2001.5.30 Claudio Valderrama: alter column should use 1..N for the
|
|
* position argument since the call comes from SQL DDL.
|
|
* 2001.6.27 Claudio Valderrama: DDL_resolve_intl_type() was adding 2 to the
|
|
* length of varchars instead of just checking that len+2<=MAX_COLUMN_SIZE.
|
|
* It required a minor change to put_field() where it was decremented, too.
|
|
* 2001.6.27 Claudio Valderrama: Finally stop users from invoking the same option
|
|
* several times when altering a domain. Specially dangerous with text data types.
|
|
* Ex: alter domain d type char(5) type varchar(5) default 'x' default 'y';
|
|
* Bear in mind that if DYN functions are addressed directly, this protection
|
|
* becomes a moot point.
|
|
* 2001.6.30 Claudio Valderrama: revert changes from 2001.6.26 because the code
|
|
* is called from several places and there are more functions, even in metd.c,
|
|
* playing the same nonsense game with the field's length, so it needs more
|
|
* careful examination. For now, the new checks in DYN_MOD should catch most anomalies.
|
|
* 2001.7.3 Claudio Valderrama: fix Firebird Bug #223059 with mismatch between number
|
|
* of declared fields for a VIEW and effective fields in the SELECT statement.
|
|
* 2001.07.22 Claudio Valderrama: minor fixes and improvements.
|
|
* 2001.08.18 Claudio Valderrama: RECREATE PROCEDURE.
|
|
* 2001.10.01 Claudio Valderrama: modify_privilege() should recognize that a ROLE can
|
|
* now be made an explicit grantee.
|
|
* 2001.10.08 Claudio Valderrama: implement fb_sysflag enum values for autogenerated
|
|
* non-system triggers so DFW can recognize them easily.
|
|
* 2001.10.26 Claudio Valderrama: added a call to the new METD_drop_function()
|
|
* in DDL_execute() so the metadata cache for udfs can be refreshed.
|
|
* 2001.12.06 Claudio Valderrama: DDL_resolve_intl_type should calculate field length
|
|
* 2002.08.04 Claudio Valderrama: allow declaring and defining variables at the same time
|
|
* 2002.08.04 Dmitry Yemanov: ALTER VIEW
|
|
* 2002.08.31 Dmitry Yemanov: allowed user-defined index names for PK/FK/UK constraints
|
|
* 2002.09.01 Dmitry Yemanov: RECREATE VIEW
|
|
* 2002.09.12 Nickolay Samofatov: fixed cached metadata errors
|
|
* 2004.01.16 Vlad Horsun: added support for default parameters and
|
|
* EXECUTE BLOCK statement
|
|
* Adriano dos Santos Fernandes
|
|
*/
|
|
|
|
#include "firebird.h"
|
|
#include "dyn_consts.h"
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "../jrd/SysFunction.h"
|
|
#include "../common/classes/MetaName.h"
|
|
#include "../dsql/dsql.h"
|
|
#include "../dsql/ExprNodes.h"
|
|
#include "../jrd/ibase.h"
|
|
#include "../jrd/Attachment.h"
|
|
#include "../jrd/RecordSourceNodes.h"
|
|
#include "../jrd/intl.h"
|
|
#include "../jrd/intl_classes.h"
|
|
#include "../jrd/jrd.h"
|
|
#include "../jrd/flags.h"
|
|
#include "../jrd/constants.h"
|
|
#include "../dsql/errd_proto.h"
|
|
#include "../dsql/ddl_proto.h"
|
|
#include "../dsql/gen_proto.h"
|
|
#include "../dsql/make_proto.h"
|
|
#include "../dsql/metd_proto.h"
|
|
#include "../dsql/pass1_proto.h"
|
|
#include "../dsql/utld_proto.h"
|
|
#include "../jrd/intl_proto.h"
|
|
#include "../jrd/met_proto.h"
|
|
#include "../yvalve/gds_proto.h"
|
|
#include "../jrd/jrd_proto.h"
|
|
#include "../jrd/vio_proto.h"
|
|
#include "../yvalve/why_proto.h"
|
|
#include "../common/utils_proto.h"
|
|
#include "../dsql/DdlNodes.h"
|
|
#include "../dsql/DSqlDataTypeUtil.h"
|
|
#include "../common/StatusArg.h"
|
|
|
|
#ifdef DSQL_DEBUG
|
|
#include "../common/prett_proto.h"
|
|
#endif
|
|
|
|
using namespace Jrd;
|
|
using namespace Firebird;
|
|
|
|
|
|
static void assign_field_length(dsql_fld*, USHORT);
|
|
static void post_607(const Arg::StatusVector& v);
|
|
|
|
|
|
///const int DEFAULT_BLOB_SEGMENT_SIZE = 80; // bytes
|
|
|
|
|
|
// Determine whether ids or names should be referenced when generating blr for fields and relations.
|
|
bool DDL_ids(const DsqlCompilerScratch* scratch)
|
|
{
|
|
return !(scratch->flags & DsqlCompilerScratch::FLAG_DDL);
|
|
}
|
|
|
|
|
|
void DDL_resolve_intl_type(DsqlCompilerScratch* dsqlScratch, dsql_fld* field,
|
|
const MetaName& collation_name, bool modifying)
|
|
{
|
|
/**************************************
|
|
*
|
|
* D D L _ r e s o l v e _ i n t l _ t y p e
|
|
*
|
|
**************************************
|
|
*
|
|
* Function
|
|
|
|
* If the field is defined with a character set or collation,
|
|
* resolve the names to a subtype now.
|
|
*
|
|
* Also resolve the field length & whatnot.
|
|
*
|
|
* If the field is being created, it will pick the db-wide charset
|
|
* and collation if not specified. If the field is being modified,
|
|
* since we don't allow changes to those attributes, we'll go and
|
|
* calculate the correct old length from the field itself so DYN
|
|
* can validate the change properly.
|
|
*
|
|
* For International text fields, this is a good time to calculate
|
|
* their actual size - when declared they were declared in
|
|
* lengths of CHARACTERs, not BYTES.
|
|
*
|
|
**************************************/
|
|
|
|
if (field->typeOfName.hasData())
|
|
{
|
|
if (field->typeOfTable.hasData())
|
|
{
|
|
dsql_rel* relation = METD_get_relation(dsqlScratch->getTransaction(), dsqlScratch,
|
|
field->typeOfTable.c_str());
|
|
const dsql_fld* fld = NULL;
|
|
|
|
if (relation)
|
|
{
|
|
const MetaName fieldName(field->typeOfName);
|
|
|
|
for (fld = relation->rel_fields; fld; fld = fld->fld_next)
|
|
{
|
|
if (fieldName == fld->fld_name)
|
|
{
|
|
field->dimensions = fld->dimensions;
|
|
field->fieldSource = fld->fieldSource;
|
|
field->length = fld->length;
|
|
field->scale = fld->scale;
|
|
field->subType = fld->subType;
|
|
field->charSetId = fld->charSetId;
|
|
field->collationId = fld->collationId;
|
|
field->charLength = fld->charLength;
|
|
field->flags = fld->flags;
|
|
field->dtype = fld->dtype;
|
|
field->segLength = fld->segLength;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!fld)
|
|
{
|
|
// column @1 does not exist in table/view @2
|
|
post_607(Arg::Gds(isc_dyn_column_does_not_exist) <<
|
|
Arg::Str(field->typeOfName) <<
|
|
field->typeOfTable);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!METD_get_domain(dsqlScratch->getTransaction(), field, field->typeOfName))
|
|
{
|
|
// Specified domain or source field does not exist
|
|
post_607(Arg::Gds(isc_dsql_domain_not_found) << Arg::Str(field->typeOfName));
|
|
}
|
|
}
|
|
|
|
if (field->dimensions != 0)
|
|
{
|
|
ERRD_post(Arg::Gds(isc_wish_list) <<
|
|
Arg::Gds(isc_random) <<
|
|
Arg::Str("Usage of domain or TYPE OF COLUMN of array type in PSQL"));
|
|
}
|
|
|
|
if (field->dtype <= dtype_any_text ||
|
|
(field->dtype == dtype_blob && field->subType == isc_blob_text))
|
|
{
|
|
field->charSet = METD_get_charset_name(dsqlScratch->getTransaction(), field->charSetId);
|
|
}
|
|
}
|
|
|
|
if ((field->dtype > dtype_any_text) && field->dtype != dtype_blob)
|
|
{
|
|
if (field->charSet.hasData() || collation_name.hasData() || (field->flags & FLD_national))
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
|
|
Arg::Gds(isc_dsql_datatype_err) << Arg::Gds(isc_collation_requires_text));
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (field->dtype == dtype_blob)
|
|
{
|
|
if (field->subTypeName.hasData())
|
|
{
|
|
SSHORT blob_sub_type;
|
|
if (!METD_get_type(dsqlScratch->getTransaction(), field->subTypeName,
|
|
"RDB$FIELD_SUB_TYPE", &blob_sub_type))
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
|
|
Arg::Gds(isc_dsql_datatype_err) <<
|
|
Arg::Gds(isc_dsql_blob_type_unknown) <<
|
|
Arg::Str(field->subTypeName));
|
|
}
|
|
field->subType = blob_sub_type;
|
|
}
|
|
|
|
if (field->subType > isc_blob_text)
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
|
|
Arg::Gds(isc_dsql_datatype_err) <<
|
|
Arg::Gds(isc_subtype_for_internal_use));
|
|
}
|
|
|
|
if (field->charSet.hasData() && (field->subType == isc_blob_untyped))
|
|
field->subType = isc_blob_text;
|
|
|
|
if (field->charSet.hasData() && (field->subType != isc_blob_text))
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
|
|
Arg::Gds(isc_dsql_datatype_err) <<
|
|
Arg::Gds(isc_collation_requires_text));
|
|
}
|
|
|
|
if (collation_name.hasData() && (field->subType != isc_blob_text))
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
|
|
Arg::Gds(isc_dsql_datatype_err) <<
|
|
Arg::Gds(isc_collation_requires_text));
|
|
}
|
|
|
|
if (field->subType != isc_blob_text)
|
|
return;
|
|
}
|
|
|
|
if (field->charSetId != 0 && collation_name.isEmpty())
|
|
{
|
|
// This field has already been resolved once, and the collation
|
|
// hasn't changed. Therefore, no need to do it again.
|
|
return;
|
|
}
|
|
|
|
if (modifying)
|
|
{
|
|
const dsql_fld* afield = field->fld_next;
|
|
USHORT bpc = 0;
|
|
|
|
while (afield)
|
|
{
|
|
// The first test is redundant.
|
|
if (afield != field && afield->fld_relation && afield->fld_name == field->fld_name)
|
|
{
|
|
fb_assert(afield->fld_relation == dsqlScratch->relation || !dsqlScratch->relation);
|
|
break;
|
|
}
|
|
|
|
afield = afield->fld_next;
|
|
}
|
|
|
|
if (afield)
|
|
{
|
|
field->charSetId = afield->charSetId;
|
|
bpc = METD_get_charset_bpc(dsqlScratch->getTransaction(), field->charSetId);
|
|
field->collationId = afield->collationId;
|
|
field->textType = afield->textType;
|
|
|
|
if (afield->flags & FLD_national)
|
|
field->flags |= FLD_national;
|
|
else
|
|
field->flags &= ~FLD_national;
|
|
|
|
assign_field_length(field, bpc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!(field->charSet.hasData() || field->charSetId || // set if a domain
|
|
(field->flags & FLD_national)))
|
|
{
|
|
// Attach the database default character set, if not otherwise specified
|
|
|
|
MetaName defaultCharSet;
|
|
|
|
if (dsqlScratch->flags & DsqlCompilerScratch::FLAG_DDL)
|
|
defaultCharSet = METD_get_default_charset(dsqlScratch->getTransaction());
|
|
else
|
|
{
|
|
USHORT charSet = dsqlScratch->getAttachment()->dbb_attachment->att_charset;
|
|
if (charSet != CS_NONE)
|
|
defaultCharSet = METD_get_charset_name(dsqlScratch->getTransaction(), charSet);
|
|
}
|
|
|
|
if (defaultCharSet.hasData())
|
|
field->charSet = defaultCharSet;
|
|
else
|
|
{
|
|
// If field is not specified with NATIONAL, or CHARACTER SET
|
|
// treat it as a single-byte-per-character field of character set NONE.
|
|
assign_field_length(field, 1);
|
|
field->textType = 0;
|
|
|
|
if (collation_name.isEmpty())
|
|
return;
|
|
}
|
|
}
|
|
|
|
MetaName charset_name;
|
|
|
|
if (field->flags & FLD_national)
|
|
charset_name = NATIONAL_CHARACTER_SET;
|
|
else if (field->charSet.hasData())
|
|
charset_name = field->charSet;
|
|
|
|
// Find an intlsym for any specified character set name & collation name
|
|
const dsql_intlsym* resolved_type = NULL;
|
|
|
|
if (charset_name.hasData())
|
|
{
|
|
const dsql_intlsym* resolved_charset =
|
|
METD_get_charset(dsqlScratch->getTransaction(), (USHORT) charset_name.length(), charset_name.c_str());
|
|
|
|
// Error code -204 (IBM's DB2 manual) is close enough
|
|
if (!resolved_charset)
|
|
{
|
|
// specified character set not found
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
|
|
Arg::Gds(isc_dsql_datatype_err) <<
|
|
Arg::Gds(isc_charset_not_found) << Arg::Str(charset_name));
|
|
}
|
|
|
|
field->charSetId = resolved_charset->intlsym_charset_id;
|
|
resolved_type = resolved_charset;
|
|
}
|
|
|
|
if (collation_name.hasData())
|
|
{
|
|
const dsql_intlsym* resolved_collation = METD_get_collation(dsqlScratch->getTransaction(),
|
|
collation_name, field->charSetId);
|
|
|
|
if (!resolved_collation)
|
|
{
|
|
MetaName charSetName;
|
|
|
|
if (charset_name.hasData())
|
|
charSetName = charset_name;
|
|
else
|
|
{
|
|
charSetName = METD_get_charset_name(dsqlScratch->getTransaction(),
|
|
field->charSetId);
|
|
}
|
|
|
|
// Specified collation not found
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
|
|
///Arg::Gds(isc_dsql_datatype_err) << // (too large status vector)
|
|
Arg::Gds(isc_collation_not_found) << collation_name << charSetName);
|
|
}
|
|
|
|
// If both specified, must be for same character set
|
|
// A "literal constant" must be handled (charset as ttype_dynamic)
|
|
|
|
resolved_type = resolved_collation;
|
|
|
|
if ((field->charSetId != resolved_type->intlsym_charset_id) &&
|
|
(field->charSetId != ttype_dynamic))
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
|
|
Arg::Gds(isc_dsql_datatype_err) <<
|
|
Arg::Gds(isc_collation_not_for_charset) << collation_name);
|
|
}
|
|
|
|
field->explicitCollation = true;
|
|
}
|
|
|
|
assign_field_length(field, resolved_type->intlsym_bytes_per_char);
|
|
|
|
field->textType = resolved_type->intlsym_ttype;
|
|
field->charSetId = resolved_type->intlsym_charset_id;
|
|
field->collationId = resolved_type->intlsym_collate_id;
|
|
}
|
|
|
|
|
|
static void assign_field_length(dsql_fld* field, USHORT bytes_per_char)
|
|
{
|
|
/**************************************
|
|
*
|
|
* a s s i g n _ f i e l d _ l e n g t h
|
|
*
|
|
**************************************
|
|
*
|
|
* Function
|
|
* We'll see if the field's length fits in the maximum
|
|
* allowed field, including charset and space for varchars.
|
|
* Either we raise an error or assign the field's length.
|
|
* If the charlen comes as zero, we do nothing, although we
|
|
* know that DYN, MET and DFW will blindly set field length
|
|
* to zero if they don't catch charlen or another condition.
|
|
*
|
|
**************************************/
|
|
|
|
if (field->charLength)
|
|
{
|
|
ULONG field_length = (ULONG) bytes_per_char * field->charLength;
|
|
|
|
if (field->dtype == dtype_varying)
|
|
field_length += sizeof(USHORT);
|
|
|
|
if (field_length > MAX_COLUMN_SIZE)
|
|
{
|
|
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
|
|
Arg::Gds(isc_dsql_datatype_err) <<
|
|
Arg::Gds(isc_imp_exc) <<
|
|
Arg::Gds(isc_field_name) << Arg::Str(field->fld_name));
|
|
}
|
|
|
|
field->length = (USHORT) field_length;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// post very often used error - avoid code duplication
|
|
static void post_607(const Arg::StatusVector& v)
|
|
{
|
|
Arg::Gds err(isc_sqlerr);
|
|
err << Arg::Num(-607) << Arg::Gds(isc_dsql_command_err);
|
|
|
|
err.append(v);
|
|
ERRD_post(err);
|
|
}
|