Author: wyoung
Date: Thu Jan 24 04:30:38 2008
New Revision: 2127

URL: http://svn.gna.org/viewcvs/mysqlpp?rev=2127&view=rev
Log:
Replaced string-to-number conversion stuff with a new template-based
system that gives us more control over how conversions happen, and uses
C++ mechanisms instead of C ones.  (Also rolls back previous locale
stuff: doesn't work right with MySQL.)  Patch by Jonathan Wakely with
only style changes and minor tweaks by me; most of the new tests by me,
starting from one provided by Jonathan.

Modified:
    trunk/lib/mystring.h
    trunk/test/string.cpp

Modified: trunk/lib/mystring.h
URL: 
http://svn.gna.org/viewcvs/mysqlpp/trunk/lib/mystring.h?rev=2127&r1=2126&r2=2127&view=diff
==============================================================================
--- trunk/lib/mystring.h (original)
+++ trunk/lib/mystring.h Thu Jan 24 04:30:38 2008
@@ -37,6 +37,8 @@
 #include "sql_buffer.h"
 
 #include <string>
+#include <sstream>
+#include <limits>
 
 #include <locale.h>
 #include <stdlib.h>
@@ -46,67 +48,58 @@
 #if !defined(DOXYGEN_IGNORE)
 // Doxygen will not generate documentation for this section.
 
-template <class Type> class internal_string_to_number_proxy;
-
-#define internal_convert_string_to_float(TYPE, FUNC) \
-  template <> \
-  class internal_string_to_number_proxy<TYPE> {\
-  public:\
-    internal_string_to_number_proxy(const char* str, const char *& end) { \
-      num_ = FUNC(str, const_cast<char **>(&end));}\
-    operator TYPE () {return num_;}\
-  private:\
-    TYPE num_;\
-  };\
-
-#define internal_convert_string_to_int(TYPE, FUNC) \
-  template <> \
-  class internal_string_to_number_proxy<TYPE> {\
-  public:\
-    internal_string_to_number_proxy(const char* str, const char *& end) { \
-      num_ = FUNC(str, const_cast<char **>(&end),10);}\
-    operator TYPE () {return num_;}\
-  private:\
-    TYPE num_;\
-  };\
-
-
-#if defined(MYSQLPP_PLATFORM_VISUAL_CPP)
-// Squish VC++ warning about "possible loss of data" for these conversions
-#      pragma warning(disable: 4244)
-#endif
-
-internal_convert_string_to_float(float, strtod)
-internal_convert_string_to_float(double, strtod)
-
-internal_convert_string_to_int(char, strtol)
-internal_convert_string_to_int(signed char, strtol)
-internal_convert_string_to_int(int, strtol)
-internal_convert_string_to_int(short int, strtol)
-internal_convert_string_to_int(long int, strtol)
-
-internal_convert_string_to_int(unsigned char, strtoul)
-internal_convert_string_to_int(unsigned int, strtoul)
-internal_convert_string_to_int(unsigned short int, strtoul)
-internal_convert_string_to_int(unsigned long int, strtoul)
-
-#if defined(MYSQLPP_PLATFORM_VISUAL_CPP)
-#      pragma warning(default: 4244)
-#endif
-
-#if !defined(NO_LONG_LONGS)
-#if defined(MYSQLPP_PLATFORM_VISUAL_CPP)
-// Handle 64-bit ints the VC++ way
-internal_convert_string_to_int(longlong, _strtoi64)
-internal_convert_string_to_int(ulonglong, _strtoui64)
-#else
-// No better idea, so assume the C99 way.  If your compiler doesn't
-// support this, please provide a patch to extend this ifdef, or define
-// NO_LONG_LONGS.
-internal_convert_string_to_int(longlong, strtoll)
-internal_convert_string_to_int(ulonglong, strtoull)
-#endif
-#endif // !defined(NO_LONG_LONGS)
+namespace detail
+{
+       template<typename T, bool is_signed = std::numeric_limits<T>::is_signed>
+       struct conv_promotion;
+
+       template<>
+       struct conv_promotion<float>
+       {
+               typedef double type;
+       };
+
+       template<>
+       struct conv_promotion<double>
+       {
+               typedef double type;
+       };
+
+#      if !defined(NO_LONG_LONGS)
+       template<>
+       struct conv_promotion<unsigned long long>
+       {
+               typedef unsigned long long type;
+       };
+
+       template<>
+       struct conv_promotion<long long>
+       {
+               typedef long long type;
+       };
+#      endif
+
+       // preserve existing behaviour, char converted as signed long
+       template<>
+       struct conv_promotion<char>
+       {
+               typedef long type;
+       };
+
+       // all other types use signed/unsigned long
+
+       template<typename T>
+       struct conv_promotion<T, true>
+       {
+               typedef long type;
+       };
+
+       template<typename T>
+       struct conv_promotion<T, false>
+       {
+               typedef unsigned long type;
+       };
+} // namespace detail
 
 #if !defined(DOXYGEN_IGNORE)
 class MYSQLPP_EXPORT SQLTypeAdapter;
@@ -295,38 +288,26 @@
        /// \brief Return a const pointer to the string data.
        const char* c_str() const { return data(); }
        
+#if defined(MYSQLPP_PLATFORM_VISUAL_CPP)
+// Squish VC++ warning about "possible loss of data" for these conversions
+#   pragma warning(disable: 4244)
+#endif
+
        /// \brief Template for converting the column data to most any
        /// numeric data type.
        template <class Type>
        Type conv(Type) const
        {
-               if (buffer_) {
-                       std::string strbuf;
-                       strip_leading_blanks(strbuf);
-                       std::string::size_type len = strbuf.size();
-                       const char* str = strbuf.data();
-                       const char* end = str;
-                       Type num = internal_string_to_number_proxy<Type>(str, 
end);
-
-                       lconv* lc = localeconv();
-                       if ((lc && lc->decimal_point && lc->decimal_point[0] ) 
? 
-                                       *end == lc->decimal_point[0] :
-                                       *end == '.') {
-                               ++end;
-                               for (; *end == '0'; ++end) ;
-                       }
-                       
-                       if (*end != '\0') {
-                               throw BadConversion(typeid(Type).name(), data(),
-                                               end - str, len);
-                       }
-
-                       return num;
-               }
-               else {
-                       return 0;
-               }
-       }
+               // Conversions are done using one of 
double/long/ulong/llong/ullong
+               // so we call a helper function to do the work using that type.
+               // This reduces the amount of template code instantiated.
+               typedef typename detail::conv_promotion<Type>::type conv_type;
+               return do_conv<conv_type>(typeid(Type).name());
+       }
+
+#if defined(MYSQLPP_PLATFORM_VISUAL_CPP)
+#   pragma warning(default: 4244)
+#endif
 
        /// \brief Overload of conv() for types wrapped with Null<>
        ///
