如何解决使用自定义客户端调用插入操作时,插入之前的触发器会引发错误,但使用MySQL客户端时则不会
您好,感谢您谁愿意帮助我。
我正在编写一个名为directory_azienDale
(公司目录)的MysqL数据库模式,该模式必须跟踪公司的所有雇主,他们的工作,他们在办公室的位置以及在办公室之间的迁移。
我已经编写了一个名为scambiaDipendenti
的存储过程(switchEmployers)。当被调用时,仅当提供的雇主当前被分配到同一职位时,它才会切换席位,在TRASFERITO_A
表(转移到)中插入雇主转移到的座位的电话号码,日期和工作。
如果雇主在过去三年中已被分配到该席位,则TRASFERITO_A
的插入前触发器将中止插入。为此,它使用保存在TRASFERITO_A
中的日期
我已清空表TRASFERITO_A
并使用准备好的语句调用了scambiaDipendenti
,这就是ouptut,它符合预期的行为:
MysqL> prepare stmt from "call scambiaDipendenti(?,?)"
-> ;
Query OK,0 rows affected (0.00 sec)
MysqL> select * from TRASFERITO_A;
Empty set (0.00 sec)
MysqL> set @b = "TXHTGD97E65H851W"
-> ;
Query OK,0 rows affected (0.00 sec)
MysqL> set @a = "LNYRYM72A08Z327C"
-> ;
Query OK,0 rows affected (0.00 sec)
MysqL> execute stmt using @a,@b;
Query OK,0 rows affected (0.01 sec)
MysqL> select * from TRASFERITO_A;
+------------------+--------------------------------+------------+------------------+--------------+
| CFDipendente | NumTelefonicoEsternopostazione | Data | NomeMansione | NomeSettore |
+------------------+--------------------------------+------------+------------------+--------------+
| TXHTGD97E65H851W | 1230987654 | 2020-09-06 | calcolo bilancio | contabilità |
| LNYRYM72A08Z327C | 8582484945 | 2020-09-06 | calcolo bilancio | contabilità |
+------------------+--------------------------------+------------+------------------+--------------+
2 rows in set (0.00 sec)
我还正在使用MysqL C API用C编写瘦客户机,以与DB交互以调用某些操作。它们的一个操作准备了相同的状态,这是输出:
$ ./debug dipendenteSettoreSpazi
[I] DirAz Thin Client - connector for directory_azienDale DB
[I] Please enter password:
[I] Succesfully logged in as dipendenteSettoreSpazi
[I] Here is a list of supported operations. Please note that You must have the correct privileges for an operation in order to execute it. For executing an operation,opCode followed by its arguments (i.e: opCode [arg0,...])
[I] op1: generaReportDaTrasferire()
[I] op2_1: trovaDipendentiScambiabili(cfDipendente)
[I] op2_2: scambiaDipendenti(cfDipendente1,cfDipendente2)
[I] op3_1: trovaUfficiConPostazioneVuota(nomeMansione,nomeSettore)
[I] op3_2: assegnaDipendenteAPostazioneVuota(cfDipendente,numTelefonicoEsternopostazioneVuota)
[I] op4: cambiaMansioneDipendente(cfDipendente,nomeNuovaMansione,nomeNuovoSettore)
[I] op5: elencaTrasferimentiDipendente(cfDipendente)
[I] op6: ricercaDipendente(nome,cognome)
[I] op7: ricercaPerNumeroTelefono(numTelefonoEsterno)
[I] op9: assumiDipendente(cf,nome,cognome,luogoNascita,datanascita,emailPersonale,indirizzoResidenza,nomeMansione,nomeSettore)
[I] Type here:
op2_2 LNYRYM72A08Z327C TXHTGD97E65H851W
[D] 45004
[E] MysqL_stmt_execute: ERROR: Un dipendente non può essere trasferito a una postazione dove è stato già trasferito meno di tre anni fa
[E] Failed to launch statement
[E] Failed to prepare and launch statement
[E] Failed to execute op2_2
scambiaDipendenti
因sqlstatus 45004而失败,这意味着TRASFERITO_A的插入前触发器失败。
我的问题是:当MysqL客户端调用scambiaDipendenti
时,而我的瘦客户端调用失败时,为什么相同的触发器没有引发错误?
以下是来源。 op2_2是scambiaDipendenti
的操作码。
CREATE DEFINER=`root`@`localhost` PROCEDURE `scambiaDipendenti`(
in cfDipendente1 char(16),in cfDipendente2 char(16)
)
BEGIN
declare tempNumeroTelefonico1 varchar(45);
declare tempNumeroTelefonico2 varchar(45);
declare tempNomeMansione varchar(45);
declare tempNomeSettore varchar(45);
declare exit handler for sqlexception
begin
rollback;
resignal;
end;
start transaction;
-- controlla se le due postazioni appartengono ad uffici fisici assegnati alla stessa mansione e settore,che verranno salvati dentro delle variabili
call checkDipendentiStessaMansione(cfDipendente1,cfDipendente2,tempNomeMansione,tempNomeSettore);
if (tempNomeMansione is null and tempNomeSettore is null)
then signal sqlstate "45005" set message_text = "ERROR: i dipendenti forniti non sono assegnati alla stessa mansione";
end if;
-- scambia i dipendenti e aggiorna la tabella TRASFERITO_A atomicamente
set tempNumeroTelefonico1 = (select NumTelefonicoEsternopostazione from DIPENDENTE where CF = cfDipendente1);
set tempNumeroTelefonico2 = (select NumTelefonicoEsternopostazione from DIPENDENTE where CF = cfDipendente2);
update DIPENDENTE set NumTelefonicoEsternopostazione = null where CF = cfDipendente1;
update DIPENDENTE set NumTelefonicoEsternopostazione = null where CF = cfDipendente2;
insert into TRASFERITO_A values (cfDipendente1,tempNumeroTelefonico2,curdate(),tempNomeSettore);
insert into TRASFERITO_A values (cfDipendente2,tempNumeroTelefonico1,tempNomeSettore);
commit;
END
CREATE
DEFINER=`root`@`localhost`
TRIGGER `directory_azienDale`.`TRASFERITO_A_BEFORE_INSERT`
BEFORE INSERT ON `directory_azienDale`.`TRASFERITO_A`
FOR EACH ROW
BEGIN
if (select CFDipendente from DA_TRASFERIRE_A where new.CFDipendente = DA_TRASFERIRE_A.CFDipendente) is not null
then delete from DA_TRASFERIRE_A where new.CFDipendente = DA_TRASFERIRE_A.CFDipendente;
end if;
if (select NumTelefonicoEsternopostazione
from TRASFERITO_A
where TRASFERITO_A.NumTelefonicoEsternopostazione = new.NumTelefonicoEsternopostazione
and TRASFERITO_A.CFDipendente = new.CFDipendente
and timestampdiff(year,TRASFERITO_A.`Data`,curdate()) <= 3
) is not null
then signal sqlstate '45004' set message_text = "ERROR: Un dipendente non può essere trasferito a una postazione dove è stato già trasferito meno di tre anni fa";
end if;
END
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>
#include <MysqL_time.h>
#include "controller.h"
#include "logger.h"
#define MAX_LENGTH 1024
struct op {
const char* name;
const char* params;
const char* stmt;
};
static const char* db_name = "directory_azienDale";
static const char* host_name = "localhost";
static struct op operations[NUM_OPS];
static char *opStrings[NUM_OPS];
void initController(){
// populates opStrings with literal form of opCode
// ...
opStrings[op2_2] = "op2_2";
// ...
// Populates structures in operations with data for each op. (Todo: data should be read from a file)
// ...
operations[op2_2].name = "scambiaDipendenti";
operations[op2_2].params = "cfDipendente1,cfDipendente2";
operations[op2_2].stmt = "call scambiaDipendenti(?,?)";
// ...
}
int prepareOp(MysqL *conn,enum opCode op,MysqL_STMT **stmtAddr){
// init stmt
if ((*stmtAddr = MysqL_stmt_init(conn)) == NULL){
logMsg(E,"MysqL_stmt_init: %s\n",MysqL_error(conn));
return 1;
}
// prepares stmt
MysqL_STMT *stmt = *stmtAddr;
if (MysqL_stmt_prepare(stmt,operations[op].stmt,strlen(operations[op].stmt)) != 0){
logMsg(E,"MysqL_stmt_prepare: %s\n",MysqL_stmt_error(stmt));
return 1;
}
return 0;
}
int launchOp(MysqL *conn,MysqL_STMT *stmt,MysqL_BIND *inParams){
// bind params
if (inParams != NULL && MysqL_stmt_bind_param(stmt,inParams)){
logMsg(E,"MysqL_stmt_bind_param: %s\n",MysqL_stmt_error(stmt));
return 1;
}
// execute statement
if (MysqL_stmt_execute(stmt)){
logMsg(D,"%s\n",MysqL_sqlstate(conn));
logMsg(E,"MysqL_stmt_execute: %s\n",MysqL_stmt_error(stmt));
return 1;
}
// buffers result set
if (MysqL_stmt_store_result(stmt) != 0){
logMsg(E,"MysqL_stmt_store_result: %s\n",MysqL_stmt_error(stmt));
return 1;
}
return 0;
}
int prepareAndLaunchOp(MysqL *conn,MysqL_BIND *inParams,MysqL_STMT **stmtAddr){
// prepares stmt
if (prepareOp(conn,op,stmtAddr)){
logMsg(E,"Failed to prepare statement\n");
return 1;
}
MysqL_STMT *stmt = *stmtAddr;
// launches op
if (launchOp(conn,stmt,"Failed to launch statement\n");
return 1;
}
return 0;
}
int printRes(MysqL_STMT* stmt,MysqL_RES *MetaRes,MysqL_BIND *resultSetCols){
MysqL_field_seek(MetaRes,0);
int resNumCol = MysqL_num_fields(MetaRes);
int width[resNumCol],res;
logMsg(I," ) ");
for (int c = 0; c < resNumCol; c ++){
printf("%s%n | ",MysqL_fetch_field(MetaRes) -> name,width + c);
}
printf("\n");
fflush(stdout);
for (int r = 0; ; r ++){
if ((res = MysqL_stmt_fetch(stmt)) == MysqL_NO_DATA){
break;
}
switch(res) {
case 1:
logMsg(E,"MysqL_stmt_fetch: %d\n",MysqL_stmt_error(stmt));
return 1;
case MysqL_DATA_TruncATED:
logMsg(W,"data truncation occurred\n",r);
case 0:
break;
}
logMsg(I,"%d) ",r);
for (int c = 0; c < resNumCol; c ++){
switch((resultSetCols + c) -> buffer_type){
case MysqL_TYPE_STRING:
case MysqL_TYPE_VAR_STRING:
case MysqL_TYPE_NEWDECIMAL:
printf("%*s | ",width[c],(*((bool *) ((resultSetCols + c) -> is_null)))? "NULL" : (char *) resultSetCols[c].buffer);
break;
case MysqL_TYPE_TINY:
case MysqL_TYPE_SHORT:
case MysqL_TYPE_INT24:
case MysqL_TYPE_LONG:
case MysqL_TYPE_LONGLONG:
if (*((bool *) ((resultSetCols + c) -> is_null))){
printf("%*s | ","NULL");
}
else {
printf("%*d | ",*((int *)(resultSetCols[c].buffer)));
}
break;
case MysqL_TYPE_FLOAT:
case MysqL_TYPE_DOUBLE:
if (*((bool *) ((resultSetCols + c) -> is_null))){
printf("%*s | ","NULL");
}
else {
printf("%*f | ",*((double *)(resultSetCols[c].buffer)));
}
break;
case MysqL_TYPE_DATE:
if (*((bool *) ((resultSetCols + c) -> is_null))){
printf("%s/ ","NULL");
}
else {
printf("%d/%d/%d | ",((MysqL_TIME *)(resultSetCols[c].buffer)) -> day,((MysqL_TIME *)(resultSetCols[c].buffer)) -> month,((MysqL_TIME *)(resultSetCols[c].buffer)) -> year
);
}
break;
default:
printf("(not supported) | ");
}
}
printf("\n");
}
fflush(stdout);
return 0;
}
void freeResultSet(MysqL_BIND *resultSetCols,int resNumCol){
for (int i = 0; i < resNumCol; i ++){
free(resultSetCols[i].buffer);
free(resultSetCols[i].length);
free(resultSetCols[i].is_null);
free(resultSetCols[i].error);
}
free(resultSetCols);
}
MysqL_BIND *callocResultSetCols(MysqL_RES *MetaRes){
int resNumCol = MysqL_num_fields(MetaRes);
MysqL_BIND *resultSetCols = calloc(resNumCol,sizeof(MysqL_BIND));
MysqL_FIELD *currentField;
MysqL_field_seek(MetaRes,0);
for (int i = 0; i < resNumCol; i ++){
currentField = MysqL_fetch_field(MetaRes);
resultSetCols[i].buffer_type = currentField -> type;
resultSetCols[i].buffer = calloc(MAX_LENGTH,sizeof(char));
resultSetCols[i].buffer_length = MAX_LENGTH;
resultSetCols[i].length = (unsigned long*) calloc(1,sizeof(unsigned long));
resultSetCols[i].is_null = (bool *) calloc(1,sizeof(bool));
resultSetCols[i].error = (bool *) calloc(1,sizeof(bool));
}
return resultSetCols;
}
int bindRes(MysqL_STMT *stmt,MysqL_BIND **resultSetColsAddr,MysqL_RES **MetaResAddr){
MysqL_RES *MetaRes;
MysqL_BIND *resultSetCols;
int resNumCol;
int numRes = 0;
if ((*MetaResAddr = MysqL_stmt_result_Metadata(stmt)) == NULL){
logMsg(E,"MysqL_stmt_result_Metadata: %s\n",MysqL_stmt_error(stmt));
return -1;
}
MetaRes = *MetaResAddr;
resNumCol = MysqL_num_fields(MetaRes);
if (resNumCol > 0){
numRes ++;
// binds result set dinamically
*resultSetColsAddr = callocResultSetCols(MetaRes);
resultSetCols = *resultSetColsAddr;
if (MysqL_stmt_bind_result(stmt,resultSetCols)){
logMsg(E,"MysqL_stmt_bind_result: %s\n",MysqL_stmt_error(stmt));
return -1;
}
}
return numRes;
}
// ...
int callOp2_2(MysqL *conn,int numOfArgs,char *cfDipendente1,char *cfDipendente2){
MysqL_STMT *stmt;
MysqL_BIND *resSet;
MysqL_BIND *inParams = calloc(numOfArgs,sizeof(MysqL_BIND));
MysqL_RES *MetaRes;
int hasNext;
unsigned long len[numOfArgs];
bool isNull[numOfArgs];
// prepares params
memset(isNull,false,sizeof(bool) * numOfArgs);
if (cfDipendente1 == NULL){
cfDipendente1 = "";
isNull[0] = true;
inParams -> is_null = isNull;
}
len[0] = sizeof(char) * strlen(cfDipendente1);
inParams -> buffer_type = MysqL_TYPE_STRING;
inParams -> buffer = cfDipendente1;
inParams -> buffer_length = len[0];
inParams -> length = len;
if (cfDipendente2 == NULL){
cfDipendente2 = "";
isNull[1] = true;
(inParams + 1) -> is_null = isNull;
}
len[1] = sizeof(char) * strlen(cfDipendente2);
(inParams + 1) -> buffer_type = MysqL_TYPE_STRING;
(inParams + 1) -> buffer = cfDipendente2;
(inParams+ 1) -> buffer_length = len[1];
(inParams + 1) -> length = len + 1;
// prepare and launches stmt
if (prepareAndLaunchOp(conn,op2_2,inParams,&stmt)){
logMsg(E,"Failed to prepare and launch statement\n");
return 1;
}
// binds res set
if (bindRes(stmt,&resSet,&MetaRes) <= 0){
logMsg(W,"Either Failed to bind a result set or no result set was available to bind\n");
}
// no res set has to be printed
// discards remaining result sets
do {MysqL_stmt_free_result(stmt);} while ((hasNext = MysqL_stmt_next_result(stmt) == 0));
if (hasNext > 0){
logMsg(E,"MysqL_stmt_next_result: %s\n",MysqL_stmt_error(stmt));
return 1;
}
// frees memory allocated dinamically
freeResultSet(resSet,MysqL_num_fields(MetaRes));
MysqL_free_result(MetaRes);
if (MysqL_stmt_close(stmt) != 0){
logMsg(E,"MysqL_stmt_close: %s\n",MysqL_error(conn));
return 1;
}
return 0;
}
// ...
int callOp(MysqL *conn,const enum opCode op,char *opArgs){
// Todo: Binding of in and out params and collecting the result set are demanded to the particular op
int res;
switch(op){
// ...
case op2_2:
//FIXME: sqlstate 45004 caught even if TRASFERITO_A is empty. Calling scambiaDipendenti with MysqL client result in success
res = callOp2_2(conn,2,strtok(opArgs,ARG_DEL),strtok(NULL,ARG_DEL));
break;
// ...
default:
logMsg(E,"There is no such operation with provided opCode\n");
return 1;
}
return res;
}
int connectToDB(char *username,char* passwd,MysqL** connAddr){
// Initialize connection
if ((*connAddr = MysqL_init(NULL)) == NULL){
int err = errno;
logMsg(E,"MysqL_init: %s\n",strerror(err));
return 1;
}
MysqL *conn = *connAddr;
// Tries to connect with db. NULL values are read from settings file
if ((MysqL_real_connect(conn,host_name,username,passwd,db_name,// port number
NULL,// socket name
CLIENT_MULTI_STATEMENTS )) == NULL){
logMsg(E,"MysqL_real_connect: %s\n",MysqL_error(conn));
return 1;
}
return 0;
}
// ...
解决方法
我发现了错误,它在callOp2_2中:
// ...
bool isNull[numOfArgs];
// ...
if (cfDipendente2 == NULL){
cfDipendente1 = "";
isNull[1] = true;
inParams -> is_null = isNull; // it should be inParams -> is_null = isNull + 1
}
这导致传递未定义的值以绑定到准备好的语句,从而导致不可预测的错误。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。