8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-26 07:23:08 +01:00
firebird-mirror/src/qli/proc.epp
2003-02-08 00:58:10 +00:00

903 lines
21 KiB
Plaintext

/*
* PROGRAM: JRD Command Oriented Query Language
* MODULE: proc.e
* DESCRIPTION: Procedure maintenance routines
*
* 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): ______________________________________.
*
* 2002.10.30 Sean Leyne - Removed support for obsolete "PC_PLATFORM" define
*
*/
#include "firebird.h"
#include <string.h>
#include "../jrd/gds.h"
#include "../qli/dtr.h"
#include "../qli/parse.h"
#include "../qli/compile.h"
#include "../qli/err_proto.h"
#include "../qli/lex_proto.h"
#include "../qli/meta_proto.h"
#include "../qli/parse_proto.h"
#include "../qli/proc_proto.h"
DATABASE DB1 = EXTERN FILENAME "yachts.lnk";
DATABASE DB = EXTERN FILENAME "yachts.lnk";
static int clear_out_qli_procedures(DBB);
static void create_qli_procedures(DBB);
static void probe(DBB, TEXT *);
static int upcase_name(TEXT *, TEXT *);
static UCHAR tpb[] = { gds_tpb_version1, gds_tpb_write,
gds_tpb_concurrency
};
static UCHAR dyn_gdl1[] = {
#include "../qli/procddl1.h"
};
static UCHAR dyn_gdl2[] = {
#include "../qli/procddl2.h"
};
static UCHAR dyn_gdl3[] = {
#include "../qli/procddl3.h"
};
static UCHAR dyn_gdl4[] = {
#include "../qli/procddl4.h"
};
void PRO_close( DBB database, FRBRD *blob)
{
/**************************************
*
* P R O _ c l o s e
*
**************************************
*
* Functional description
* Close out an open procedure blob.
*
**************************************/
STATUS status_vector[ISC_STATUS_LENGTH];
if (database && gds__close_blob(status_vector, GDS_REF(blob)))
ERRQ_database_error(database, status_vector);
}
void PRO_commit( DBB database)
{
/**************************************
*
* P R O _ c o m m i t
*
**************************************
*
* Functional description
* Commit the procedure transaction.
*
**************************************/
STATUS status_vector[ISC_STATUS_LENGTH];
if ((database->dbb_capabilities & DBB_cap_multi_trans) &&
!(LEX_active_procedure()))
if (gds__commit_transaction(status_vector,
GDS_REF(database->dbb_proc_trans))) {
PRO_rollback(database);
ERRQ_database_error(database, status_vector);
}
}
void PRO_copy_procedure(
DBB old_database,
TEXT * old_name, DBB new_database, TEXT * new_name)
{
/**************************************
*
* P R O _ c o p y _ p r o c e d u r e
*
**************************************
*
* Functional description
* Create a new copy of an existing
* procedure. Search databases for the
* existing procedure.
*
**************************************/
FRBRD *old_blob, *new_blob;
FRBRD *store_request;
STATUS status_vector[ISC_STATUS_LENGTH];
TEXT buffer[255];
SSHORT length;
USHORT bpb_length;
UCHAR bpb[20], *p;
if (!new_database)
new_database = QLI_databases;
if (!old_database) {
for (old_database = QLI_databases; old_database;
old_database = old_database->dbb_next) if (old_blob =
PRO_fetch_procedure
(old_database,
old_name)) break;
}
else
old_blob = PRO_fetch_procedure(old_database, old_name);
if (!old_blob)
ERRQ_print_error(70, old_name, NULL, NULL, NULL, NULL);
/* Msg 70 procedure \"%s\" is undefined */
if (new_database != old_database)
PRO_setup(new_database);
new_blob = NULL;
store_request = NULL;
probe(new_database, new_name);
DB1 = new_database->dbb_handle;
/* create blob parameter block since procedure is a text blob */
p = bpb;
bpb_length = p - bpb;
STORE(REQUEST_HANDLE store_request TRANSACTION_HANDLE new_database->dbb_proc_trans)
NEW IN DB1.QLI$PROCEDURES USING
strcpy(NEW.QLI$PROCEDURE_NAME, new_name);
if (gds__create_blob2(status_vector,
GDS_REF(new_database->dbb_handle),
GDS_REF(new_database->dbb_proc_trans),
GDS_REF(new_blob),
GDS_REF(NEW.QLI$PROCEDURE),
bpb_length,
(char*) bpb))
ERRQ_database_error(new_database, status_vector);
while (!(gds__get_segment(status_vector,
GDS_REF(old_blob),
(USHORT*) GDS_REF(length),
sizeof(buffer), GDS_VAL(buffer)))) {
buffer[length] = 0;
if (gds__put_segment(status_vector, GDS_REF(new_blob),
length, buffer))
ERRQ_database_error(new_database, status_vector);
}
PRO_close(old_database, old_blob);
PRO_close(new_database, new_blob);
END_STORE;
/* Release the FOR and STORE requests */
gds__release_request(gds_status, GDS_REF(store_request));
PRO_commit(new_database);
}
void PRO_create( DBB database, TEXT * name)
{
/**************************************
*
* P R O _ c r e a t e
*
**************************************
*
* Functional description
* Create a new procedure, assuming, of course, it doesn't already
* exist.
*
**************************************/
STATUS status_vector[ISC_STATUS_LENGTH];
SLONG start, stop;
FRBRD *blob;
USHORT bpb_length;
UCHAR bpb[20], *p;
/* See if procedure is already in use */
probe(database, name);
blob = NULL;
start = QLI_token->tok_position;
stop = start;
p = bpb;
bpb_length = p - bpb;
PRO_setup(database);
/* Store record in QLI$PROCEDURES. Eat tokens until we run into
END_PROCEDURE, then have LEX store the procedure in blobs. */
STORE(REQUEST_HANDLE database->dbb_store_blob) X IN DB.QLI$PROCEDURES
gds__vtof(name, X.QLI$PROCEDURE_NAME, sizeof(X.QLI$PROCEDURE_NAME));
if (gds__create_blob2(status_vector,
GDS_REF(database->dbb_handle),
GDS_REF(gds_trans),
GDS_REF(blob),
GDS_REF(X.QLI$PROCEDURE),
bpb_length,
(char*) bpb))
ERRQ_database_error(database, status_vector);
while (!MATCH(KW_END_PROCEDURE)) {
if (QLI_token->tok_type == tok_eof)
SYNTAX_ERROR(350);
if (QLI_token->tok_type != tok_eol)
stop = QLI_token->tok_position + QLI_token->tok_length;
LEX_token();
}
LEX_put_procedure(blob, start, stop);
gds__close_blob(status_vector, GDS_REF(blob));
END_STORE;
/* Commit the procedure transaction, if there is one */
PRO_commit(database);
}
int PRO_delete_procedure( DBB database, TEXT * name)
{
/**************************************
*
* P R O _ d e l e t e _ p r o c e d u r e
*
**************************************
*
* Functional description
* Delete a procedure.
*
**************************************/
USHORT count;
FRBRD *request;
request = NULL;
count = 0;
PRO_setup(database);
FOR(REQUEST_HANDLE request) X IN DB.QLI$PROCEDURES WITH
X.QLI$PROCEDURE_NAME EQ name
ERASE X;
count++;
END_FOR;
gds__release_request(gds_status, GDS_REF(request));
/* Commit the procedure transaction, if there is one */
PRO_commit(database);
return count;
}
void PRO_edit_procedure( DBB database, TEXT * name)
{
/**************************************
*
* P R O _ e d i t _ p r o c e d u r e
*
**************************************
*
* Functional description
* Edit a procedure, using the token stream to get the name of
* the procedure.
*
**************************************/
PRO_setup(database);
FOR(REQUEST_HANDLE database->dbb_edit_blob) X IN DB.QLI$PROCEDURES WITH
X.QLI$PROCEDURE_NAME EQ name
MODIFY X USING
if (!BLOB_edit(&X.QLI$PROCEDURE, database->dbb_handle,
gds_trans, name))
return;
END_MODIFY
PRO_commit(database);
return;
END_FOR
STORE(REQUEST_HANDLE database->dbb_edit_store) X IN DB.QLI$PROCEDURES
X.QLI$PROCEDURE = gds_blob_null;
if (!BLOB_edit(&X.QLI$PROCEDURE, database->dbb_handle,
gds_trans, name))
return;
gds__vtof(name, X.QLI$PROCEDURE_NAME, sizeof(X.QLI$PROCEDURE_NAME));
END_STORE
/* Commit the procedure transaction, if there is one */
PRO_commit(database);
}
FRBRD *PRO_fetch_procedure( DBB database, TEXT * proc)
{
/**************************************
*
* P R O _ f e t c h _ p r o c e d u r e
*
**************************************
*
* Functional description
* Fetch a procedure. Look up a name and return an open blob.
* If the name doesn't exit, return NULL.
*
**************************************/
FRBRD *blob;
TEXT name[32];
upcase_name(proc, name);
PRO_setup(database);
blob = NULL;
FOR(REQUEST_HANDLE database->dbb_lookup_blob) X IN DB.QLI$PROCEDURES WITH
X.QLI$PROCEDURE_NAME EQ name
blob = PRO_open_blob(database, (SLONG*) &X.QLI$PROCEDURE);
END_FOR
return blob;
}
int PRO_get_line( FRBRD *blob, TEXT * buffer, USHORT size)
{
/**************************************
*
* P R O _ g e t _ l i n e
*
**************************************
*
* Functional description
* Get the next segment of procedure blob. If there are
* no more segments, return false.
*
**************************************/
STATUS status_vector[ISC_STATUS_LENGTH];
TEXT *p;
USHORT length;
gds__get_segment(status_vector,
GDS_REF(blob), GDS_REF(length), size, GDS_VAL(buffer));
if (status_vector[1] && status_vector[1] != gds_segment)
return FALSE;
p = buffer + length;
if (p[-1] != '\n' && !status_vector[1])
*p++ = '\n';
*p = 0;
return TRUE;
}
void PRO_invoke( DBB database, TEXT * name)
{
/**************************************
*
* P R O _ i n v o k e
*
**************************************
*
* Functional description
* Invoke a procedure. The qualified procedure
* block may include the database we want, or
* we may have to loop through databases. Whatever...
*
**************************************/
FRBRD *blob;
blob = NULL;
if (database) {
if (!(blob = PRO_fetch_procedure(database, name)))
ERRQ_print_error(71, name, database->dbb_symbol->sym_string, NULL,
NULL, NULL);
/* Msg 71 procedure \"%s\" is undefined in database %s */
}
else
for (database = QLI_databases; database;
database = database->dbb_next) if (blob =
PRO_fetch_procedure(database,
name))
break;
if (!blob)
ERRQ_print_error(72, name, NULL, NULL, NULL, NULL);
/* Msg 72 procedure \"%s\" is undefined */
LEX_procedure((struct dbb*) database, blob);
LEX_token();
}
FRBRD *PRO_open_blob( DBB database, SLONG * blob_id)
{
/**************************************
*
* P R O _ o p e n _ b l o b
*
**************************************
*
* Functional description
* Open a procedure blob.
*
**************************************/
FRBRD *blob;
STATUS status_vector[ISC_STATUS_LENGTH];
USHORT bpb_length;
UCHAR bpb[20], *p;
blob = NULL;
p = bpb;
bpb_length = p - bpb;
if (gds__open_blob2(status_vector,
GDS_REF(database->dbb_handle),
GDS_REF(database->dbb_proc_trans),
GDS_REF(blob),
(GDS__QUAD*) GDS_VAL(blob_id),
bpb_length,
(char*) bpb)) ERRQ_database_error(database, status_vector);
return blob;
}
int PRO_rename_procedure( DBB database, TEXT * old_name, TEXT * new_name)
{
/**************************************
*
* P R O _ r e n a m e _ p r o c e d u r e
*
**************************************
*
* Functional description
* Change the name of a procedure.
*
**************************************/
USHORT count;
STATUS status_vector[ISC_STATUS_LENGTH];
FRBRD *request;
request = NULL;
PRO_setup(database);
probe(database, new_name);
count = 0;
FOR(REQUEST_HANDLE request) X IN DB.QLI$PROCEDURES WITH
X.QLI$PROCEDURE_NAME EQ old_name count++;
MODIFY X USING
gds__vtof(new_name, X.QLI$PROCEDURE_NAME, sizeof(X.QLI$PROCEDURE_NAME));
END_MODIFY
ON_ERROR
gds__release_request(gds_status, GDS_REF(request));
ERRQ_database_error(database, status_vector);
END_ERROR;
END_FOR;
gds__release_request(gds_status, GDS_REF(request));
/* Commit the procedure transaction, if there is one */
PRO_commit(database);
return count;
}
void PRO_rollback( DBB database)
{
/**************************************
*
* P R O _ r o l l b a c k
*
**************************************
*
* Functional description
* Rollback the procedure transaction,
* if there is one.
*
**************************************/
STATUS status_vector[ISC_STATUS_LENGTH];
if (database->dbb_capabilities & DBB_cap_multi_trans) {
gds__rollback_transaction(status_vector,
GDS_REF(database->dbb_proc_trans));
gds_trans = NULL;
}
}
void PRO_scan( DBB database, void (*routine) (), void *arg)
{
/**************************************
*
* P R O _ s c a n
*
**************************************
*
* Functional description
* Loop thru procedures calling given routine.
*
**************************************/
TEXT *p;
PRO_setup(database);
FOR(REQUEST_HANDLE database->dbb_scan_blobs) X IN DB.QLI$PROCEDURES
SORTED BY X.QLI$PROCEDURE_NAME
p = X.QLI$PROCEDURE_NAME;
while (*p && *p != ' ') {
p++;
}
(* (void(*)(void*, TEXT *, USHORT, DBB, SLONG *)) routine) (arg, X.QLI$PROCEDURE_NAME,
strlen(X.QLI$PROCEDURE_NAME), database, (SLONG*) &X.QLI$PROCEDURE);
END_FOR
}
void PRO_setup( DBB dbb)
{
/**************************************
*
* P R O _ s e t u p
*
**************************************
*
* Functional description
* Prepare for a DML operation on a database. Start a procedure
* transaction is one hasn't been started and the database
* system will start multiple transactions. Otherwise use
* the default transaction.
*
**************************************/
if (!dbb)
IBERROR(77);
/* Msg 77 database handle required */
/* If we don't have a QLI$PROCEDURES relation, and can't get one, punt */
if (dbb->dbb_type == gds_info_db_impl_rdb_vms &&
!(dbb->dbb_flags & DBB_procedures)) IBERROR(78);
/* Msg 78 QLI$PROCEDURES relation must be created with RDO in Rdb/VMS databases */
DB = dbb->dbb_handle;
if (dbb->dbb_flags & DBB_procedures) {
gds_trans = PRO_transaction(dbb, FALSE);
return;
}
/* Sigh. Relation doesn't exist. So make it exist. */
create_qli_procedures(dbb);
gds_trans = PRO_transaction(dbb, FALSE);
}
FRBRD *PRO_transaction( DBB database, int update_flag)
{
/**************************************
*
* P R O _ t r a n s a c t i o n
*
**************************************
*
* Functional description
* Setup transaction for procedure or form operation.
*
* In any event, we will set up the met_transaction because
* it's widely used and somebody is going to forget to test
* that the database is multi_transaction before using it.
* We have to be careful about committing or rolling back
* because we ould affect user data.
*
**************************************/
STATUS status_vector[ISC_STATUS_LENGTH];
FRBRD *transaction;
if (!database)
IBERROR(248); /* Msg248 no active database for operation */
transaction = (database->dbb_capabilities & DBB_cap_multi_trans) ?
database->dbb_proc_trans : NULL;
DB = database->dbb_handle;
/* If we don't know whether or not the database can handle
multiple transactions, find out now */
if (!transaction &&
((database->dbb_capabilities & DBB_cap_multi_trans) ||
!(database->dbb_capabilities & DBB_cap_single_trans)))
if (gds__start_transaction(status_vector,
GDS_REF(transaction),
1,
GDS_REF(database->dbb_handle),
sizeof(tpb),
tpb))
database->dbb_capabilities |= DBB_cap_single_trans;
else
database->dbb_capabilities |= DBB_cap_multi_trans;
/* If we already have a procedure transaction, there's more nothing to do */
gds_trans = transaction;
/* If we only support a single transaction, use the data transaction */
if (!gds_trans && (database->dbb_capabilities & DBB_cap_single_trans)) {
if (update_flag)
IBERROR(249); /* Msg249 Interactive metadata updates are not available on Rdb */
if (!(gds_trans = database->dbb_transaction))
gds_trans = MET_transaction(nod_start_trans, database);
}
/* otherwise make one more effort to start the transaction */
else if (!gds_trans) {
START_TRANSACTION
ON_ERROR
ERRQ_database_error(database, status_vector);
END_ERROR;
}
database->dbb_proc_trans = gds_trans;
return gds_trans;
}
static int clear_out_qli_procedures( DBB dbb)
{
/**************************************
*
* c l e a r _ o u t _ q l i _ p r o c e d u r e s
*
**************************************
*
* Functional description
* The procedures relation can't be found. Poke
* around and delete any trash lying around.
* Before cleaning out the trash, see if somebody
* else has set up for us.
*
**************************************/
int count;
FRBRD *req;
STATUS status_vector[ISC_STATUS_LENGTH];
req = NULL;
count = 0;
FOR(REQUEST_HANDLE req) R IN DB.RDB$RELATIONS CROSS
F IN DB.RDB$FIELDS CROSS RFR IN DB.RDB$RELATION_FIELDS WITH
RFR.RDB$RELATION_NAME = R.RDB$RELATION_NAME AND
RFR.RDB$FIELD_NAME = F.RDB$FIELD_NAME AND
R.RDB$RELATION_NAME = "QLI$PROCEDURES"
count++;
END_FOR
ON_ERROR
gds__release_request(status_vector, GDS_REF(req));
ERRQ_database_error(dbb, gds_status);
END_ERROR;
gds__release_request(status_vector, GDS_REF(req));
if (count >= 2) {
dbb->dbb_flags |= DBB_procedures;
return 0;
}
count = 0;
FOR(REQUEST_HANDLE req) X IN DB.RDB$INDICES WITH X.RDB$INDEX_NAME = "QLI$PROCEDURES_IDX1"
ERASE X;
count++;
END_FOR
ON_ERROR
gds__release_request(status_vector, GDS_REF(req));
ERRQ_database_error(dbb, gds_status);
END_ERROR;
gds__release_request(status_vector, GDS_REF(req));
FOR(REQUEST_HANDLE req) X IN DB.RDB$INDEX_SEGMENTS WITH X.RDB$INDEX_NAME = "QLI$PROCEDURES_IDX1"
ERASE X;
count++;
END_FOR
ON_ERROR
gds__release_request(status_vector, GDS_REF(req));
ERRQ_database_error(dbb, gds_status);
END_ERROR;
gds__release_request(status_vector, GDS_REF(req));
FOR(REQUEST_HANDLE req) X IN DB.RDB$RELATION_FIELDS
WITH X.RDB$FIELD_NAME = "QLI$PROCEDURE_NAME" OR
X.RDB$FIELD_NAME = "QLI$PROCEDURE"
ERASE X;
count++;
END_FOR
ON_ERROR
gds__release_request(status_vector, GDS_REF(req));
ERRQ_database_error(dbb, gds_status);
END_ERROR;
gds__release_request(gds_status, GDS_REF(req));
FOR(REQUEST_HANDLE req) X IN DB.RDB$FIELDS
WITH X.RDB$FIELD_NAME = "QLI$PROCEDURE_NAME" OR
X.RDB$FIELD_NAME = "QLI$PROCEDURE"
ERASE X;
count++;
END_FOR
ON_ERROR
gds__release_request(status_vector, GDS_REF(req));
ERRQ_database_error(dbb, gds_status);
END_ERROR;
gds__release_request(gds_status, GDS_REF(req));
FOR(REQUEST_HANDLE req) X IN DB.RDB$RELATIONS
WITH X.RDB$RELATION_NAME = "QLI$PROCEDURES"
ERASE X;
count++;
END_FOR
ON_ERROR
gds__release_request(status_vector, GDS_REF(req));
ERRQ_database_error(dbb, gds_status);
END_ERROR;
gds__release_request(gds_status, GDS_REF(req));
return count;
}
static void create_qli_procedures( DBB dbb)
{
/**************************************
*
* c r e a t e _ q l i _ p r o c e d u r e s
*
**************************************
*
* Functional description
* The procedures relation can't be found. Clean
* out residual trash and (re)create the relation.
* Before cleaning out the trash, see if somebody
* else has set up for us.
*
**************************************/
gds_trans = PRO_transaction(dbb, TRUE);
if (clear_out_qli_procedures(dbb)) {
PRO_commit(dbb);
gds_trans = PRO_transaction(dbb, TRUE);
}
if (dbb->dbb_flags & DBB_procedures)
return;
if (gds__ddl(gds_status,
GDS_REF(DB),
GDS_REF(gds_trans), sizeof(dyn_gdl1), (char*) dyn_gdl1)) {
PRO_rollback(dbb);
IBERROR(73);
/* Msg 73 Could not create QLI$PROCEDURE_NAME field */
}
if (gds__ddl(gds_status,
GDS_REF(DB),
GDS_REF(gds_trans), sizeof(dyn_gdl2), (char*) dyn_gdl2)) {
PRO_rollback(dbb);
IBERROR(74);
/* Msg 74 Could not create QLI$PROCEDURE field */
}
if (gds__ddl(gds_status,
GDS_REF(DB),
GDS_REF(gds_trans), sizeof(dyn_gdl3), (char*) dyn_gdl3)) {
PRO_rollback(dbb);
IBERROR(75);
/* Msg 75 Could not create QLI$PROCEDURES relation */
}
if (gds__ddl(gds_status,
GDS_REF(DB),
GDS_REF(gds_trans), sizeof(dyn_gdl4), (char*) dyn_gdl4)) {
PRO_rollback(dbb);
IBERROR(409);
/* msg 409 Could not create QLI$PROCEDURES index */
}
dbb->dbb_flags |= DBB_procedures;
PRO_commit(dbb);
}
static void probe( DBB database, TEXT * name)
{
/**************************************
*
* p r o b e
*
**************************************
*
* Functional description
* Check whether a procedure name is already in
* use in a particular database. IBERROR and don't
* return if it is.
*
**************************************/
FRBRD *blob;
STATUS status_vector[ISC_STATUS_LENGTH];
/* Probe to see if procedure is already in use */
if (blob = PRO_fetch_procedure(database, name)) {
gds__close_blob(status_vector, GDS_REF(blob));
ERRQ_print_error(76, name, database->dbb_symbol->sym_string, NULL,
NULL, NULL);
/* Msg 76 procedure name \"%s\" in use in database %s */
}
}
static int upcase_name( TEXT * name, TEXT * buffer)
{
/**************************************
*
* u p c a s e _ n a m e
*
**************************************
*
* Functional description
* Upcase a null terminated string and return its length. If the
* length is greater than 31 bytes, barf.
*
**************************************/
USHORT l;
TEXT c;
l = 0;
while (TRUE) {
c = *name++;
*buffer++ = UPPER(c);
if (!c)
return l;
if (++l > 31)
IBERROR(79); /* Msg 79 procedure name over 31 characters */
}
}