@@ -575,6 +556,49 @@
        operator Null<T, B>() const { return conv(Null<T, B>()); }
 
 private:
+       /// \brief Do the actual numeric conversion via @p Type.
+       template <class Type>
+       Type do_conv(const char* type_name) const
+       {
+               if (buffer_) {
+                       std::stringstream buf;
+                       buf.write(data(), length());
+                       buf.imbue(std::locale::classic()); // "C" locale
+                       Type num = Type();
+                       
+                       if (buf >> num) {
+                               char c;
+                               if (!(buf >> c)) {
+                                       // Nothing left in buffer, so 
conversion complete,
+                                       // and thus successful.
+                                       return num;
+                               }
+
+                               if (c == '.' &&
+                                               (typeid(Type) != typeid(float)) 
&&
+                                               (typeid(Type) != 
typeid(double))) {
+                                       // Conversion stopped on a decimal 
point -- locale
+                                       // doesn't matter to MySQL -- so only 
way to succeed
+                                       // is if it's an integer and everything 
following
+                                       // the decimal is inconsequential.
+                                       c = '0';        // handles '.' at end 
of string
+                                       while (buf >> c && c == '0') /* spin */ 
;
+                                       if (buf.eof() && c == '0') {
+                                               return num;  // only zeros 
after decimal point
+                                       }
+                               }
+                       }
+                       else if (buf.eof()) {
+                               return num;  // nothing to convert, return 
default value
+                       }
+
+                       throw BadConversion(type_name, data(), 0, length());
+               }
+               else {
+                       return 0;
+               }
+       }
+
        RefCountedBuffer buffer_;       ///< reference-counted data buffer
 
        friend class SQLTypeAdapter;

