BIND 10 #1062: add a base for DatabaseZoneHandle::find()

BIND 10 Development do-not-reply at isc.org
Thu Aug 11 16:48:58 UTC 2011


#1062: add a base for DatabaseZoneHandle::find()
-------------------------------------+-------------------------------------
                   Reporter:         |                 Owner:  jinmei
  jinmei                             |                Status:  reviewing
                       Type:  task   |             Milestone:
                   Priority:  major  |  Sprint-20110816
                  Component:  data   |            Resolution:
  source                             |             Sensitive:  0
                   Keywords:         |           Sub-Project:  DNS
            Defect Severity:  N/A    |  Estimated Difficulty:  5.0
Feature Depending on Ticket:         |           Total Hours:  0
        Add Hours to Ticket:  0      |
                  Internal?:  0      |
-------------------------------------+-------------------------------------
Changes (by jelte):

 * owner:  jelte => jinmei


Comment:

 Replying to [comment:9 jinmei]:
 > Replying to [comment:8 jelte]:
 >
 > A few preliminary notes:
 >
 > - (as always) I made some minor editorial changes
 > - please note that in #1061 it's likely we'll change "connection" to
 >   "accessor".  some of internal variable names, test cases, etc. may
 >   have to be adjusted as a result.

 Yes I noticed, I did a quick check yesterday and luckily git seems quite
 good at
 handling the changes for existing things. But yeah I'll still need to run
 through the things I added.

 > - I know comments below are not short, but most of them are quite
 >   minor or trivial to address.  The (almost) only real point to
 >   discuss is the use of try-catch in find().
 >
 > > > '''General Discussions'''
 > > >
 > > > - I agree with changing getNextRecord() to a non const member
 function
 > > >   for the reason you explained.  But this would also mean find()
 > > >   shouldn't be a cost member function of DatabaseClient::Finder (and
 > > >   therefore, of ZoneFinder).  [...]
 > >
 > > ok, unconsted it
 >
 > Hmm, it doesn't seem to be changed...
 >

 it appears I lost one of my commits :/ anyway. Unconsted again.

 > > > - Don't we need to use loggers in database and sqlite3_connection?
 > > >
 > >
 > > hmm, yes, completely forgot about those :/
 > >
 > > I added some in find(), it'll log either a successful return, or one
 of the
 > > errors as caught by the big try/catch introduced in this revision.
 >
 > Looks good, but I'd also include the class name and some identifier of
 > the underlying database (maybe we need a method in
 > DatabaseConnection?) for all these log messages, e.g.:
 > {{{
 >     logger.debug(DBG_TRACE_DETAILED,
 DATASRC_DATABASE_FIND_RECORDS).arg(name).arg(getClass()).arg(type).arg(connection_->getDBName());
 > }}}
 > (I assume a new method "getDBName()", which would result in something
 > like "sqlite3 (+ perhaps filename)")
 >

 I'm afraid putting in the full path may be a bit long, so I added yet
 another utility method to util/filename.h; getNameAndExtension, and the
 result
 for getDBName() is now "sqlite_<slitefile>". This can be ambiguous should
 there
 be more than one sqlite database with the same name in different
 directories,
 but I personally prefer it over full paths. Maybe we should make it
 something
 user-settable, esp if we want to support any number of
 any type of backend (where there may not be something like a file to base
 it
 on), and/or make it user-settable but default to something like this. But
 both
 of those are possible future enhancements :)


 > > > - in database.cc, I guess the exception/error handling needs to be
 > > >   revised. [...]
 > >
 > > I've implemented this approach for now, doing it with RAII is probably
 better,
 > > but may mean some other interface changes as well, and probably an
 entire
 > > different approach to the way we use the sqlite stmt variables right
 now
 > > (although i guess some of the potential problems i see would also be
 present
 > > now)
 >
 > The revised code seems to do the right thing, but I'm personally
 > concerned about the heavily nested try-catch logic:
 >


 Yes, we should do RAII, and we should probably do it in a form where
 searchForRecords returns some object you can call getNext() on (apparently
 michal is creating something similar for #1067, and the base class seems
 the
 same as what i need here).

 I am passing this back to you now though, though so you can look at these
 other
 changes


 > > I did add some tests that make sure resetSearch() (the cleanup method)
 is
 > > called, both on completion of a call to find() and on any exception
 that is
 > > raised.
 >
 > Regarding tests for resetSearch(): why are some of the tests commented
 > out?
 > {{{
 >     // Trigger the hardcoded exceptions and see if find() has cleaned up
 >     /*
 >     EXPECT_THROW(finder->find(isc::dns::Name("dsexception.in.search."),
 >                                               isc::dns::RRType::A(),
 >                                               NULL,
 ZoneFinder::FIND_DEFAULT),
 >                  DataSourceError);
 > ...
 > }}}
 >

 woops, they should not have been, fixed

 > > > '''database.h/cc'''
 > > >
 > > > - maybe we want to use a plain old array instead of vector for
 > > >   columns, if we are sure it has a fixed size? [...]
 > >
 > > Hmm, yes, a direct array is probably better, though also slightly
 > > more dangerous.  Therefore I also added a second argument, the
 > > vector size. Doesn't provide full protection against passing in a
 > > smaller array, but at least it's something; the dabaseimplementation
 > > should check it.
 >
 > If you like the efficiency of the array but want to make it safe, an
 > alternative would be to use boost::array (at the cost of the
 > additional dependency) and use at() instead of operator[] inside the
 > callee.  I don't have a strong opinion, and leave it to you.
 >

 no current array is fine imo, there has to be some method of foot-
 shooting, right ;)

 > > > - (general) I think it's better to define an enum or something to
 > > >   represent the semantics of the vector used in getNextRecord(),
 > > >   rather than hardcoding the magic numbers:
 > > > {{{
 > > >         if (columns.size() != 4) {
 > > > ...
 > > >             const isc::dns::RRType cur_type(columns[0]);
 > > >             const isc::dns::RRTTL cur_ttl(columns[1]);
 > > > }}}
 > >
 > > ack, done
 >
 > Looks good, but I guess RecordColumnCount should also be all upper
 > cased (with _, i.e. RECORD_COLUMN_COUNT) as it's a constant according
 > to the coding guideline.
 >

 ack

 > Also, this comment seems incomplete:
 > {{{
 >         TTL_COLUMN = 1,     ///< The TTL of the record (a
 > }}}
 >
 > > > - addOrCreate(): it's not clear how it's possible that type !=
 rrset->getType():
 > [...]
 > > >   (BTW, lcov indicates this case isn't covered by tests)
 > >
 > > yes well with the current code it shouldn't be possible, and since
 > > addOrCreate is in anonymous namespace we can't directly call it (or
 > > did we have a clean way to do that?), so it's not covered by a
 > > test...
 > >
 > > made it an assert.
 >
 > With the revised (with assert), I think code missing the test is okay.
 > One thing I didn't notice in the previous code: the assert() should
 > probably be better before the TTL check:
 > {{{
 >         // This is a check to make sure find() is not messing things up
 >         assert(type == rrset->getType());
 >
 >         if (ttl < rrset->getTTL()) {
 >             rrset->setTTL(ttl);
 >         }
 > }}}
 >

 Should not matter in practice, but yeah, is better :)

 > BTW maybe we want to log the event when we perform setTTL()?
 >

 good idea (long discussion on jabber room about level. no real consensus,
 making
 it INFO (but I can easily see it being DBG_TRACE_BASIC as well)

 btw, i also changed the if; if we have such a message we should also print
 it when the first TTL happens to be the lowest one


 > > > '''sqlite3_connection.cc'''
 > > >
 > > > - In searchForRecords(), sqlite3_bind_text() can fail (and
 > > >   sqlite3_bind_int() may also fail in theory?), and we should catch
 > > >   the case even though it would be a rare event.
 > >
 > > ack, done
 >
 > Looks good, but in #1068 I noticed we might rather use
 > sqlite3_errmsg() rather than including the raw result code.  That is,
 >
 > This way, we can eliminate the temporary variable, can make methods
 > shorter, and can provide more human readable what() message.
 >

 ack, changed

 > > > - convertToPlainChar(): (I'm asking partly because I forgot:-) in
 > > >   which case can ucp be NULL?  Also, the main purpose of this helper
 > > >   function is to work around the C++ strictness on the difference
 > > >   between 'unsigned char*' and 'char *' (sqlite3 uses the former
 > > >   throughout its API, while std::string accepts the latter).  And,
 > > >   from a point of type safe purist this conversion isn't correct
 > > >   (because in theory it's not guaranteed that these two pointer
 types
 > > >   are convertible).  I'd note these points as comments.
 > >
 > > IIRC the return value from sqlite_column_text() can be NULL if the
 database
 > > field itself was NULL or if it has run out of memory. [...]
 >
 > Ah, okay, I experimentally commented out the NULL check and saw the
 > tests triggered segfault:-)  Hmm, so, technically, I think we
 > should separate these two cases: if ucp == NULL and sqlite3_errcode()
 > is NOMEM we should probably throw an exception rather than silently
 > convert it to ""...
 >

 done. Added the dbparameters as an argument, needed for sqlite3_errcode

 >
 > > > - getNextRecord(): although it should be rare and might be purely in
 > > >   theory, this method can internally throw an exception (bad_alloc).
 > > >   we should probably catch that within this method and convert it to
 > > >   DataSourceError so that the application can handle that situation
 > > >   more gracefully (e.g., exit after closing the DB connection).
 > >
 > > done
 >
 > bad_alloc can happen only here:
 > {{{
 >                 columns[column] =
 convertToPlainChar(sqlite3_column_text(
 >                                                     current_stmt,
 column));
 > }}}
 > so we could narrow the try-catch scope.  But you might rather want to
 > avoid missing a non obvious case, so I'd leave it to you.
 >

 nah, reduced scope.

 > BTW, I now notice one another point in this method: we should probably
 > do validate column_count before sqlite3_step.
 >

 ack, moved.

 > Also note you may want to use sqlite3_errmsg() instead of playing with
 > the result code directly (see above).

 yes, though here it is slightly trickier, as we had a resetSearch() there
 (which
 made a couple of sqlite3_ calls that would cause the errmsg value to be
 either
 undefined or unrelated). But since we now catch the error in the calling
 method,
 and do resetSearch() there, I just removed the resetSearch() here.
 (the other option would be to copy the errmsg() result first)

 >
 > > > - not a big deal especially in tests, but if I understand it
 correctly
 > > >   "conn" can be scoped_ptr instead of shared_ptr:
 > >
 > > ack, changed. Actually, for countRecords, see below.
 >
 > Okay, and I suspect you can remove this:
 > {{{
 > #include <boost/shared_ptr.hpp>
 > }}}
 >

 yup


 > > >   - if not very hard, I'd check RDATA(s), not only the number of
 > > >     RDATAs and counts.  maybe we could use rrset[s]Check defined in
 > > >     lib/testutils/dnsmessage_test.h.
 >
 > This doesn't seem to be addressed.  Did you think it too hard?

 oh, oops, somehow read over this one. Added it by passing a vector of
 expected_rdatas and expected_sig_rdatas; the expected_counts that were
 there are
 now unnecessary (same as size() on those vectors), so i removed those as
 arguments. (i did not want to do something clever with the data as it is
 already
 in the database; as that could potentially cause internal problems we
 miss, like
 data not being checked at all)

-- 
Ticket URL: <http://bind10.isc.org/ticket/1062#comment:11>
BIND 10 Development <http://bind10.isc.org>
BIND 10 Development


More information about the bind10-tickets mailing list