libtabula

Check-in [be382a7009]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Merged row-pimpl branch into trunk: moves one of the last few remaining MySQLisms on the trunk down into the MySQLDBDriver layer.
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:be382a7009a5b545e6de904773483ba91bfb96a8
User & Date: tangent 2015-08-15 09:35:59
Context
2015-08-15
10:11
Fixed some errors introduced in the recent merge check-in: 7e59757bd5 user: tangent tags: trunk
09:35
Merged row-pimpl branch into trunk: moves one of the last few remaining MySQLisms on the trunk down into the MySQLDBDriver layer. check-in: be382a7009 user: tangent tags: trunk
09:32
Merged trunk changes in Closed-Leaf check-in: 4e189f92a7 user: tangent tags: row-pimpl
09:21
The crash that prompted the creation of the row-pimpl branch was due to calling Row.size() before Row was initialized. This old MYSQL_ROW base implementation copes with this, but the new branch will try to dereference a null pointer to its nonexistent implementation object. Guarding this and a few other paths so we don't try to use that smart pointer if we aren't initialized yet.

(This change should have been made on the row-pimpl branch, but it also works against trunk, where it amounts to belt-and-suspenders.) check-in: bd141d86c6 user: tangent tags: trunk

Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to Wishlist.md.

139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
...
235
236
237
238
239
240
241







242
243
244
245
246
247
248
    less likely to be a problem.

*   Convert from Bakefile to CMake.  See the TODO list in the top-level
    `CMakeLists.txt` file.

*   Database independence:

    -   `Row` remains implemented in terms of `MYSQL_ROW`.  Need to
	    create a `[MySQL]RowImpl` class hierarchy and pass instances
	    of them via the pimpl idiom to `Row` instead of `MYSQL_ROW`.
	    An easy case is `MySQLDriver::fetch_row()`, part of the
	    low-level implementation of "use" queries, but the real
	    trick is doing it for "store" queries.

    -   Field is entirely MySQL-specific.  Rather than pimpl it,
        consider just ripping it out, along with the only other
        users of it, Result::fetch_field*().  All it does is let the
        library user iterate through a thinly-wrapped version of the
        C API's view of the table schema.  That's basically a form
        of reflection, which isn't really a key use case for libtabula.

................................................................................
            class UseQueryResult { ...
                DBD::row_type fetch_raw_row();
            }

    -   Will CMake let us figure out which DB engines are available
        on non-autoconf systems, or at least pass in options that
        let the user select them?








*   If `pkg-config` is available, register ourselves with it using
    information discovered by `configure`.  Also, write out a
    `libtabula-config` script, which either wraps `pkg-config` or
    reinvents it, poorly, for systems that don't have it.

*   Add `String::operator==(const libtabula::null_type&)`.  Needed to







<
<
<
<
<
<
<







 







>
>
>
>
>
>
>







139
140
141
142
143
144
145







146
147
148
149
150
151
152
...
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
    less likely to be a problem.

*   Convert from Bakefile to CMake.  See the TODO list in the top-level
    `CMakeLists.txt` file.

*   Database independence:








    -   Field is entirely MySQL-specific.  Rather than pimpl it,
        consider just ripping it out, along with the only other
        users of it, Result::fetch_field*().  All it does is let the
        library user iterate through a thinly-wrapped version of the
        C API's view of the table schema.  That's basically a form
        of reflection, which isn't really a key use case for libtabula.

................................................................................
            class UseQueryResult { ...
                DBD::row_type fetch_raw_row();
            }

    -   Will CMake let us figure out which DB engines are available
        on non-autoconf systems, or at least pass in options that
        let the user select them?

    -   The MySQL `type_info` refactoring caused us to split out
        FieldType::Base and is_null parameters in a few places.  We
        can pass a FieldType object here instead, since that encodes
        null-ness as well.  We might need to make a distinction
        between "can be NULL" and "is NULL" to make this work,
        though.

*   If `pkg-config` is available, register ourselves with it using
    information discovered by `configure`.  Also, write out a
    `libtabula-config` script, which either wraps `pkg-config` or
    reinvents it, poorly, for systems that don't have it.

*   Add `String::operator==(const libtabula::null_type&)`.  Needed to

Changes to src/mysql/driver.cpp.

137
138
139
140
141
142
143





























144
145
146
147
148
149
150
			break;
		}
	}

	mysql_field_seek(pres, 0);	// semantics break otherwise!
}































string
MySQLDriver::query_info()
{
	const char* i = mysql_info(&mysql_);
	return i ? string(i) : string();
}







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
			break;
		}
	}

	mysql_field_seek(pres, 0);	// semantics break otherwise!
}


