| /********************************************************************** |
| // @@@ START COPYRIGHT @@@ |
| // |
| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| // |
| // @@@ END COPYRIGHT @@@ |
| // |
| **********************************************************************/ |
| /* -*-C++-*- |
| * **************************************************************************** |
| * |
| * File: |
| * Description: This file is intended to hold that portion of the |
| * implementation of the ComDiagsArea/ComCondition classes |
| * that does not belong in the "../common" directory by |
| * virtue of making use of non-kosher runtime libraries. |
| * Note, for example, the inclusion of the iostream.h header... |
| * |
| * Created: 3/17/96 (St. Patrick's Day) |
| * Language: C++ |
| * Status: Fresh out of the oven; warm to the touch; tasty? |
| * |
| * |
| * |
| * **************************************************************************** |
| */ |
| #include "Platform.h" |
| |
| |
| #include "BaseTypes.h" |
| #include "NAWinNT.h" |
| #include <ctype.h> |
| |
| #include <iostream> |
| #include <stdarg.h> |
| |
| #if !defined(__GNUC__) || __GNUC__ < 3 |
| #include <strstream> |
| #endif |
| |
| |
| |
| #include "GetErrorMessage.h" |
| #include "str.h" |
| #include "ComDiags.h" |
| #include "copyright.h" |
| #include "ErrorMessage.h" |
| #include "NAString.h" |
| #include "nawstring.h" |
| #include "NLSConversion.h" |
| #include "SqlciError.h" |
| #include "csconvert.h" |
| |
| // What comes next are two parallel declarations: some enum values |
| // and an array of constant strings which we can use to refer to |
| // message text tokens. |
| // |
| // Let's start first with the enumerations and then give the strings. |
| // |
| // Wouldn't it be better to make this type definition a private member |
| // of ComCondition or is what we've done here satisfactory? |
| // |
| // ********WARNING!!! |
| // This enumeration definition must match the array of char* definition |
| // that comes below. |
| enum TOKEN_ENUM { |
| STRING0, STRING1, STRING2, STRING3, STRING4, |
| INT0, INT1, INT2, INT3, INT4, |
| INTERNAL_SQLCODE, |
| CONDITION_NUMBER, |
| RETURNED_SQLSTATE, |
| CLASS_ORIGIN, |
| SUBCLASS_ORIGIN, |
| SERVER_NAME, |
| CONNECTION_NAME, |
| CONSTRAINT_CATALOG, |
| CONSTRAINT_SCHEMA, |
| CONSTRAINT_NAME, |
| TRIGGER_CATALOG, |
| TRIGGER_SCHEMA, |
| TRIGGER_NAME, |
| CATALOG_NAME, |
| SCHEMA_NAME, |
| TABLE_NAME, |
| COLUMN_NAME, |
| CURSOR_NAME, |
| ROW_NUMBER, |
| NSK_CODE, |
| MAX_TOKEN |
| }; |
| |
| // These names will be of full length. The comparison will |
| // be made after converting a given string to all lower case, |
| // so these names are given in lower case internally. |
| // |
| // Wouldn't it maybe be better to make "tokens" a private |
| // member (albeit, static) of ComCondition, or is it |
| // satisfactory to use the C-language-style information hiding, such |
| // as we do here, using static? |
| // |
| // ******WARNING!!! |
| // This char* array definition must match the enumeration definition |
| // that came before. |
| |
| static const NAWchar *const tokens[MAX_TOKEN] = { |
| WIDE_("string0"), WIDE_("string1"), WIDE_("string2"), WIDE_("string3"), |
| WIDE_("string4"), |
| WIDE_("int0"), WIDE_("int1"), WIDE_("int2"), WIDE_("int3"), |
| WIDE_("int4"), |
| WIDE_("sqlcode"), |
| WIDE_("conditionnumber"), |
| WIDE_("returnedsqlstate"), |
| WIDE_("classorigin"), |
| WIDE_("subclassorigin"), |
| WIDE_("servername"), |
| WIDE_("connectionname"), |
| WIDE_("constraintcatalog"), |
| WIDE_("constraintschema"), |
| WIDE_("constraintname"), |
| WIDE_("triggercatalog"), |
| WIDE_("triggerschema"), |
| WIDE_("triggername"), |
| WIDE_("catalogname"), |
| WIDE_("schemaname"), |
| WIDE_("tablename"), |
| WIDE_("columnname"), |
| WIDE_("cursorname"), |
| WIDE_("rownumber"), |
| WIDE_("nskcode") |
| }; |
| |
| // MSG_BUF_SIZE is 2K which is the maximum msg length. |
| // We want extra 2K to expand the parameters of the error msg. |
| // Note that we actually allocate more than DEST_BUF_SIZE |
| // which account for the size of string params. |
| static const UInt32 DEST_BUF_SIZE = 2 * ErrorMessage::MSG_BUF_SIZE; |
| |
| // little helper method for ComSQLSTATE() |
| |
| static inline void Encode36(char &dst, Int32 src) |
| { |
| char tc = (char) src; |
| dst = (tc < 10 ? '0' + tc : 'A' + tc - 10); |
| } |
| |
| NABoolean GetSqlstateInfo(Lng32 sqlcode, char * sqlstate, |
| NABoolean &fabricatedSqlstate); |
| void AddSqlstateInfo(Lng32 sqlcode, char * sqlstate, |
| NABoolean fabricatedSqlstate); |
| NABoolean ComSQLSTATE(Lng32 theSQLCODE, char *theSQLSTATE) |
| { |
| // --------------------------------------------------------------------- |
| // Compute SQLSTATE value from SQLCODE. |
| // |
| // There are basically 6 different cases for doing this: |
| // |
| // 1. The easy case for SQLCODE values 0 and 100: |
| // |
| // Use a hard-coded str_cpy method. |
| // |
| // All cases other than 1 will require a lookup in the message file. |
| // |
| // 2. The case where a hard-coded SQLSTATE was found in the message |
| // file, and where this SQLSTATE matches the sign of SQLCODE: |
| // |
| // What we mean by "matching sign" is that SQLSTATE starts with |
| // "01" for a warning and with anything other than "00" "01" "02" |
| // for an error. In this case we return the hard-coded SQLSTATE. |
| // |
| // 3. The case where a hard-coded SQLSTATE was found but it does |
| // not match the sign of the SQLCODE: |
| // |
| // In this case we "fabricate" an SQLSTATE, but we'll put a |
| // "W" into the second character of SQLSTATE if SQLCODE was < 0. |
| // That's just as a little reminder. Continue with cases 4 or 5. |
| // |
| // 4. The case where the message file has a "ZZZZZ" as the SQLSTATE |
| // value, and SQLCODE is < 0 (Error): |
| // |
| // In this case we'll return Xaabb as the SQLSTATE, where aa is |
| // the SQLCODE/1000 encoded in base 36, and bb is SQLCODE % 1000 |
| // encoded in base 36. Note that if we came here from case 3 the |
| // returned SQLSTATE is XWabb. |
| // |
| // 5. The case where the message file has a "ZZZZZ" as the SQLSTATE |
| // value, and SQLCODE is > 0 (Warning): |
| // |
| // In this case we'll return 01abb as the SQLSTATE. a is a kludgy |
| // encoding of SQLCODE/1000 and bb is the same as in case 4. We do |
| // this because we don't have enough characters available for |
| // warnings to use the more simple encoding of step 4. |
| // |
| // 6. The case where we don't find the matching message file entry: |
| // |
| // We treat this the same as cases 4 and 5. |
| // |
| // The return value of this function is TRUE for all fabricated states; |
| // it's FALSE for all valid states found in the message file |
| // and for states we were able to map successfully on a code/state mismatch. |
| // |
| // Thus, the fabricated error states have SQL/MX class origin (X_) |
| // while the fab warnings have ISO-9075 class origin (01) but SQL/MX |
| // subclass origin. |
| // |
| // Please be aware of the encoding in step 5 when adding new ranges of |
| // error codes. Choosing the wrong range or failing to update this code |
| // may result in SQLSTATE values that are not longer unique and can't |
| // always be mapped back to SQLCODE values!!! |
| // --------------------------------------------------------------------- |
| |
| NABoolean fabricatedSqlstate = FALSE; |
| if (theSQLCODE == 0) |
| { |
| str_cpy_all(theSQLSTATE,"00000",6); |
| return FALSE; |
| } |
| |
| if (theSQLCODE == 100) |
| { |
| str_cpy_all(theSQLSTATE,"02000",6); |
| return FALSE; |
| } |
| |
| // see if this sqlcode exists in the in-memory list |
| if (GetSqlstateInfo(theSQLCODE, theSQLSTATE, fabricatedSqlstate)) |
| return fabricatedSqlstate; |
| |
| // We need to lookup the SQLSTATE for this SQLCODE. |
| NAWchar* source; |
| |
| // Get the Message string for this error. The first field is the SQLSTATE. |
| NABoolean sourceIsNotKnown = GetErrorMessage(theSQLCODE, source, SQL_STATE); |
| |
| NABoolean sourceIsAWarning = FALSE; |
| NABoolean sourceIsInverted = FALSE; |
| |
| if (!sourceIsNotKnown) |
| { |
| // Message found. |
| // If the first field is 'ZZZZZ' then the SQLSTATE is not known, |
| // and we'll fabricate a special one. |
| |
| if (NAWstrcmp(source, WIDE_("ZZZZZ")) == 0) |
| sourceIsNotKnown = TRUE; |
| else |
| { |
| // Not "ZZZZZ" -- it's a known SQLSTATE. |
| |
| //UR2 CNTNSK. The State is always in ASCII. |
| // Add a NULL at the end. |
| Int32 numBytes = |
| UnicodeStringToLocale(CharInfo::ISO88591, |
| source, 5, theSQLSTATE, 5, FALSE); |
| |
| if (numBytes != 5) |
| *theSQLSTATE = '\0'; |
| else |
| theSQLSTATE[5] = '\0'; |
| |
| // Ok, it's known, but is it valid for this condition? |
| // All Ansi warnings begin with "01... (warning)" or "02000 (no data)". |
| sourceIsAWarning = (theSQLSTATE[0] == '0' && |
| (theSQLSTATE[1] == '1' || |
| theSQLSTATE[1] == '2')); |
| if ((theSQLCODE > 0 && sourceIsAWarning) || |
| (theSQLCODE < 0 && !sourceIsAWarning)) |
| goto saveAndReturnSqlstate; // it is valid |
| // return FALSE; // it is valid |
| |
| // Here we have a mismatch between SQLCODE and SQLSTATE. |
| // We try to map a warning state to error, and vice versa. |
| // This is symmetric for "01004/22001 string data, right truncation". |
| // The mapping of syntax errors (42000 etc) to generic warning (01000) |
| // is NOT symmetric (of course you can't convert a generic warning |
| // to any meaningful specific error state...). |
| // |
| if (sourceIsAWarning) |
| { |
| if (strcmp(theSQLSTATE, "01004") == 0) // string data, right truncation |
| { |
| str_cpy_all(theSQLSTATE,"22001",6); // warning -> error |
| goto saveAndReturnSqlstate; |
| // return FALSE; |
| } |
| } |
| else |
| { |
| if (strcmp(theSQLSTATE, "22001") == 0) // string data, right truncation |
| { |
| str_cpy_all(theSQLSTATE,"01004",6); // error -> warning |
| goto saveAndReturnSqlstate; |
| // return FALSE; |
| } |
| } |
| sourceIsInverted = TRUE; // not valid, no mapping |
| } // known source |
| } |
| |
| // If message is not found or a known and valid SQLSTATE was not found, |
| // fabricate an implementation-defined SQLSTATE. |
| |
| // split the SQLCODE value into a left and a right part at the 1000s boundary |
| #define AZ09 36 |
| Lng32 codeLeft; |
| Lng32 codeRight; |
| codeLeft = ABS(theSQLCODE); |
| codeRight = codeLeft % 1000; |
| codeLeft = codeLeft / 1000; |
| |
| // Set first the three characters of SQLSTATE, based on whether this is an |
| // error or a warning, and based on the left part of the SQLCODE. |
| if (theSQLCODE < 0) |
| { |
| // error: use a SQL/MX SQLSTATE class starting with 'X' |
| theSQLSTATE[0] = 'X'; |
| if (sourceIsAWarning) |
| theSQLSTATE[1] = 'W'; |
| else |
| if (codeLeft / AZ09 < AZ09) |
| Encode36(theSQLSTATE[1],codeLeft / AZ09); |
| else |
| theSQLSTATE[1] = 'Z'; |
| Encode36(theSQLSTATE[2],codeLeft % AZ09); |
| } |
| else |
| { |
| // warning: use ANSI SQLSTATE class '01' |
| theSQLSTATE[0] = '0'; |
| theSQLSTATE[1] = '1'; |
| // sorry, not enough space to encode "sourceIsAWarning" and all |
| // of codeLeft |
| char state2; |
| // Note the knowledge about which ranges of error codes are used. |
| // Note also that if additional ranges are used, we could either go |
| // to the default handling (some SQLSTATEs might be returned for two |
| // different SQLCODEs, or we could add a case as long as one of the |
| // characters [5-9I-Z] are still available. |
| switch (codeLeft) |
| { |
| case 1: // 5 |
| case 2: // 6 |
| case 3: // 7 |
| case 4: // 8 |
| case 5: // 9 - not really used |
| state2 = '4' + (char) codeLeft; |
| break; |
| case 6: // I |
| case 7: // J |
| case 8: // K |
| case 9: // L |
| case 10: // M |
| case 11: // N |
| case 12: // O - not used so far (Nov 2001) |
| case 13: // P |
| case 14: // Q - not used so far (Nov 2001) |
| case 15: // R |
| state2 = 'I' + (char) (codeLeft - 6); |
| break; |
| case 19: // S |
| state2 = 'S'; |
| break; |
| case 20: // T |
| state2 = 'T'; |
| break; |
| case 30: // U |
| state2 = 'U'; |
| break; |
| |
| // V, W, X are still for sale, and some of the ranges above |
| // are also not yet used |
| |
| default: |
| state2 = 'Y' + (char) (codeLeft % 2); |
| break; |
| } |
| theSQLSTATE[2] = state2; |
| } |
| |
| // Encode the right part of SQLCODE as 2 digits with base 36 |
| // and put it into the last 2 characters of the subclass. |
| Encode36(theSQLSTATE[3], codeRight / AZ09); |
| Encode36(theSQLSTATE[4], codeRight % AZ09); |
| |
| // add NUL terminator |
| theSQLSTATE[5] = '\0'; |
| fabricatedSqlstate = TRUE; // it is fabricated |
| |
| saveAndReturnSqlstate: |
| AddSqlstateInfo(theSQLCODE, theSQLSTATE, fabricatedSqlstate); |
| return fabricatedSqlstate; |
| |
| |
| } |
| |
| static const char *returnClassOrigin(Lng32 theSQLCODE, size_t offset) |
| { |
| char sqlstate[6]; |
| ComSQLSTATE(theSQLCODE,sqlstate); |
| // Classes and subclasses starting with letters '0' ... '4', 'A' ... 'H' are |
| // defined by ISO/ANSI SQL92. See subclause 18.1, GR 3b) and subclause 22.1. |
| |
| if ((sqlstate[offset] >= '0' && sqlstate[offset] <= '4') || |
| (sqlstate[offset] >= 'A' && sqlstate[offset] <= 'H')) |
| return "ISO 9075"; |
| else |
| return (const char *)COPYRIGHT_XTOP_PRODNAME_H; |
| } |
| |
| const char *ComClassOrigin(Lng32 theSQLCODE) |
| { |
| // The class is stored in the first two characters of SQLSTATE. |
| return returnClassOrigin(theSQLCODE, 0); |
| } |
| |
| const char *ComSubClassOrigin(Lng32 theSQLCODE) |
| { |
| // The subclass is stored in the last three characters of SQLSTATE. |
| return returnClassOrigin(theSQLCODE, 2); |
| } |
| |
| void ComCondition::getSQLSTATE(char *theSQLSTATE) const |
| { |
| if(NULL == customSQLState_){ |
| // If the SQLCODE shows it's a SIGNAL statement, don't call |
| // the ComSQLSTATE() function - instead use the SQLSTATE value |
| // supplied by the user, in optionalString_[0]. |
| if (theSQLCODE_ == -ComDiags_SignalSQLCODE) |
| str_cpy_all(theSQLSTATE, getOptionalString(0), 5); |
| else |
| ComSQLSTATE(theSQLCODE_,theSQLSTATE); |
| } |
| else{ |
| // caller providing space in char* theSQLSSTATE. |
| str_cpy_all(theSQLSTATE, customSQLState_, 6); |
| } |
| } |
| |
| const char * ComCondition::getClassOrigin() const |
| { |
| return ComClassOrigin(theSQLCODE_); |
| } |
| |
| const char * ComCondition::getSubClassOrigin() const |
| { |
| return ComSubClassOrigin(theSQLCODE_); |
| } |
| |
| // To get the message length, we first make sure that there |
| // indeed exists a message. Once the message has been established, |
| |
| // that guarantees that the messageLen_ member has a valid value, |
| // and we return that value. |
| // |
| // The octet length is just the message length --- as of this writing anyway. |
| |
| ComDiagBigInt ComCondition::getMessageLength() |
| { |
| if (!isLocked_) |
| getMessageText(); |
| return messageLen_; |
| } |
| |
| ComDiagBigInt ComCondition::getMessageOctetLength() |
| { |
| return getMessageLength(); |
| } |
| |
| // Ancillary routines for ComCondition::getMessageText() |
| // (could be made regular ComCondition methods) |
| |
| // This function is used in other pieces |
| // of code so that if by chance the programmer leaves NULL one |
| // of the char* strings used in instantiating message text, |
| // we don't get an assertion failure, but a more graceful result. |
| |
| const char *getSafeString(const char *const cp) |
| { |
| if (!cp) |
| return ""; |
| else |
| return cp; |
| } |
| |
| const NAWchar *getSafeWString(const NAWchar*const cp) |
| { |
| if (!cp) |
| return WIDE_(""); |
| else |
| return cp; |
| } |
| |
| |
| // Convert s into Unicode UTF16 and append to dest. |
| // We assume that there is space for 's' in 'dest'. |
| void appendSafeStringInW(NAWchar* dest, |
| CollHeap *heap, |
| const char* s, |
| CharInfo::CharSet cs = CharInfo::ISO88591) |
| { |
| const char* src = getSafeString(s); |
| UInt32 len = strlen(src); |
| |
| if (cs == CharInfo::ISO88591) |
| { |
| // Keep the old "pass through" behavior so |
| // use of ISO 8859-15 characters (a.k.a., Latin-9) in |
| // CHARACTER SET ISO88591 target column continues to work. |
| |
| NAWcharBuf* res = NULL; // must set this variable to NULL to force |
| // the following call to allocate space for |
| // the output buffer |
| res = ISO88591ToUnicode(charBuf((unsigned char*)src, len), |
| // const charBuf& iso88591String |
| heap, // CollHeap *heap |
| res, // NAWcharBuf*& unicodeString |
| TRUE); // NABoolean addNullAtEnd |
| if (res && res->getStrLen() > 0) |
| { |
| NAWsprintf(dest, WIDE_("%s"), res->data()); // append to dest |
| } |
| else |
| dest[0] = WIDE_('\0'); // so later call to NAWstrlen(dest) return 0 |
| |
| NADELETE(res, NAWcharBuf, heap); |
| |
| return; // we are done; exit the routine |
| |
| } // if (cs == CharInfo::ISO88591) |
| |
| // |
| // (cs != CharInfo::ISO88591) |
| // |
| |
| enum cnv_charset convCS = convertCharsetEnum(cs); |
| if (convCS == cnv_UnknownCharSet) |
| { |
| dest[0] = WIDE_('\0'); // so later call to NAWstrlen(dest) return 0 |
| return; |
| } |
| |
| // Allocate a temporary output buffer of len+1 NAWchar's |
| NAWcharBuf* pOutputBuf = NULL; |
| pOutputBuf = checkSpace(heap, len /* # of NAWchar's */, |
| pOutputBuf, TRUE /* addNullAtEnd */); |
| if (pOutputBuf == NULL) // not supposed to happen... |
| { |
| dest[0] = WIDE_('\0'); // so later call to NAWstrlen(dest) return 0 |
| return; |
| } |
| |
| char * pFirstUntranslatedChar = NULL; |
| UInt32 outputDataLenInBytes = 0; |
| UInt32 translatedtCharCount = 0; |
| Int32 convStatus = |
| LocaleToUTF16(cnv_version1, // const enum cnv_version version |
| src, // const char *in_bufr |
| len, // const int in_len -- src str len in num of bytes |
| (const char *)pOutputBuf->data(), // const char *out_bufr |
| (const Int32)((len+1)*sizeof(NAWchar)), // output buffer size in # of bytes |
| convCS, // enum cnv_charset charset -- output cs |
| pFirstUntranslatedChar, // char * & first_untranslated_char |
| &outputDataLenInBytes, // unsigned int *output_data_len_p |
| 0, // const int cnv_flags (default is 0) |
| (const Int32)TRUE, // const int addNullAtEnd_flag |
| &translatedtCharCount); // unsigned int *translated_char_cnt_p |
| UInt32 outLenInW = outputDataLenInBytes/sizeof(NAWchar); |
| pOutputBuf->data()[len] = WIDE_('\0'); // needed when conversion errors occured |
| switch (convStatus) |
| { |
| case 0: // success |
| // assert (pOutputBuf->data()[outLenInW-1] == WIDE_'\0'); |
| NAWsprintf(dest, WIDE_("%s"), pOutputBuf->data()); // append to dest |
| break; |
| case CNV_ERR_BUFFER_OVERRUN: |
| if (outLenInW <= len) |
| pOutputBuf->data()[outLenInW] = WIDE_('\0'); |
| NAWsprintf(dest, WIDE_("%s"), pOutputBuf->data()); // append to dest |
| break; |
| case CNV_ERR_INVALID_CHAR: |
| if (outLenInW < len) |
| { |
| pOutputBuf->data()[outLenInW] = WIDE_('?'); // substitute char |
| pOutputBuf->data()[outLenInW+1] = WIDE_('\0'); |
| } |
| NAWsprintf(dest, WIDE_("%s"), pOutputBuf->data()); // append to dest |
| // skip the remaining characters in the source string |
| break; |
| case CNV_ERR_NOINPUT: // okay |
| case CNV_ERR_INVALID_CS: // not supposed to happen |
| case CNV_ERR_INVALID_VERS: // not supposed to happen |
| default: |
| dest[0] = WIDE_('\0'); // so later call to NAWstrlen(dest) return 0 |
| break; |
| } |
| |
| NADELETE(pOutputBuf, NAWcharBuf, heap); |
| } |
| |
| NABoolean safeStringCheck(const char *const cp) |
| { |
| if (!cp) |
| return FALSE; // False if cp is NULL |
| else |
| return cp != ""; // False if cp points to ""; True if non-empty string |
| } |
| |
| |
| // Helper method |
| // We compute the buffer size as |
| // DEST_BUF_SIZE + size of the string parameters |
| // + size of the catalog/schema/table/etc. parameters |
| // String params can be long and we will show the entire params |
| // by allocating the memory dynamically. |
| UInt32 computeMsgLen(const ComCondition &cond) |
| { |
| UInt32 totallen = DEST_BUF_SIZE, len = 0; |
| |
| for (Int32 index=0; index < ComCondition::NumOptionalParms; ++index) |
| { |
| CharInfo::CharSet cs = cond.getOptionalStringCharSet(index); |
| if (CharInfo::isSingleByteCharSet(cs)) |
| { |
| const char* optStr = cond.getOptionalString(index); |
| len = (optStr) ? strlen(optStr) : 0; |
| } |
| else |
| { |
| const NAWchar* optWStr = cond.getOptionalWString(index); |
| len = (optWStr) ? NAWstrlen(optWStr) : 0; |
| } |
| |
| totallen += len; |
| } |
| |
| if (cond.getConstraintCatalog()) totallen += strlen(cond.getConstraintCatalog()); |
| if (cond.getConstraintSchema()) totallen += strlen(cond.getConstraintSchema()); |
| if (cond.getConstraintName()) totallen += strlen(cond.getConstraintName()); |
| if (cond.getTriggerCatalog()) totallen += strlen(cond.getTriggerCatalog()); |
| if (cond.getTriggerSchema()) totallen += strlen(cond.getTriggerSchema()); |
| if (cond.getTriggerName()) totallen += strlen(cond.getTriggerName()); |
| if (cond.getCatalogName()) totallen += strlen(cond.getCatalogName()); |
| if (cond.getSchemaName()) totallen += strlen(cond.getSchemaName()); |
| if (cond.getTableName()) totallen += strlen(cond.getTableName()); |
| if (cond.getColumnName()) totallen += strlen(cond.getColumnName()); |
| if (((ComCondition&)cond).getCustomSQLState()) |
| totallen += strlen(((ComCondition&)cond).getCustomSQLState()); |
| |
| return totallen; |
| } |
| |
| NAWString* terseParamDisplay(const ComCondition &cc, CollHeap *p) |
| { |
| NAWString* dest = NULL; |
| if ( !p ) { |
| dest = new NAWString; |
| } else |
| dest = new (p) NAWString(p); |
| |
| if ( dest == NULL ) |
| return NULL; |
| |
| UInt32 expandedBufferLen = computeMsgLen(cc); |
| NAWchar *wbuffer = new (p) NAWchar[expandedBufferLen]; |
| |
| #define STRING_TO_DEST(x_, cs_) \ |
| { *dest += L' '; \ |
| appendSafeStringInW(wbuffer,p, x_, cs_); \ |
| dest -> append(wbuffer, NAWstrlen(wbuffer)); } |
| |
| #define WSTRING_TO_DEST(x_) \ |
| { *dest += L' '; \ |
| *dest -> append(getSafeWString(x_), NAWstrlen(getSafeWString(x_))); } |
| |
| #define INT_TO_DEST(x_) \ |
| { NAWsprintf(wbuffer, WIDE_("%d"), x_); WSTRING_TO_DEST(wbuffer); } |
| |
| #define STRING_TO_DEST_IF(x_) \ |
| { if (safeStringCheck(x_)) STRING_TO_DEST(x_, CharInfo::ISO88591); } |
| |
| // Always display the optional string message params (as blanks if absent). |
| Int32 i=0; |
| for (; i<ComCondition::NumOptionalParms; i++) { |
| if (CharInfo::isSingleByteCharSet(cc.getOptionalStringCharSet(i))) { |
| STRING_TO_DEST(cc.getOptionalString(i), cc.getOptionalStringCharSet(i)); |
| } |
| else { |
| WSTRING_TO_DEST(cc.getOptionalWString(i)); |
| } |
| } |
| |
| // Display everything else only if it was passed in (initialized). |
| |
| for (i=0; i<ComCondition::NumOptionalParms; i++) |
| if (cc.getOptionalInteger(i) != ComDiags_UnInitialized_Int) |
| INT_TO_DEST(cc.getOptionalInteger(i)); |
| |
| STRING_TO_DEST_IF(cc.getServerName()); |
| STRING_TO_DEST_IF(cc.getConnectionName()); |
| STRING_TO_DEST_IF(cc.getConstraintCatalog()); |
| STRING_TO_DEST_IF(cc.getConstraintSchema()); |
| STRING_TO_DEST_IF(cc.getConstraintName()); |
| STRING_TO_DEST_IF(cc.getTriggerCatalog()); |
| STRING_TO_DEST_IF(cc.getTriggerSchema()); |
| STRING_TO_DEST_IF(cc.getTriggerName()); |
| STRING_TO_DEST_IF(cc.getCatalogName()); |
| STRING_TO_DEST_IF(cc.getSchemaName()); |
| STRING_TO_DEST_IF(cc.getTableName()); |
| STRING_TO_DEST_IF(cc.getColumnName()); |
| STRING_TO_DEST_IF(cc.getSqlID()); |
| |
| if (cc.getRowNumber() != ComCondition::INVALID_ROWNUMBER) INT_TO_DEST(cc.getRowNumber()); |
| if (cc.getNskCode()) INT_TO_DEST(cc.getNskCode()); |
| |
| // For now, ignoring SQLSTATE, classOrigin, subClassOrigin |
| |
| // remove trailing blanks |
| size_t j; |
| if (j = dest->length()) { // |
| for ( ; j--; ) |
| if (!NAWisspace((*dest)[j])) |
| break; |
| dest->remove(++j); |
| } |
| |
| if (dest->length() > expandedBufferLen - 100) // prevent msg overflow |
| dest->remove(expandedBufferLen - 100); // (100 is a fudge factor) |
| |
| NADELETEBASICARRAY(wbuffer, p); |
| return dest; |
| } |
| |
| |
| #ifndef NDEBUG |
| Int32 displayWCHAR(NAWchar* wstr, NAWchar* wend = NULL) // for debugging |
| { |
| unsigned char str[2000+1]; |
| if (wend) *wend = '\0'; |
| Int32 i=0; |
| for (; i<2000 && wstr[i]; i++) |
| str[i] = (unsigned char)wstr[i]; |
| str[i] = '\0'; |
| cerr << "{{{" << endl << str << "}}}" << endl; |
| return i; |
| } |
| #endif // NDEBUG |
| |
| // |
| // ComCondition::getMessageEMSSeverity() |
| // |
| void ComEMSSeverity(Lng32 theSQLCODE, char *theEMSSeverity) |
| { |
| // this function should not have been called for these |
| // sqlcodes but just in case |
| // "UUUUU" is an undefined severity |
| if ((theSQLCODE == 0) || (theSQLCODE == 100)) |
| { |
| str_cpy_all(theEMSSeverity,"UUUUU",6); |
| return; |
| } |
| // if it's a warning then lower the severity to 'informational' |
| if (theSQLCODE >0) |
| { |
| str_cpy_all(theEMSSeverity,"INFRM",6); |
| return; |
| } |
| |
| // We need to lookup the EMS_SEVERITY for this SQLCODE. |
| NAWchar* source; |
| |
| // Get the Message string for this error. The fourth field is the EMS_SEVERITY. |
| NABoolean sourceIsNotKnown = GetErrorMessage(theSQLCODE, source, EMS_SEVERITY); |
| |
| if (!sourceIsNotKnown) |
| { |
| // Message found. |
| Int32 numBytes = |
| UnicodeStringToLocale(CharInfo::ISO88591, |
| source, 5, theEMSSeverity, 5, FALSE); |
| |
| if (numBytes != 5) |
| *theEMSSeverity = '\0'; |
| else |
| theEMSSeverity[5] = '\0'; |
| } |
| } |
| |
| // |
| // ComCondition::getMessageEMSEventTarget() |
| // |
| void ComEMSEventTarget(Lng32 theSQLCODE, char *theEMSEventTarget, NABoolean forceDialout) |
| { |
| Int32 numBytes = 0; |
| |
| // this function should not have been called for these |
| // sqlcodes but just in case |
| // "UUUUUUU" is an undefined event target |
| if ((theSQLCODE == 0) || (theSQLCODE == 100)) |
| { |
| str_cpy_all(theEMSEventTarget,"UUUUUUU",8); |
| numBytes = 7; |
| } |
| else if (forceDialout) |
| { |
| str_cpy_all(theEMSEventTarget,"DIALOUT",8); |
| numBytes = 7; |
| } |
| else |
| { |
| // We need to lookup the EMS_EVENT_TARGET for this SQLCODE. |
| NAWchar* source; |
| |
| // Get the Message string for this error. The fifth field is the EMS_EVENT_TARGET. |
| NABoolean sourceIsNotKnown = GetErrorMessage(theSQLCODE, source, EMS_EVENT_TARGET); |
| |
| if (!sourceIsNotKnown) |
| { |
| // Message found. |
| numBytes = |
| UnicodeStringToLocale(CharInfo::ISO88591, |
| source, 7, theEMSEventTarget, 7, FALSE); |
| |
| // if the message is a warning |
| // downgrade to LOGONLY. |
| // soln10-070918-7550 and soln 10-071206-9296. |
| if( theSQLCODE > 0) |
| { |
| str_cpy_all(theEMSEventTarget,"LOGONLY",8); |
| } |
| |
| } |
| } |
| |
| if (numBytes != 7) |
| *theEMSEventTarget = '\0'; |
| else |
| theEMSEventTarget[7] = '\0'; |
| } |
| |
| // |
| // ComCondition::getMessageEMSExperienceLevel() |
| // |
| void ComEMSExperienceLevel(Lng32 theSQLCODE, char *theEMSExperienceLevel) |
| { |
| // this function should not have been called for these |
| // sqlcodes but just in case |
| // "UUUUUUUU" is an undefined experience level |
| if ((theSQLCODE == 0) || (theSQLCODE == 100)) |
| { |
| str_cpy_all(theEMSExperienceLevel,"UUUUUUUU",9); |
| return; |
| } |
| |
| // We need to lookup the EMS_EVENT_TARGET for this SQLCODE. |
| NAWchar* source; |
| |
| // Get the Message string for this error. The third field is the EMS_EXPERIENCE_LEVEL. |
| NABoolean sourceIsNotKnown = GetErrorMessage(theSQLCODE, source, EMS_EXPERIENCE_LEVEL); |
| |
| if (!sourceIsNotKnown) |
| { |
| // Message found. |
| Int32 numBytes = |
| UnicodeStringToLocale(CharInfo::ISO88591, |
| source, 8, theEMSExperienceLevel, 8, FALSE); |
| |
| if (numBytes != 8) |
| *theEMSExperienceLevel = '\0'; |
| else |
| theEMSExperienceLevel[8] = '\0'; |
| } |
| } |
| |
| |
| // |
| // ComCondition::getMessageText() |
| // |
| // Getting message text establishes that messageText_ points |
| // to a buffer, on the proper heap, that holds the instantiated |
| // form of the error message corresponding to the current SQLCODE value. |
| // |
| // This function just returns messageText_ if this object is already |
| // locked. |
| // |
| // Otherwise, the algorithm followed is: |
| // 1. Set isLocked_. |
| // 2. Get a char* pointing to a buffer holding the raw message text per |
| // the SQLCODE value. |
| // 3. Instantiate the message text into a buffer on the global heap. |
| // 4. Store a pointer to that buffer in messageText_. |
| // 5. Store the length of that string in messageLen_. |
| // |
| // It's okay to get the message text for a ComCondition whose |
| // SQLCODE is zero. You just can't insert a zero-condition ComCondition |
| // into a ComDiagsArea. |
| // |
| const NAWchar * ComCondition::getMessageText(NABoolean prefixAdded) |
| { |
| return getMessageText(prefixAdded, CharInfo::ISO88591/* iso_mapping */); |
| } |
| |
| const NAWchar *const ComCondition::getMessageText(NABoolean prefixAdded, |
| CharInfo::CharSet isoMapCS) |
| { |
| if (!isLocked_) { |
| isLocked_ = TRUE; |
| Int32 arg = (Int32) theSQLCODE_; |
| assert(arg == theSQLCODE_); |
| // Ignore the contents of isoMapCS because error messages on SeaQuest |
| // is always in UTF8 - Set isoMapsCS to UTF8 to avoid using conditional |
| // compilations in this routine (i.e., to save me some typing); |
| // i still keep a couple conditional compilations below so you see what |
| // i mean - On SeaQuest, the name isoMapCS in this routine does |
| // not mean ISO(88951)_MAPPING_CHARSET - Please think of it as UTF8. |
| isoMapCS = CharInfo::UTF8; |
| NAWchar* source; |
| |
| //UR2 CNTNSK |
| NABoolean msgNotFound = GetErrorMessage(arg, source, ERROR_TEXT, NULL, 0, prefixAdded); |
| assert(source); |
| |
| //dbg: NAWstrcat(source, WIDE_(" $ $$ $ab $9 $~ $~~ $~0 $0~ $~a $a~ $0x $0x~int0 $int0~x # $0~int0 $int0~0 $0 $00 $0$0 $int0 $int0$int0")); |
| |
| // What we want to do here is to first allocate a buffer of some size. |
| // Once that has been accomplished, we declare and construct dest, |
| // an ostrstream. |
| |
| NAWchar* buffer = NULL; |
| UInt32 expandedBufferLen = computeMsgLen(*this); |
| |
| if (!collHeapPtr_) |
| buffer = new NAWchar[expandedBufferLen]; |
| else |
| buffer = (NAWchar*) collHeapPtr_->allocateMemory(expandedBufferLen * sizeof(NAWchar)); |
| assert(buffer); |
| NAWchar* dest = buffer; |
| |
| // This chunk has the code for scanning message text and replacing |
| // any text retrieved for the given SQLCODE value. |
| // |
| // We are implementing a state machine based scanner. The state |
| // of the scanner is given by theState. The main point is to |
| // scan until $ is located and then grab whatever identifier |
| // happens to come next. We then lookup the identifier in our |
| // fixed table of params which we know about. We then attempt to |
| // make an intelligent choice for what string to write in place |
| // of the token we just found. |
| // |
| // We assume on entry that source is a forward iterator over |
| // the given message and that the message is null-terminated. |
| // |
| // It is an important point that if the str() member of dest |
| // is messaged, then we own the storage returned by that buffer. |
| |
| UInt32 theState = 1; |
| const UInt32 DONE_STATE = 99; |
| NAWchar tokenBuf[256]; |
| NAWchar* theToken = tokenBuf; |
| tokenBuf[0] = L'\0'; |
| while (theState != DONE_STATE) { |
| switch (theState) { |
| case 1: |
| if (*source == L'\0') |
| theState = DONE_STATE; |
| |
| else if (*source == ERRORPARAM_BEGINMARK) { |
| tokenBuf[0] = L'\0'; |
| theToken = tokenBuf; |
| source++; |
| theState = 2; |
| } |
| else |
| *dest++ = *source++; |
| break; |
| |
| case 2: |
| if (isAlNum8859_1(*source)) { |
| *theToken++ = *source++; |
| theState = 3; |
| } |
| else { |
| *dest++ = ERRORPARAM_BEGINMARK; |
| if (*source == L'\0') |
| theState = DONE_STATE; |
| else { |
| theState = 1; |
| *dest++ = *source++; |
| } |
| } |
| break; |
| |
| case 3: |
| if (isAlNum8859_1(*source) || *source == ERRORPARAM_TYPESEP) |
| *theToken++ = *source++; |
| else { |
| // In this chunk we |
| // lookup theToken in a table of tokens and from that result |
| // determine a substitution, generally drawing on the value of |
| // a data member of a ComCondition object. |
| |
| // This chunk of code takes the NAString theToken, |
| // removes positional parameter syntax from it, does a |
| // tolower on it to convert its contents to all lower-case, |
| // and then tries to locate theToken in the tokens array. |
| // If not found, the post-condition of this chunk is tokIndex |
| // must equal MAX_TOKEN; otherwise tokIndex has a value which |
| // can be cast to TOKEN_ENUM to refer to the token which |
| // theToken represents. |
| // There are several SQLSTATE related items for which we have |
| // no values to reply, currently. |
| |
| *theToken = L'\0'; |
| FixupMessageParam(tokenBuf); // remove positional param syntax |
| |
| for (theToken=tokenBuf; *theToken; theToken++) |
| *theToken = na_towlower(*theToken); |
| theToken = tokenBuf; |
| UInt32 tokIndex = 0; |
| while (tokIndex != MAX_TOKEN && |
| na_wcscmp(theToken, tokens[tokIndex])) |
| tokIndex++; |
| |
| Int32 optStrNum = 0; |
| |
| switch (tokIndex) { |
| case MAX_TOKEN: *dest++ = ERRORPARAM_BEGINMARK; |
| na_wcscpy(dest, tokenBuf); |
| break; |
| |
| case STRING4: optStrNum++; |
| case STRING3: optStrNum++; |
| case STRING2: optStrNum++; |
| case STRING1: optStrNum++; |
| case STRING0: |
| |
| { |
| UInt32 len; |
| UInt32 availChars = expandedBufferLen - (dest - buffer); |
| |
| CharInfo::CharSet cs = optionalStringCharSet_[optStrNum]; |
| if (CharInfo::isSingleByteCharSet(cs)) |
| { |
| if (cs == CharInfo::ISO88591) |
| { |
| cs = CharInfo::UTF8; |
| } |
| const char* optStr = getOptionalString(optStrNum); |
| len = (optStr) ? strlen(optStr) : 0; |
| |
| if (availChars < len+1) |
| assert(FALSE); |
| appendSafeStringInW(dest, collHeapPtr_, optStr, cs); |
| } else { |
| const NAWchar* optStrInW = |
| getSafeWString(getOptionalWString(optStrNum)); |
| len = NAWstrlen(optStrInW); |
| if (availChars < len+1) |
| assert(FALSE); |
| NAWsprintf(dest, WIDE_("%s"), optStrInW); |
| } |
| } |
| |
| break; |
| |
| |
| case INT0: NAWsprintf(dest,WIDE_("%d"),optionalInteger_[0]); break; |
| case INT1: NAWsprintf(dest,WIDE_("%d"),optionalInteger_[1]); break; |
| case INT2: NAWsprintf(dest,WIDE_("%d"),optionalInteger_[2]); break; |
| case INT3: NAWsprintf(dest,WIDE_("%d"),optionalInteger_[3]); break; |
| case INT4: NAWsprintf(dest,WIDE_("%d"),optionalInteger_[4]); break; |
| case INTERNAL_SQLCODE: NAWsprintf(dest,WIDE_("%d"),theSQLCODE_); break; |
| case CONDITION_NUMBER: NAWsprintf(dest,WIDE_("%d"),conditionNumber_); break; |
| case SERVER_NAME: |
| appendSafeStringInW(dest, collHeapPtr_, serverName_, isoMapCS); |
| break; |
| case CONNECTION_NAME: |
| appendSafeStringInW(dest, collHeapPtr_, connectionName_, isoMapCS); |
| break; |
| case CONSTRAINT_CATALOG: |
| appendSafeStringInW(dest, collHeapPtr_, constraintCatalog_, CharInfo::UTF8); |
| break; |
| case CONSTRAINT_SCHEMA: |
| appendSafeStringInW(dest, collHeapPtr_, constraintSchema_, isoMapCS); |
| break; |
| case CONSTRAINT_NAME: |
| appendSafeStringInW(dest, collHeapPtr_, constraintName_, isoMapCS); |
| break; |
| case TRIGGER_CATALOG: |
| appendSafeStringInW(dest, collHeapPtr_, triggerCatalog_, isoMapCS); |
| break; |
| case TRIGGER_SCHEMA: |
| appendSafeStringInW(dest, collHeapPtr_, triggerSchema_, isoMapCS); |
| break; |
| case TRIGGER_NAME: |
| appendSafeStringInW(dest, collHeapPtr_, triggerName_, isoMapCS); |
| break; |
| case CATALOG_NAME: |
| appendSafeStringInW(dest, collHeapPtr_, catalogName_, isoMapCS); |
| break; |
| case SCHEMA_NAME: |
| appendSafeStringInW(dest, collHeapPtr_, schemaName_, isoMapCS); |
| break; |
| |
| case TABLE_NAME: |
| appendSafeStringInW(dest, collHeapPtr_, tableName_, isoMapCS); |
| break; |
| case COLUMN_NAME: |
| appendSafeStringInW(dest, collHeapPtr_, columnName_, isoMapCS); |
| break; |
| |
| case CURSOR_NAME: |
| // CursorName_ is deprecated (coopted by sqlID_) |
| break; |
| case ROW_NUMBER: NAWsprintf(dest,WIDE_("%d"),rowNumber_); break; |
| case NSK_CODE: NAWsprintf(dest,WIDE_("%d"),nskCode_); break; |
| // case RETURNED_SQLSTATE: dest << returnedSQLSTATE_; break; |
| // case CLASS_ORIGIN: dest << classOrigin_; break; |
| // case SUBCLASS_ORIGIN: dest << subClassOrigin_; break; |
| default: assert(FALSE); |
| } |
| // Note that dest[0] will be 0 on a %S format if: |
| // - the rhs src string is the empty string (correct behavior) |
| // - the rhs src string is too big, > 1023 chars |
| // (reasonable, but unexpected behavior!) |
| dest += NAWstrlen(dest); |
| |
| if (*source == ERRORPARAM_BEGINMARK) { |
| tokenBuf[0] = L'\0'; |
| theToken = tokenBuf; |
| source++; |
| theState = 2; |
| } |
| else if (*source == L'\0') |
| theState = DONE_STATE; |
| else { |
| theState = 1; |
| *dest++ = *source++; |
| } |
| } |
| break; |
| default: assert(FALSE); |
| } |
| } // while (theState != DONE_STATE) |
| |
| // If the error text is not available, at least display the error params, |
| // for debugging purposes when testing on a machine where the error file |
| // is missing, incomplete, or corrupted. |
| // |
| // This mirrors what ErrorMessage::printErrorMessage does. |
| if (msgNotFound) |
| { |
| // Remove ALL trailing newlines/carriage-returns so params will be on |
| // the same line (not quite the same as FixCarriageReturn below). |
| while (dest > buffer && (*--dest == L'\n' || *dest == L'\r')) ; |
| |
| NAWString* msgParams = terseParamDisplay(*this, collHeapPtr_); |
| |
| if ( msgParams ) { |
| const NAWchar *p = msgParams -> data(); |
| NAWchar* bufend = &buffer[expandedBufferLen-1]; |
| while (++dest < bufend && *p) |
| *dest = *p++; |
| |
| NADELETE(msgParams, NAWString, collHeapPtr_); |
| } |
| |
| |
| } |
| |
| *dest = L'\0'; |
| messageText_ = buffer; |
| assert(messageText_); |
| ErrorMessageOverflowCheckW(messageText_, expandedBufferLen); |
| |
| FixCarriageReturn(messageText_); |
| messageLen_ = (Lng32) NAWstrlen(messageText_); |
| } |
| |
| return messageText_; |
| } |
| |
| // We create an operator<< for outputting a ComDiagsArea |
| // and giving a summary of its contents. |
| |
| ostream &operator<<(ostream &dest, const ComDiagsArea& da) |
| { |
| char rowCount[21]; // for row count which is Int64 |
| dest << "Function : " << da.getFunctionName() << endl; |
| dest << "SQLCODE : " << da.mainSQLCODE() << endl; |
| dest << "number : " << da.getNumber() << endl; |
| dest << "are more?: " << ((da.areMore()) ? "Yes" : "No") << endl; |
| convertInt64ToAscii(da.getRowCount(), rowCount); |
| dest << "row count: " << rowCount << endl; |
| Lng32 i = 1; |
| while (i != da.getNumber()+1) |
| dest << (da[i++].getSQLCODE()) << endl; |
| return dest; |
| } |
| |
| // stuff for error processing using variable argument list |
| // |
| void emitError( Lng32 ErrNum, char *stringType, Lng32 numArgs, ... ) |
| { |
| va_list ap; |
| ComCondition currentErr; |
| |
| currentErr.clear(); |
| currentErr.setSQLCODE( ErrNum ); |
| |
| // set optional data if arguments were provided, they must be provided |
| // in order according to type so that the first optional integer gets |
| // slot 1, the second to appear gets slot 2, etc. Same for optional |
| // strings |
| |
| if ( stringType && numArgs > 0 ) { |
| UInt32 intIdx = 0, strIdx = 0; |
| |
| va_start( ap, numArgs ); |
| assert( numArgs < 11 ); // at most, 10 arguments are acceptable |
| |
| for( UInt32 argNum = 0; argNum < (UInt32)numArgs; argNum++ ) |
| if ( stringType[ argNum ] == 'T' ) { |
| assert( strIdx < 5 ); |
| currentErr.setOptionalString( strIdx, va_arg( ap, const char * ) ); |
| strIdx++; |
| } |
| else { |
| assert( intIdx < 5 ); |
| currentErr.setOptionalInteger( intIdx, |
| * (Lng32 *) va_arg(ap, UInt32 *) ); |
| intIdx++; |
| } |
| |
| va_end( ap ); |
| } |
| NAWriteConsole(currentErr.getMessageText(), cerr, TRUE/*newline*/); |
| } |
| |