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