Row
MySQLDriver::fetch_row(ResultBase& res, ResultBase::Impl& impl)
// FIXME: Why do we need the impl param?  Can't we get that from res?
{
	if (MYSQL_ROW raw = mysql_fetch_row(MYSQL_RES_FROM_IMPL(impl))) {
		Row::size_type size = res.num_fields();
		Row::Impl* pd = new Row::Impl;
		pd->reserve(size);
		const unsigned long* lengths = fetch_lengths(impl);
		for (Row::size_type i = 0; i < size; ++i) {
			bool is_null = raw[i] == 0;
			pd->at(i).assign(Row::value_type(
					is_null ? "NULL" : raw[i],
					is_null ? 4 : lengths[i],
					res.field_type(int(i)).base_type(),
					is_null));
		}

		return Row(pd, res.field_names(), throw_exceptions());
	}
	else if (throw_exceptions()) {
		throw ObjectNotInitialized("ROW is NULL");
	}
	
	// Either no more rows or !res && !te
	return Row();
}


string
MySQLDriver::query_info()
{
	const char* i = mysql_info(&mysql_);
	return i ? string(i) : string();
}

Changes to src/mysql/driver.h.

194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
		return !mysql_real_query(&mysql_, qstr,
				static_cast<unsigned long>(length));
	}

	/// \brief Returns the next DB row from the given result set.
	///
	/// Wraps \c mysql_fetch_row() in MySQL C API.
	Row fetch_row(ResultBase& res, ResultBase::Impl& impl)
	{
		if (MYSQL_ROW raw = mysql_fetch_row(MYSQL_RES_FROM_IMPL(impl))) {
			return Row(raw, &res, fetch_lengths(impl),
					res.throw_exceptions());
		}
		else {
			return Row();
		}
	}

	/// \brief Returns the lengths of the fields in the current row
	///
	/// Wraps \c mysql_fetch_lengths() in MySQL C API.
	const unsigned long* fetch_lengths(ResultBase::Impl& impl) const
	{
		return mysql_fetch_lengths(MYSQL_RES_FROM_IMPL(impl));







|
<
<
<
<
<
<
<
<
<







194
195
196
197
198
199
200
201









202
203
204
205
206
207
208
		return !mysql_real_query(&mysql_, qstr,
				static_cast<unsigned long>(length));
	}

	/// \brief Returns the next DB row from the given result set.
	///
	/// Wraps \c mysql_fetch_row() in MySQL C API.
	Row fetch_row(ResultBase& res, ResultBase::Impl& impl);










	/// \brief Returns the lengths of the fields in the current row
	///
	/// Wraps \c mysql_fetch_lengths() in MySQL C API.
	const unsigned long* fetch_lengths(ResultBase::Impl& impl) const
	{
		return mysql_fetch_lengths(MYSQL_RES_FROM_IMPL(impl));

Changes to src/result.h.

146
147
148
149
150
151
152




153
154
155
156
157
158
159
	const FieldTypes::value_type& field_type(int i) const
			{ return types_->at(i); }

	/// \brief Get a list of the types of the fields within this
	/// result set.
	const RefCountedPointer<FieldTypes>& field_types() const
			{ return types_; }





	/// \brief Returns the number of fields in this result set
	size_t num_fields() const { return fields_.size(); }

	/// \brief Return the name of the table the result set comes from
	const char* table() const
			{ return fields_.empty() ? "" : fields_[0].table(); }







>
>
>
>







146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
	const FieldTypes::value_type& field_type(int i) const
			{ return types_->at(i); }

	/// \brief Get a list of the types of the fields within this
	/// result set.
	const RefCountedPointer<FieldTypes>& field_types() const
			{ return types_; }

	/// \brief Returns a pointer to the DBDriver subclass that created
	/// this object.
	DBDriver* driver() const { return driver_; }

	/// \brief Returns the number of fields in this result set
	size_t num_fields() const { return fields_.size(); }

	/// \brief Return the name of the table the result set comes from
	const char* table() const
			{ return fields_.empty() ? "" : fields_[0].table(); }

Changes to src/row.cpp.

27
28
29
30
31
32
33
34
35
36


37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
...
179
180
181
182
183
184
185












186
187
188
189
190
191
192
#include "row.h"

#include "result.h"


namespace libtabula {

Row::Row(MYSQL_ROW row, const ResultBase* res,
		const unsigned long* lengths, bool throw_exceptions) :
OptionalExceptions(throw_exceptions),


initialized_(false)
{
	if (row) {
		if (res) {
			size_type size = res->num_fields();
			data_.reserve(size);
			for (size_type i = 0; i < size; ++i) {
				bool is_null = row[i] == 0;
				data_.push_back(value_type(
						is_null ? "NULL" : row[i],
						is_null ? 4 : lengths[i],
						res->field_type(int(i)).base_type(),
						is_null));
			}

			field_names_ = res->field_names();
			initialized_ = true;
		}
		else if (throw_exceptions) {
			throw ObjectNotInitialized("RES is NULL");
		}
	}
	else if (throw_exceptions) {
		throw ObjectNotInitialized("ROW is NULL");
	}
}


Row::const_reference
Row::at(size_type i) const
{
	if (i < size()) {
		return data_[i];
	}
	else {
		throw BadIndex("Row", int(i), int(size()));
	}
}


................................................................................
		throw BadFieldName(name);
	}
	else {
		return 0;
	}
}