Modified: trunk/test/string.cpp
URL: 
http://svn.gna.org/viewcvs/mysqlpp/trunk/test/string.cpp?rev=2127&r1=2126&r2=2127&view=diff
==============================================================================
--- trunk/test/string.cpp (original)
+++ trunk/test/string.cpp Thu Jan 24 04:30:38 2008
@@ -2,7 +2,7 @@
  test/string.cpp - Tests the behavior of mysqlpp::String, particularly
        its data conversion methods.
 
- Copyright (c) 2007 by Educational Technology Resources, Inc.
+ Copyright (c) 2007-2008 by Educational Technology Resources, Inc.
  Others may also hold copyrights on code in this file.  See the
  CREDITS file in the top directory of the distribution for details.
 
@@ -50,8 +50,92 @@
 }
 
 
-// Runs uses the above functions to test many different types of
-// conversion.
+// Check that we can convert strings with decimals in them to native
+// floating-point values, regardless of locale.
+static bool
+test_float_conversion()
+{
+       // This stuff should just work
+       if (!test_equality(mysqlpp::String("123.00"), 123)) return false;
+       if (!test_equality(mysqlpp::String("123."), 123)) return false;
+
+       // This is trickier: MySQL ignores the system locale when it comes
+       // to decimal separators, always using '.', so ensure the conversion
+       // stuff in MySQL++ does the right thing regardless.  Test against
+       // this system's current locale, an arbitrary European one where ','
+       // is the decimal separator, and the "C" locale where it's '.'.
+       if (!test_equality(mysqlpp::String("621.200"), 621.2)) return false;
+       std::locale old_locale = std::locale::global(std::locale::classic());
+       if (!test_equality(mysqlpp::String("621.200"), 621.2)) return false;
+       std::locale::global(std::locale("de_DE"));
+       if (!test_equality(mysqlpp::String("621.200"), 621.2)) return false;
+       std::locale::global(old_locale);
+
+       // Check that we choke on silly float-like values
+       try {
+               if (test_equality(mysqlpp::String("621.20.0"), 621.2)) {
+                       std::cerr << "Quasi-FP with two decimal points "
+                                       "converting without error!" << 
std::endl;
+               }
+               return false;
+       }
+       catch (const mysqlpp::BadConversion&) {
+               return true;
+       }
+}
+
+
+// Tries to convert the given string to an int.  Returns false if we got
+// a BadConversion exception and didn't expect it, or didn't get one we
+// expected.  Returns false on all other exceptions regardless.
+static bool
+test_int_conversion(const mysqlpp::String& s, bool throw_expected)
+{
+       // Try the conversion
+       bool conv_threw = false;
+       try {
+               int converted = s;
+       }
+       catch (const mysqlpp::BadConversion&) {
+               conv_threw = true;
+       }
+       catch (const std::exception& e) {
+               std::cerr << "Unexpected " << typeid(e).name() <<
+                               " exception in test_int_conv: " << e.what() << 
std::endl;
+               return false;
+       }
+       catch (...) {
+               std::cerr << "Like, totally bogus exception in test_int_conv, "
+                               "man!" << std::endl;
+               return false;
+       }
+
+       // Did it do what we expected?
+       if (throw_expected == conv_threw) {
+               return true;
+       }
+       else {
+               std::cerr << "Conversion of \"" << s << "\" to int " <<
+                               (conv_threw ? "did not " : "") << "throw; did " 
<<
+                               (throw_expected ? "not " : "") << "expect it 
to." <<
+                               std::endl;
+               return false;
+       }
+}
+
+
+// Ensures that the program's locale doesn't affect our floating-point
+// conversions.  ('.' vs. ',' stuff.)
+static bool
+test_locale()
+{
+
+       return true;
+}
+
+
+// Ensures numeric conversions of many different types get handled
+// correctly.
 static bool
 test_numeric(const mysqlpp::String& s, int value)
 {
@@ -107,6 +191,9 @@
                failures += test_quote_q(empty, true) == false;
                failures += test_quote_q(mysqlpp::String("1", typeid(int)),
                                false) == false;
+               failures += test_locale() == false;
+               failures += test_float_conversion() == false;
+               failures += test_float_conversion() == false;
 
                return failures;
        }


_______________________________________________
Mysqlpp-commits mailing list
[email protected]
https://mail.gna.org/listinfo/mysqlpp-commits

Reply via email to