Con lo siguiente lo haces todo de manera simple y directa:
*------------------------------------------------------------------------------------------------------
*-- Ejemplo sin mostrar mensajes: ejecuta_bd(lcSQL, "cAlertasSolBod", "", "", .F.)
*------------------------------------------------------------------------------------------------------
FUNCTION ejecuta_bd(tcSQL, tcAlias, tcMensaje, tcError, tlMostrarMensajes)
LOCAL lnNumConexion, llResult
tcAlias=IIF(PCOUNT()<2, NULL, tcAlias)
tcMensaje=IIF(PCOUNT()<3, "", tcMensaje)
tlMostrarMensajes=IIF(PCOUNT()<5, .T., tlMostrarMensajes)
pone_espere(tcMensaje)
*------------------- CONEXION
lcErrorAudit = ""
lnNumConexion = conecta_bd(tlMostrarMensajes, @lcErrorAudit)
IF lnNumConexion < 0
saca_espere(tcMensaje)
*------------------- AUDITORIA
*auditoria_sql_crea_registro(tcSQL, tcAlias, lcErrorAudit)
RETURN .F.
ENDIF
*------------------- EJECUCION
IF ! ISNULL(tcAlias)
llResult = ejecuta_sql(lnNumConexion, tcSQL, tcAlias, "", @tcError, tlMostrarMensajes)
ELSE
llResult = ejecuta_sql(lnNumConexion, tcSQL, NULL, "", @tcError, tlMostrarMensajes)
ENDIF
*------------------- DESCONEXION
desconecta_bd(lnNumConexion, tlMostrarMensajes)
saca_espere(tcMensaje)
RETURN llResult
*------------------------------------------------------------------------------------------------------
*------------------------------------------------------------------------------------------------------
FUNCTION conecta_bd(tlMostrarMensajes, tcErrorAudit)
LOCAL lnConexion
IF ! SQLSETPROP(0,"DispLogin",3) > 0
IF tlMostrarMensajes
mensaje("Hubo un problema al conectarse a la base de datos.")
ENDIF
RETURN -1
ENDIF
lnConexion = SQLSTRINGCONNECT(gcCon)
IF lnConexion < 0
Local Array Errores[1]
AError(Errores)
lcMensajeError = "Hubo un problema al conectarse a la base de datos."+CRLF+CRLF+Errores[2]+':'+Transform(Errores[5])
IF tlMostrarMensajes
mensaje(lcMensajeError)
ENDIF
tcErrorAudit = lcMensajeError
ENDIF
*-- al parecer esto no serviria de mucho
*!* IF TYPE("_screen.otmrProcesosEspera") = "O"
*!* _screen.otmrProcesosEspera.Enabled = ! (lnConexion < 0)
*!* ENDIF
RETURN lnConexion
*------------------------------------------------------------------------------------------------------
*------------------------------------------------------------------------------------------------------
FUNCTION ejecuta_sql(tnNumConexion, tcSQL, tcAlias, tcMensaje, tcError, tlMostrarMensajes)
LOCAL lnResult, lcSQL, lnIDError
tcAlias=IIF(PCOUNT()<3, NULL, tcAlias)
tcMensaje=IIF(PCOUNT()<4, "", tcMensaje)
pone_espere(tcMensaje)
CURSORSETPROP('MapBinary', .T., 0)
IF ! ISNULL(tcAlias)
lnResult = SQLEXEC(tnNumConexion, tcSQL, tcAlias)
ELSE
lnResult = SQLEXEC(tnNumConexion, tcSQL)
ENDIF
IF lnResult <= 0
*-- Para quitar cualquier mensaje pendiente, que se supone ya no corre
*-- Deberia crear algo que quite todos los mensajes espere pendientes?
saca_espere()
*--
Local Array Errores[1]
AError(Errores)
*---------------------
loDatosError = CREATEOBJECT("EMPTY")
ADDPROPERTY(loDatosError, "tFechaHora", DATETIME())
ADDPROPERTY(loDatosError, "nError", errores(1))
ADDPROPERTY(loDatosError, "cMensaje", errores(2))
ADDPROPERTY(loDatosError, "cMetodo", errores(3))
ADDPROPERTY(loDatosError, "cCodigoFuente", MESSAGE(1))
ADDPROPERTY(loDatosError, "nLinea", LINENO())
ADDPROPERTY(loDatosError, "cMetodo1", PROGRAM(1))
ADDPROPERTY(loDatosError, "nLinea1", LINENO(1))
ADDPROPERTY(loDatosError, "cScript", tcSQL)
ADDPROPERTY(loDatosError, "cAlias", tcAlias)
lnIDError = GrabaError(loDatosError)
*-------------------------------------------------------------------------------
*-- ATENCIÓN: Me fijé que al mandar el error con RAISERROR, por ejemplo:
*-- errores(3) solo devolvía 511 caracteres, o sea, no devolvía el </msje>,
*-- y por lo tanto, STREXTRACT devolvía al programa un mensaje vacío!!!
*-------------------------------------------------------------------------------
*!* Mens 50000, Nivel 16, Estado 1, Procedimiento usp_RethrowError, Línea 65
*!* Error <50000>, Nivel 16, Estado 1, Procedimiento usp_pub_ValidarExistenciaCodigos, Línea 78, Mensaje: <msje>DETALLE ÓRDENES DE COMPRA
*!* Los siguientes código(s), no han sido encontrado(s),
*!* en el maestro de productos.
*!* 99464932 503 (línea 1)
*!* 11503020 503 (línea 2)
*!* 3228376R1 503 (línea 3)
*!* 3228376R91 503 (línea 4)
*!* 4785110 503 (línea 5)
*!* C5NE6303K 503 (línea 6)
*!* E1ADDN6303F (hasta aqui devuelve)503 (línea 7)
*!* FONN6300MA 503 (línea 8)
*!* K210100 503 (línea 9)
*!* ZZ90119 503 (línea 10)
*!* C5NN6303M 503 (línea 11)
*!* 4679788 503 (línea 12)
*!* </msje>
*-------------------------------------------------------------------------------
*-- Solución a lo anterior:
IF "<msje>" $ errores(3) AND NOT "</msje>" $ errores(3)
errores(3) = errores(3) + "...</msje>"
ENDIF
*-------------------------------------------------------------------------------
*------------------- AUDITORIA
*-- 20170929 ahora grabamos el script y alias en GrabaError() para tener la info de inmediato ocurrido el error
*auditoria_sql_crea_registro(tcSQL, tcAlias, errores(3))
*---------------------
*-- Antes en este trozo de código se utilizaba errores(3), pero a veces me devolvía el mensaje cortado.
*-- Entonces noté que errores(3) devolvía lo mismo completo.
*IF glCompilado
IF TYPE("tcError")!="C" && el error no se devuelve pasado por referencia
IF tlMostrarMensajes
MESSAGEBOX(msje_error_usuario_bd(errores(3), lnIDError), 48, "")
ENDIF
ELSE
tcError = msje_error_usuario_bd(errores(3), lnIDError)
ENDIF
*ENDIF
IF ! glCompilado AND .f.
IF AT("[SQL Server]", errores(3)) != 0 && no entiendo por que decia IF AT("[SQL Server]", tcMensaje) != 0
tcError = SUBSTR(errores(3), AT("[SQL Server]", errores(3)) + 12)
ELSE
tcError = errores(3)
ENDIF
_CLIPTEXT = tcSQL
IF MESSAGEBOX(tcError+CHR(13)+"********* SQL COPIADO EN EL PORTAPAPELES. ¿SUSPEND? ***********."+CHR(13)+tcSQL, 4+32+256) = 6 && SI
SUSPEND
ENDIF
ENDIF
*------------------- AUDITORIA
* auditoria_sql_crea_registro(tcSQL, tcAlias, errores(3))
RETURN .F.
*!* ELSE
*!* IF ! glCompilado
*!* _CLIPTEXT = tcSQL
*!* ENDIF
ENDIF
*------------------- AUDITORIA
*auditoria_sql_crea_registro(tcSQL, tcAlias, "") && esto no tiene efecto, no estamos grabando todos los sql
saca_espere(tcMensaje)
RETURN .T.
*------------------------------------------------------------------------------------------------------
*------------------------------------------------------------------------------------------------------
FUNCTION desconecta_bd(tnNumConexion, tlMostrarMensajes)
LOCAL lnResult
lnResult = SQLDISCONNECT(tnNumConexion)
IF lnResult < 0
IF tlMostrarMensajes
mensaje("Advertencia: hubo un problema al desconectarse.")
ENDIF
ENDIF
RETURN
*------------------------------------------------------------------------------------------------------
*------------------------------------------------------------------------------------------------------
FUNCTION msje_error_usuario_bd(tcMensaje, tnIDError)
* ¿ Estan las marcas "<" y ">" puestas por mi en usp_RethrowError en el mensaje de error sql server ?
lnPosDelim1 = AT("<", tcMensaje)
lnPosDelim2 = AT(">", tcMensaje)
IF lnPosDelim1 != 0 AND lnPosDelim2 != 0
lcNumErrorSQLServer = VAL(SUBSTR(tcMensaje, lnPosDelim1+1, lnPosDelim2-lnPosDelim1-1))
DO CASE
CASE lcNumErrorSQLServer = 2627 OR lcNumErrorSQLServer = 2601 && Violation of UNIQUE KEY constraint 'UQ_articulo_codigo'. Cannot insert duplicate key in object 'dbo.articulo'
lcMensajeUsuario = "" && por si acaso no pasara por ningún IF (error mio de código)
DO CASE
CASE LOWER("UQ_tbl_producto_correl") $ LOWER(tcMensaje)
lcMensajeUsuario = "No puede haber dos productos, con el mismo correlativo."
CASE LOWER("UQ_tbl_categoria_descripcion") $ LOWER(tcMensaje)
lcMensajeUsuario = "La descripción de la categoría no puede repetirse."
CASE LOWER("UQ_tbl_compra_grupo") $ LOWER(tcMensaje)
lcMensajeUsuario = "La descripción del grupo de compra no puede repetirse."
CASE LOWER("UQ_tbl_flujo_evento_tipo_descrip") $ LOWER(tcMensaje)
lcMensajeUsuario = "No pueden haber dos eventos con la misma descripción y tipo."
CASE LOWER("PK_tbl_sol_desp") $ LOWER(tcMensaje)
lcMensajeUsuario = "El número de solicitud indicado ya fue utilizado."
CASE LOWER("PK_tbl_guia") $ LOWER(tcMensaje)
lcMensajeUsuario = "El número de guía indicado o correlativo se repite en otra guía."
CASE LOWER("PK_tbl_toma_inv") $ LOWER(tcMensaje)
lcMensajeUsuario = "El número de toma de inventario indicado no es único."
CASE LOWER("PK_tbl_inventario") $ LOWER(tcMensaje)
lcMensajeUsuario = "El correlativo de los inventarios ya fue utilizado."
CASE LOWER("PK_tbl_ajuste_stock") $ LOWER(tcMensaje)
lcMensajeUsuario = "El número de ajuste de stock indicado no es único."
CASE LOWER("PK_tbl_detalle") $ LOWER(tcMensaje)
lcMensajeUsuario = "El nombre del detalle indicado no es único."
CASE LOWER("UQ_tbl_ch_vta_doc") $ LOWER(tcMensaje)
lcMensajeUsuario = "El número de doc. de venta, ha sido indicado anteriormente."
CASE LOWER("UQ_tbl_cartola") $ LOWER(tcMensaje)
lcMensajeUsuario = "El número de cartola se repite en el año indicado."
CASE LOWER("UQ_tbl_cargo_num_cheque") $ LOWER(tcMensaje)
lcMensajeUsuario = "El número de cheque ya fue utilizado."
CASE LOWER("UQ_tbl_normaliz_pmp_det") $ LOWER(tcMensaje)
lcMensajeUsuario = "Un producto solo puede tener una normalización de PMP."
CASE LOWER("UQ_tbl_con_comp_numero") $ LOWER(tcMensaje)
lcMensajeUsuario = "No puede haber dos comprobantes contables con el mimso número."
OTHERWISE
lcMensajeUsuario = "El número o código ya fue utilizado, especifique otro valor."
ENDCASE
CASE lcNumErrorSQLServer = 547
*-- Instrucción UPDATE en conflicto con la restricción CHECK "CK_tbl_ajuste_stock_total".
*-- El conflicto ha aparecido en la base de datos "hibrido_desa", tabla "dbo.tbl_ajuste_stock", column 'total'.</msje>
IF "CHECK" $ tcMensaje
DO CASE
CASE LOWER("CK_tbl_ajuste_stock_total") $ LOWER(tcMensaje)
lcMensajeUsuario = "El total del ajuste de stock debe ser mayor a cero." && no debiera darse
CASE LOWER("CK_tbl_articulo_codigo") $ LOWER(tcMensaje)
lcMensajeUsuario = "El código del producto no debe estar vacío."
CASE LOWER("CK_tbl_guia_det_cantidad") $ LOWER(tcMensaje)
lcMensajeUsuario = "La cantidad debe ser mayor a cero."
OTHERWISE && habría que tratar que no entre aqui, definiendo antes todos los CK
lcMensajeUsuario = "Hay un dato no permitido, como por ejemplo, un número fuera de rango."
ENDCASE
ELSE
IF "DELETE" $ tcMensaje
*-- The DELETE statement conflicted with the REFERENCE constraint "FK_venta_detalle_articulo". The conflict occurred in database "gestiona", table "dbo.venta_detalle", column 'id_articulo'
lcMensajeUsuario = "Los datos no pueden ser eliminados, porque son utilizados en otro(s) archivo(s)."
ELSE
*-- Instrucción UPDATE en conflicto con la restricción FOREIGN KEY "FK_tbl_compra_proy_det_tbl_provee". El conflicto ha aparecido en la base de datos "hibrido_desa", tabla "dbo.tbl_provee".
lcMensajeUsuario = "Los datos no pueden ser insertados o modificados, porque no existen en los archivos relacionados."
ENDIF
ENDIF
CASE lcNumErrorSQLServer = 50000 && error en un trigger (en general los provocados por RAISERROR)
*lcMensajeUsuario = SUBSTR(tcMensaje, AT("Mensaje: ", tcMensaje) + 9)
lcMensajeUsuario = STREXTRACT(tcMensaje, "<msje>", "</msje>")
OTHERWISE
&& no deberia pasar por aca (rectifico, si hay casos que no controlan el error en el sp de la misma forma)
lcMensajeUsuario = mensaje_error_estandar(tnIDError)
ENDCASE
ELSE && cuando usp_RethrowError no está de por medio
*-- Manera poco precisa de determinar el error ocurrido (por los sql enviados directamente desde el programa)
DO CASE
CASE "UNIQUE KEY" $ tcMensaje
lcMensajeUsuario = "El número o código ya fue utilizado, especifique otro valor."
CASE "DELETE" $ tcMensaje AND "REFERENCE" $ tcMensaje && supuestamente error 547
lcMensajeUsuario = "Los datos no pueden ser eliminados, porque son utilizados en otro(s) archivo(s)."
CASE "FOREIGN KEY" $ tcMensaje
lcMensajeUsuario = "Los datos no pueden ser insertados o modificados, porque no existen en los archivos relacionados."
OTHERWISE
lcMensajeUsuario = mensaje_error_estandar(tnIDError)
ENDCASE
*!* IF AT("[SQL Server]", tcMensaje) != 0
*!* lcMensajeUsuario = "Error: "+SUBSTR(tcMensaje, AT("[SQL Server]", tcMensaje) + 12)
*!* ELSE
*!* lcMensajeUsuario = "Error: "+tcMensaje
*!* ENDIF
ENDIF
RETURN lcMensajeUsuario
*------------------------------------------------------------------------------------------------------
*- Llamado desde GrabaError y desde aca en msje_error_usuario_bd
FUNCTION mensaje_error_estandar(tnIDError)
RETURN "Se produjo una incidencia. Contáctese con soporte técnico si persiste."+CHR(13)+CHR(13)+;
"De todas formas, se ha enviado el reporte nº "+ALLTRIM(STR(tnIDError))+" con los detalles."