const Row::value_type&
Row::operator [](const char* field) const
{
	size_type si = field_num(field);
	if (si < size()) {
		return at(si);







|
|

>
>
|

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







|







 







>
>
>
>
>
>
>
>
>
>
>
>







27
28
29
30
31
32
33
34
35
36
37
38
39
40























41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
...
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#include "row.h"

#include "result.h"


namespace libtabula {

Row::Row(Row::Impl* pimpl, const RefCountedPointer<FieldNames>& fn,
		bool throw_exceptions) :
OptionalExceptions(throw_exceptions),
data_(pimpl),
field_names_(fn),
initialized_(true)
{























}


Row::const_reference
Row::at(size_type i) const
{
	if (i < size()) {
		return data_->at(i);
	}
	else {
		throw BadIndex("Row", int(i), int(size()));
	}
}


................................................................................
		throw BadFieldName(name);
	}
	else {
		return 0;
	}
}


Row&
Row::operator =(const Row& rhs)
{
	data_->assign(rhs.data_->begin(), rhs.data_->end());
	field_names_->clear();
	copy(rhs.field_names_->cbegin(), rhs.field_names_->cend(),
			field_names_->begin());
	initialized_ = rhs.initialized_;
	return *this;
}


const Row::value_type&
Row::operator [](const char* field) const
{
	size_type si = field_num(field);
	if (si < size()) {
		return at(si);

Changes to src/row.h.

1
2
3
4
5

6


7
8
9
10
11
12
13
14
15
..
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
..
66
67
68
69
70
71
72
73

74
75
76



77









78
79
80
81
82
83
84
...
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138

139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
...
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
...
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
...
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
/// \file row.h
/// \brief Declares the classes for holding row data from a result set.

/***********************************************************************
 Copyright © 1998 by Kevin Atkinson, © 1999-2001 by MySQL AB, and

 © 2004-2008 by Educational Technology Resources, Inc.  Others may


 also hold copyrights on code in this file.  See the CREDITS.md file
 in the top directory of the distribution for details.

 This file is part of libtabula.

 libtabula is free software; you can redistribute it and/or modify it
 under the terms of the GNU Lesser General Public License as published
 by the Free Software Foundation; either version 2.1 of the License, or
 (at your option) any later version.
................................................................................
#include <string>

namespace libtabula {

#if !defined(DOXYGEN_IGNORE)
// Make Doxygen ignore this
class FieldNames;
class LIBTABULA_EXPORT ResultBase;
#endif

/// \brief Manages rows from a result set.
///
/// This class is like an extended version of a \c const \c std::vector
/// of libtabula::String.  It adds stuff for populating the vector.  As
/// for why it's \c const, what would it mean to modify a Row?  If we
................................................................................
	/// \brief Pointer to bool data member, for use by safe bool
	/// conversion operator.
	///
	/// \see http://www.artima.com/cppsource/safebool.html
	typedef bool Row::*private_bool_type;

public:
	/// \brief type of our internal data list

	///
	/// This is public because all other typedefs we have for
	/// mirroring std::vector's public interface depend on it.



	typedef std::vector<String> list_type;










	/// \brief constant iterator type
	typedef list_type::const_iterator const_iterator;

	/// \brief constant reference type
	typedef list_type::const_reference const_reference;

................................................................................
	initialized_(false)
	{
	}
	
	/// \brief Copy constructor
	Row(const Row& r) :
	OptionalExceptions(),
	data_(r.data_.begin(), r.data_.end()),
	field_names_(r.field_names_),
	initialized_(r.initialized_)
	{
	}

	/// \brief Create a row object
	///
	/// \param row MySQL C API row data
	/// \param res result set that the row comes from
	/// \param lengths length of each item in row
	/// \param te if true, throw exceptions on errors
	Row(MYSQL_ROW row, const ResultBase* res,
			const unsigned long* lengths, bool te = true);


	/// \brief Destroy object
	~Row() { }

	/// \brief Get a const reference to the field given its index
	///
	/// \throw libtabula::BadIndex if the row is not initialized or there
	/// are less than \c i fields in the row.
	const_reference at(size_type i) const;

	/// \brief Get a reference to the last element of the vector
	const_reference back() const { return data_.back(); }

	/// \brief Return a const iterator pointing to first element in the
	/// container
	const_iterator begin() const { return data_.begin(); }

	/// \brief Returns true if container is empty
	bool empty() const { return data_.empty(); }

	/// \brief Return a const iterator pointing to one past the last
	/// element in the container
	const_iterator end() const { return data_.end(); }

	/// \brief Get an "equal list" of the fields and values in this row
	///
	/// When inserted into a C++ stream, the delimiter 'd' will be used
	/// between the items, " = " is the relationship operator, and items
	/// will be quoted and escaped.
	equal_list_ba<FieldNames, Row, quote_type0>
................................................................................
			bool t7 = false, bool t8 = false, bool t9 = false,
			bool ta = false, bool tb = false, bool tc = false) const;

	/// \brief Returns a field's index given its name
	size_type field_num(const char* name) const;

	/// \brief Get a reference to the first element of the vector
	const_reference front() const { return data_.front(); }

	/// \brief Return maximum number of elements that can be stored
	/// in container without resizing.
	size_type max_size() const { return initialized_ ? data_.max_size() : 0; }

	/// \brief Assignment operator
	Row& operator =(const Row& rhs)
	{
		data_.assign(rhs.data_.begin(), rhs.data_.end());
		field_names_.assign(rhs.field_names_);
		initialized_ = rhs.initialized_;
		return *this;
	}

	/// \brief Get the value of a field given its name.
	///
	/// If the field does not exist in this row, we throw a BadFieldName
	/// exception if exceptions are enabled, or an empty row if not.
	/// An empty row tests as false in bool context.
	///
................................................................................
	operator private_bool_type() const
	{
		return initialized_ && data_.size() ? &Row::initialized_ : 0;
	}

	/// \brief Return reverse iterator pointing to first element in the
	/// container
	const_reverse_iterator rbegin() const { return data_.rbegin(); }

	/// \brief Return reverse iterator pointing to one past the last
	/// element in the container
	const_reverse_iterator rend() const { return data_.rend(); }

	/// \brief Get the number of fields in the row.
	size_type size() const { return initialized_ ? data_.size() : 0; }

	/// \brief Get a list of the values in this row
	///
	/// When inserted into a C++ stream, the delimiter 'd' will be used
................................................................................
		std::vector<bool> vb;
		create_vector(*this, vb, s0, s1, s2, s3, s4, s5, s6, s7, s8,
				s9, sa, sb, sc);
		return value_list_b<Row, quote_type0>(*this, vb, ",", quote);
	}

private:
	list_type data_;
	RefCountedPointer<FieldNames> field_names_;
	bool initialized_;
};

} // end namespace libtabula

#endif // !defined(LIBTABULA_ROW_H)




|
>
|
>
>
|
|







 







<







 







|
>

|
<
>
>
>
|
>
>
>
>
>
>
>
>
>







 







|







|
|
<

<
<
>











|



|


|



|







 







|






|
<
<
<
<
<
<







 







|



|







 







|
|






1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
..
42
43
44
45
46
47
48

49
50
51
52
53
54
55
..
68
69
70
71
72
73
74
75
76
77
78

79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
...
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148

149


150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
...
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311






312
313
314
315
316
317
318
...
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
...
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
/// \file row.h
/// \brief Declares the classes for holding row data from a result set.

/***********************************************************************
 Copyright © 1998 by Kevin Atkinson
           © 1999-2001 by MySQL AB
           © 2004-2008 by Educational Technology Resources, Inc.
           © 2015 by Warren Young

 Others may also hold copyrights on code in this file.  See the
 CREDITS.txt file in the top directory of the distribution for details.

 This file is part of libtabula.

 libtabula is free software; you can redistribute it and/or modify it
 under the terms of the GNU Lesser General Public License as published
 by the Free Software Foundation; either version 2.1 of the License, or
 (at your option) any later version.
................................................................................
#include <string>

namespace libtabula {

#if !defined(DOXYGEN_IGNORE)
// Make Doxygen ignore this
class FieldNames;

#endif

/// \brief Manages rows from a result set.
///
/// This class is like an extended version of a \c const \c std::vector
/// of libtabula::String.  It adds stuff for populating the vector.  As
/// for why it's \c const, what would it mean to modify a Row?  If we
................................................................................
	/// \brief Pointer to bool data member, for use by safe bool
	/// conversion operator.
	///
	/// \see http://www.artima.com/cppsource/safebool.html
	typedef bool Row::*private_bool_type;

public:
	/// \brief Base class for drivers to use for passing raw row data to
	/// us, and optionally to extend for stashing driver-specific info.
	///
	/// \internal This is public because the driver passes pointers to

	/// such objects to our ctor, and many other std::vector like
	/// interfaces on this object are implemented in terms of it.  End
	/// user code should not create such objects directly.
	class Impl : public std::vector<String>
	{
	public:
		Impl() { }
		virtual ~Impl() { } 
	};

	/// \brief Alias of Impl for compatibility with STL and MySQL++
	/// code.
	typedef Impl list_type;

	/// \brief constant iterator type
	typedef list_type::const_iterator const_iterator;

	/// \brief constant reference type
	typedef list_type::const_reference const_reference;

................................................................................
	initialized_(false)
	{
	}
	
	/// \brief Copy constructor
	Row(const Row& r) :
	OptionalExceptions(),
	data_(r.data_),
	field_names_(r.field_names_),
	initialized_(r.initialized_)
	{
	}

	/// \brief Create a row object
	///
	/// \param pri raw row data, from the C API driver
	/// \param fn list of fields in this row

	/// \param te if true, throw exceptions on errors


	Row(Impl* pri, const RefCountedPointer<FieldNames>& fn, bool te = true);

	/// \brief Destroy object
	~Row() { }

	/// \brief Get a const reference to the field given its index
	///
	/// \throw libtabula::BadIndex if the row is not initialized or there
	/// are less than \c i fields in the row.
	const_reference at(size_type i) const;

	/// \brief Get a reference to the last element of the vector
	const_reference back() const { return data_->back(); }

	/// \brief Return a const iterator pointing to first element in the
	/// container
	const_iterator begin() const { return data_->begin(); }

	/// \brief Returns true if container is empty
	bool empty() const { return data_->empty(); }

	/// \brief Return a const iterator pointing to one past the last
	/// element in the container
	const_iterator end() const { return data_->end(); }

	/// \brief Get an "equal list" of the fields and values in this row
	///
	/// When inserted into a C++ stream, the delimiter 'd' will be used
	/// between the items, " = " is the relationship operator, and items
	/// will be quoted and escaped.
	equal_list_ba<FieldNames, Row, quote_type0>
................................................................................
			bool t7 = false, bool t8 = false, bool t9 = false,
			bool ta = false, bool tb = false, bool tc = false) const;

	/// \brief Returns a field's index given its name
	size_type field_num(const char* name) const;

	/// \brief Get a reference to the first element of the vector
	const_reference front() const { return data_->front(); }

	/// \brief Return maximum number of elements that can be stored
	/// in container without resizing.
	size_type max_size() const { return initialized_ ? data_.max_size() : 0; }

	/// \brief Assignment operator
	Row& operator =(const Row& rhs);







	/// \brief Get the value of a field given its name.
	///
	/// If the field does not exist in this row, we throw a BadFieldName
	/// exception if exceptions are enabled, or an empty row if not.
	/// An empty row tests as false in bool context.
	///
................................................................................
	operator private_bool_type() const
	{
		return initialized_ && data_.size() ? &Row::initialized_ : 0;
	}

	/// \brief Return reverse iterator pointing to first element in the
	/// container
	const_reverse_iterator rbegin() const { return data_->rbegin(); }

	/// \brief Return reverse iterator pointing to one past the last
	/// element in the container
	const_reverse_iterator rend() const { return data_->rend(); }

	/// \brief Get the number of fields in the row.
	size_type size() const { return initialized_ ? data_.size() : 0; }

	/// \brief Get a list of the values in this row
	///
	/// When inserted into a C++ stream, the delimiter 'd' will be used
................................................................................
		std::vector<bool> vb;
		create_vector(*this, vb, s0, s1, s2, s3, s4, s5, s6, s7, s8,
				s9, sa, sb, sc);
		return value_list_b<Row, quote_type0>(*this, vb, ",", quote);
	}

private:
	RefCountedPointer<Impl> data_;
	const RefCountedPointer<FieldNames> field_names_;
	bool initialized_;
};

} // end namespace libtabula

#endif // !defined(LIBTABULA_ROW_H)