Browse Source

Merge branch 'master' into #1067

To bring in changes and make the interface more consistent easier.

Conflicts:
	src/lib/datasrc/database.cc
	src/lib/datasrc/database.h
	src/lib/datasrc/sqlite3_accessor.cc
	src/lib/datasrc/sqlite3_accessor.h
	src/lib/datasrc/tests/database_unittest.cc
	src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
Michal 'vorner' Vaner 13 years ago
parent
commit
ee552335b8

+ 4 - 0
ChangeLog

@@ -1,3 +1,7 @@
+278.	[doc]		jelte
+	Add logging configuration documentation to the guide.
+	(Trac #1011, git TODO)
+
 277.	[func]		jerry
 	Implement the SRV rrtype according to RFC2782.
 	(Trac #1128, git 5fd94aa027828c50e63ae1073d9d6708e0a9c223)

+ 2 - 2
README

@@ -67,8 +67,8 @@ e.g.,
 Operating-System specific tips:
 
 - FreeBSD
-  You may need to install a python binding for sqlite3 by hand.  A
-  sample procedure is as follows:
+  You may need to install a python binding for sqlite3 by hand.
+  A sample procedure is as follows:
   - add the following to /etc/make.conf
     PYTHON_VERSION=3.1
   - build and install the python binding from ports, assuming the top

+ 691 - 69
doc/guide/bind10-guide.xml

@@ -146,7 +146,7 @@
 	The processes started by the <command>bind10</command>
 	command have names starting with "b10-", including:
       </para>
-      
+
       <para>
 
         <itemizedlist>
@@ -547,7 +547,7 @@ Debian and Ubuntu:
           <varlistentry>
             <term>--prefix</term>
             <listitem>
-              <simpara>Define the the installation location (the
+              <simpara>Define the installation location (the
                 default is <filename>/usr/local/</filename>).
               </simpara>
             </listitem> 
@@ -1480,61 +1480,679 @@ then change those defaults with config set Resolver/forward_addresses[0]/address
   <chapter id="logging">
     <title>Logging</title>
 
-<!-- TODO: how to configure logging, logging destinations etc. -->
+    <section>
+      <title>Logging configuration</title>
 
-    <para>
-        Each message written by BIND 10 to the configured logging destinations
-        comprises a number of components that identify the origin of the
-        message and, if the message indicates a problem, information about the
-        problem that may be useful in fixing it.
-    </para>
+      <para>
 
-    <para>
-        Consider the message below logged to a file:
-        <screen>2011-06-15 13:48:22.034 ERROR [b10-resolver.asiolink]
-    ASIODNS_OPENSOCK error 111 opening TCP socket to 127.0.0.1(53)</screen>
-    </para>
+	The logging system in BIND 10 is configured through the
+	Logging module. All BIND 10 modules will look at the
+	configuration in Logging to see what should be logged and
+	to where.
 
-    <para>
-      Note: the layout of messages written to the system logging
-      file (syslog) may be slightly different.  This message has
-      been split across two lines here for display reasons; in the
-      logging file, it will appear on one line.)
-    </para>
+<!-- TODO: what is context of Logging module for readers of this guide? -->
 
-    <para>
-      The log message comprises a number of components:
+      </para>
+
+      <section>
+        <title>Loggers</title>
+
+        <para>
+
+	  Within BIND 10, a message is logged through a component
+	  called a "logger". Different parts of BIND 10 log messages
+	  through different loggers, and each logger can be configured
+	  independently of one another.
+
+        </para>
+
+        <para>
+
+	  In the Logging module, you can specify the configuration
+	  for zero or more loggers; any that are not specified will
+	  take appropriate default values..
+
+        </para>
+
+        <para>
+
+	  The three most important elements of a logger configuration
+	  are the <option>name</option> (the component that is
+	  generating the messages), the <option>severity</option>
+	  (what to log), and the <option>output_options</option>
+	  (where to log).
+
+        </para>
+
+        <section>
+          <title>name (string)</title>
+
+          <para>
+	  Each logger in the system has a name, the name being that
+	  of the component using it to log messages. For instance,
+	  if you want to configure logging for the resolver module,
+	  you add an entry for a logger named <quote>Resolver</quote>. This
+	  configuration will then be used by the loggers in the
+	  Resolver module, and all the libraries used by it.
+              </para>
+
+<!-- TODO: later we will have a way to know names of all modules
+
+Right now you can only see what their names are if they are running
+(a simple 'help' without anything else in bindctl for instance).
+
+ -->
+
+        <para>
+
+	  If you want to specify logging for one specific library
+	  within the module, you set the name to
+	  <replaceable>module.library</replaceable>.  For example, the
+	  logger used by the nameserver address store component
+	  has the full name of <quote>Resolver.nsas</quote>. If
+	  there is no entry in Logging for a particular library,
+	  it will use the configuration given for the module.
+
+<!-- TODO: how to know these specific names?
+
+We will either have to document them or tell the administrator to
+specify module-wide logging and see what appears...
+
+-->
+
+        </para>
+
+        <para>
+
+<!-- TODO: severity has not been covered yet -->
+
+	  To illustrate this, suppose you want the cache library
+	  to log messages of severity DEBUG, and the rest of the
+	  resolver code to log messages of severity INFO. To achieve
+	  this you specify two loggers, one with the name
+	  <quote>Resolver</quote> and severity INFO, and one with
+	  the name <quote>Resolver.cache</quote> with severity
+	  DEBUG. As there are no entries for other libraries (e.g.
+	  the nsas), they will use the configuration for the module
+	  (<quote>Resolver</quote>), so giving the desired behavior.
+
+        </para>
+
+        <para>
+
+	  One special case is that of a module name of <quote>*</quote>
+	  (asterisks), which is interpreted as <emphasis>any</emphasis>
+	  module. You can set global logging options by using this,
+	  including setting the logging configuration for a library
+	  that is used by multiple modules (e.g. <quote>*.config</quote>
+	  specifies the configuration library code in whatever
+	  module is using it).
+
+        </para>
+
+        <para>
+
+	  If there are multiple logger specifications in the
+	  configuration that might match a particular logger, the
+	  specification with the more specific logger name takes
+	  precedence. For example, if there are entries for for
+	  both <quote>*</quote> and <quote>Resolver</quote>, the
+	  resolver module &mdash; and all libraries it uses &mdash;
+	  will log messages according to the configuration in the
+	  second entry (<quote>Resolver</quote>). All other modules
+	  will use the configuration of the first entry
+	  (<quote>*</quote>). If there was also a configuration
+	  entry for <quote>Resolver.cache</quote>, the cache library
+	  within the resolver would use that in preference to the
+	  entry for <quote>Resolver</quote>.
+
+        </para>
+
+        <para>
+
+	  One final note about the naming. When specifying the
+	  module name within a logger, use the name of the module
+	  as specified in <command>bindctl</command>, e.g.
+	  <quote>Resolver</quote> for the resolver module,
+	  <quote>Xfrout</quote> for the xfrout module, etc. When
+	  the message is logged, the message will include the name
+	  of the logger generating the message, but with the module
+	  name replaced by the name of the process implementing
+	  the module (so for example, a message generated by the
+	  <quote>Auth.cache</quote> logger will appear in the output
+	  with a logger name of <quote>b10-auth.cache</quote>).
+
+        </para>
+
+        </section>
+
+        <section>
+          <title>severity (string)</title>
+
+        <para>
+
+          This specifies the category of messages logged.
+	  Each message is logged with an associated severity which
+	  may be one of the following (in descending order of
+	  severity):
+        </para>
+
+        <itemizedlist>
+          <listitem>
+            <simpara> FATAL </simpara>
+          </listitem>
+
+          <listitem>
+            <simpara> ERROR </simpara>
+          </listitem>
+
+          <listitem>
+            <simpara> WARN </simpara>
+          </listitem>
+
+          <listitem>
+            <simpara> INFO </simpara>
+          </listitem>
+
+          <listitem>
+            <simpara> DEBUG </simpara>
+          </listitem>
+        </itemizedlist>
+
+        <para>
+
+	  When the severity of a logger is set to one of these
+	  values, it will only log messages of that severity, and
+	  the severities above it. The severity may also be set to
+	  NONE, in which case all messages from that logger are
+	  inhibited.
+
+<!-- TODO: worded wrong? If I set to INFO, why would it show DEBUG which is literally below in that list? -->
+
+        </para>
+
+        </section>
+
+        <section>
+          <title>output_options (list)</title>
+
+        <para>
+
+	  Each logger can have zero or more
+	  <option>output_options</option>. These specify where log
+	  messages are sent to. These are explained in detail below.
+
+        </para>
+
+        <para>
+
+          The other options for a logger are:
+
+        </para>
+
+        </section>
+
+        <section>
+          <title>debuglevel (integer)</title>
+
+        <para>
+
+	  When a logger's severity is set to DEBUG, this value
+	  specifies what debug messages should be printed. It ranges
+	  from 0 (least verbose) to 99 (most verbose).
+        </para>
+
+
+<!-- TODO: complete this sentence:
+
+	  The general classification of debug message types is
+
+TODO; there's a ticket to determine these levels, see #1074
+
+ -->
+
+        <para>
+
+          If severity for the logger is not DEBUG, this value is ignored.
+
+        </para>
+
+        </section>
+
+        <section>
+          <title>additive (true or false)</title>
+
+        <para>
+
+	  If this is true, the <option>output_options</option> from
+	  the parent will be used. For example, if there are two
+	  loggers configured; <quote>Resolver</quote> and
+	  <quote>Resolver.cache</quote>, and <option>additive</option>
+	  is true in the second, it will write the log messages
+	  not only to the destinations specified for
+	  <quote>Resolver.cache</quote>, but also to the destinations
+	  as specified in the <option>output_options</option> in
+	  the logger named <quote>Resolver</quote>.
+
+<!-- TODO: check this -->
+
+      </para>
+
+      </section>
+
+      </section>
+
+      <section>
+        <title>Output Options</title>
+
+        <para>
+
+	  The main settings for an output option are the
+	  <option>destination</option> and a value called
+	  <option>output</option>, the meaning of which depends on
+	  the destination that is set.
+
+        </para>
+
+        <section>
+          <title>destination (string)</title>
+
+          <para>
+
+            The destination is the type of output. It can be one of:
+
+          </para>
+
+          <itemizedlist>
+
+            <listitem>
+              <simpara> console </simpara>
+          </listitem>
+
+            <listitem>
+              <simpara> file </simpara>
+          </listitem>
+
+            <listitem>
+              <simpara> syslog </simpara>
+            </listitem>
+
+          </itemizedlist>
+
+        </section>
+
+        <section>
+          <title>output (string)</title>
+
+        <para>
+
+	  Depending on what is set as the output destination, this
+	  value is interpreted as follows:
+
+        </para>
 
         <variablelist>
-        <varlistentry>
-        <term>2011-06-15 13:48:22.034</term>
-        <listitem><para>
-            The date and time at which the message was generated.
-        </para></listitem>
-        </varlistentry>
-
-        <varlistentry>
-        <term>ERROR</term>
-        <listitem><para>
-            The severity of the message.
-        </para></listitem>
-        </varlistentry>
-
-        <varlistentry>
-        <term>[b10-resolver.asiolink]</term>
-        <listitem><para>
-	    The source of the message.  This comprises two components:
-	    the BIND 10 process generating the message (in this
-	    case, <command>b10-resolver</command>) and the module
-	    within the program from which the message originated
-	    (which in the example is the asynchronous I/O link
-	    module, asiolink).
-        </para></listitem>
-        </varlistentry>
-
-        <varlistentry>
-        <term>ASIODNS_OPENSOCK</term>
-        <listitem><para>
+
+          <varlistentry>
+            <term><option>destination</option> is <quote>console</quote></term>
+            <listitem>
+              <simpara>
+		 The value of output must be one of <quote>stdout</quote>
+		 (messages printed to standard output) or
+		 <quote>stderr</quote> (messages printed to standard
+		 error).
+              </simpara>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term><option>destination</option> is <quote>file</quote></term>
+            <listitem>
+              <simpara>
+		The value of output is interpreted as a file name;
+		log messages will be appended to this file.
+              </simpara>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term><option>destination</option> is <quote>syslog</quote></term>
+            <listitem>
+              <simpara>
+		The value of output is interpreted as the
+		<command>syslog</command> facility (e.g.
+		<emphasis>local0</emphasis>) that should be used
+		for log messages.
+              </simpara>
+            </listitem>
+          </varlistentry>
+
+        </variablelist>
+
+        <para>
+
+          The other options for <option>output_options</option> are:
+
+        </para>
+
+        <section>
+          <title>flush (true of false)</title>
+
+          <para>
+	    Flush buffers after each log message. Doing this will
+	    reduce performance but will ensure that if the program
+	    terminates abnormally, all messages up to the point of
+	    termination are output.
+          </para>
+
+        </section>
+
+        <section>
+          <title>maxsize (integer)</title>
+
+          <para>
+	    Only relevant when destination is file, this is maximum
+	    file size of output files in bytes. When the maximum
+	    size is reached, the file is renamed and a new file opened.
+	    (For example, a ".1" is appended to the name &mdash;
+	    if a ".1" file exists, it is renamed ".2",
+            etc.)
+          </para>
+
+          <para>
+            If this is 0, no maximum file size is used.
+          </para>
+
+        </section>
+
+        <section>
+          <title>maxver (integer)</title>
+
+          <para>
+	    Maximum number of old log files to keep around when
+	    rolling the output file. Only relevant when
+	    <option>destination</option> is <quote>file</quote>.
+          </para>
+
+        </section>
+
+      </section>
+
+      </section>
+
+      <section>
+        <title>Example session</title>
+
+        <para>
+
+	  In this example we want to set the global logging to
+	  write to the file <filename>/var/log/my_bind10.log</filename>,
+	  at severity WARN. We want the authoritative server to
+	  log at DEBUG with debuglevel 40, to a different file
+	  (<filename>/tmp/debug_messages</filename>).
+
+        </para>
+
+        <para>
+
+          Start <command>bindctl</command>.
+
+        </para>
+
+        <para>
+
+           <screen>["login success "]
+&gt; <userinput>config show Logging</userinput>
+Logging/loggers	[]	list
+</screen>
+
+        </para>
+
+        <para>
+
+	  By default, no specific loggers are configured, in which
+	  case the severity defaults to INFO and the output is
+	  written to stderr.
+
+        </para>
+
+        <para>
+
+          Let's first add a default logger:
+
+        </para>
+
+<!-- TODO: adding the empty loggers makes no sense -->
+        <para>
+
+          <screen><userinput>&gt; config add Logging/loggers</userinput>
+&gt; <userinput>config show Logging</userinput>
+Logging/loggers/	list	(modified)
+</screen>
+
+        </para>
+
+        <para>
+
+	  The loggers value line changed to indicate that it is no
+	  longer an empty list:
+
+        </para>
+
+        <para>
+
+          <screen>&gt; <userinput>config show Logging/loggers</userinput>
+Logging/loggers[0]/name	""	string	(default)
+Logging/loggers[0]/severity	"INFO"	string	(default)
+Logging/loggers[0]/debuglevel	0	integer	(default)
+Logging/loggers[0]/additive	false	boolean	(default)
+Logging/loggers[0]/output_options	[]	list	(default)
+</screen>
+
+        </para>
+
+        <para>
+
+	  The name is mandatory, so we must set it. We will also
+	  change the severity as well. Let's start with the global
+	  logger.
+
+        </para>
+
+        <para>
+
+          <screen>&gt; <userinput>config set Logging/loggers[0]/name *</userinput>
+&gt; <userinput>config set Logging/loggers[0]/severity WARN</userinput>
+&gt; <userinput>config show Logging/loggers</userinput>
+Logging/loggers[0]/name	"*"	string	(modified)
+Logging/loggers[0]/severity	"WARN"	string	(modified)
+Logging/loggers[0]/debuglevel	0	integer	(default)
+Logging/loggers[0]/additive	false	boolean	(default)
+Logging/loggers[0]/output_options	[]	list	(default)
+</screen>
+
+        </para>
+
+        <para>
+
+	  Of course, we need to specify where we want the log
+	  messages to go, so we add an entry for an output option.
+
+        </para>
+
+        <para>
+
+          <screen>&gt; <userinput> config add Logging/loggers[0]/output_options</userinput>
+&gt; <userinput> config show Logging/loggers[0]/output_options</userinput>
+Logging/loggers[0]/output_options[0]/destination	"console"	string	(default)
+Logging/loggers[0]/output_options[0]/output	"stdout"	string	(default)
+Logging/loggers[0]/output_options[0]/flush	false	boolean	(default)
+Logging/loggers[0]/output_options[0]/maxsize	0	integer	(default)
+Logging/loggers[0]/output_options[0]/maxver	0	integer	(default)
+</screen>
+
+
+        </para>
+
+        <para>
+
+          These aren't the values we are looking for.
+
+        </para>
+
+        <para>
+
+          <screen>&gt; <userinput> config set Logging/loggers[0]/output_options[0]/destination file</userinput>
+&gt; <userinput> config set Logging/loggers[0]/output_options[0]/output /var/log/bind10.log</userinput>
+&gt; <userinput> config set Logging/loggers[0]/output_options[0]/maxsize 30000</userinput>
+&gt; <userinput> config set Logging/loggers[0]/output_options[0]/maxver 8</userinput>
+</screen>
+
+        </para>
+
+        <para>
+
+	  Which would make the entire configuration for this logger
+	  look like:
+
+        </para>
+
+        <para>
+
+          <screen>&gt; <userinput> config show all Logging/loggers</userinput>
+Logging/loggers[0]/name	"*"	string	(modified)
+Logging/loggers[0]/severity	"WARN"	string	(modified)
+Logging/loggers[0]/debuglevel	0	integer	(default)
+Logging/loggers[0]/additive	false	boolean	(default)
+Logging/loggers[0]/output_options[0]/destination	"file"	string	(modified)
+Logging/loggers[0]/output_options[0]/output	"/var/log/bind10.log"	string	(modified)
+Logging/loggers[0]/output_options[0]/flush	false	boolean	(default)
+Logging/loggers[0]/output_options[0]/maxsize	30000	integer	(modified)
+Logging/loggers[0]/output_options[0]/maxver	8	integer	(modified)
+</screen>
+
+        </para>
+
+        <para>
+
+	  That looks OK, so let's commit it before we add the
+	  configuration for the authoritative server's logger.
+
+        </para>
+
+        <para>
+
+          <screen>&gt; <userinput> config commit</userinput></screen>
+
+        </para>
+
+        <para>
+
+	  Now that we have set it, and checked each value along
+	  the way, adding a second entry is quite similar.
+
+        </para>
+
+        <para>
+
+          <screen>&gt; <userinput> config add Logging/loggers</userinput>
+&gt; <userinput> config set Logging/loggers[1]/name Auth</userinput>
+&gt; <userinput> config set Logging/loggers[1]/severity DEBUG</userinput>
+&gt; <userinput> config set Logging/loggers[1]/debuglevel 40</userinput>
+&gt; <userinput> config add Logging/loggers[1]/output_options</userinput>
+&gt; <userinput> config set Logging/loggers[1]/output_options[0]/destination file</userinput>
+&gt; <userinput> config set Logging/loggers[1]/output_options[0]/output /tmp/auth_debug.log</userinput>
+&gt; <userinput> config commit</userinput>
+</screen>
+
+        </para>
+
+        <para>
+
+	  And that's it. Once we have found whatever it was we
+	  needed the debug messages for, we can simply remove the
+	  second logger to let the authoritative server use the
+	  same settings as the rest.
+
+        </para>
+
+        <para>
+
+          <screen>&gt; <userinput> config remove Logging/loggers[1]</userinput>
+&gt; <userinput> config commit</userinput>
+</screen>
+
+        </para>
+
+        <para>
+
+	  And every module will now be using the values from the
+	  logger named <quote>*</quote>.
+
+        </para>
+
+      </section>
+
+    </section>
+
+    <section>
+      <title>Logging Message Format</title>
+
+      <para>
+	  Each message written by BIND 10 to the configured logging
+	  destinations comprises a number of components that identify
+	  the origin of the message and, if the message indicates
+	  a problem, information about the problem that may be
+	  useful in fixing it.
+      </para>
+
+      <para>
+          Consider the message below logged to a file:
+          <screen>2011-06-15 13:48:22.034 ERROR [b10-resolver.asiolink]
+    ASIODNS_OPENSOCK error 111 opening TCP socket to 127.0.0.1(53)</screen>
+      </para>
+
+      <para>
+        Note: the layout of messages written to the system logging
+        file (syslog) may be slightly different.  This message has
+        been split across two lines here for display reasons; in the
+        logging file, it will appear on one line.)
+      </para>
+
+      <para>
+        The log message comprises a number of components:
+
+          <variablelist>
+          <varlistentry>
+          <term>2011-06-15 13:48:22.034</term>
+<!-- TODO: timestamp repeated even if using syslog? -->
+          <listitem><para>
+              The date and time at which the message was generated.
+          </para></listitem>
+          </varlistentry>
+
+          <varlistentry>
+          <term>ERROR</term>
+          <listitem><para>
+              The severity of the message.
+          </para></listitem>
+          </varlistentry>
+
+          <varlistentry>
+          <term>[b10-resolver.asiolink]</term>
+          <listitem><para>
+            The source of the message.  This comprises two components:
+            the BIND 10 process generating the message (in this
+            case, <command>b10-resolver</command>) and the module
+            within the program from which the message originated
+            (which in the example is the asynchronous I/O link
+            module, asiolink).
+          </para></listitem>
+          </varlistentry>
+
+          <varlistentry>
+          <term>ASIODNS_OPENSOCK</term>
+          <listitem><para>
 	    The message identification.  Every message in BIND 10
 	    has a unique identification, which can be used as an
 	    index into the <ulink
@@ -1542,25 +2160,29 @@ then change those defaults with config set Resolver/forward_addresses[0]/address
 	    Manual</citetitle></ulink> (<ulink
 	    url="http://bind10.isc.org/docs/bind10-messages.html"
 	    />) from which more information can be obtained.
-        </para></listitem>
-        </varlistentry>
-
-        <varlistentry>
-        <term>error 111 opening TCP socket to 127.0.0.1(53)</term>
-        <listitem><para>
-            A brief description of the cause of the problem.  Within this text,
-            information relating to the condition that caused the message to
-            be logged will be included.  In this example, error number 111
-            (an operating system-specific error number) was encountered when
-            trying to open a TCP connection to port 53 on the local system
-            (address 127.0.0.1).  The next step would be to find out the reason
-            for the failure by consulting your system's documentation to
-            identify what error number 111 means.
-        </para></listitem>
-        </varlistentry>
-        </variablelist>
+          </para></listitem>
+          </varlistentry>
+
+          <varlistentry>
+          <term>error 111 opening TCP socket to 127.0.0.1(53)</term>
+          <listitem><para>
+	      A brief description of the cause of the problem.
+	      Within this text, information relating to the condition
+	      that caused the message to be logged will be included.
+	      In this example, error number 111 (an operating
+	      system-specific error number) was encountered when
+	      trying to open a TCP connection to port 53 on the
+	      local system (address 127.0.0.1).  The next step
+	      would be to find out the reason for the failure by
+	      consulting your system's documentation to identify
+	      what error number 111 means.
+          </para></listitem>
+          </varlistentry>
+          </variablelist>
+      </para>
+
+    </section>
 
-    </para>
   </chapter>
 
 <!-- TODO: how to help: run unit tests, join lists, review trac tickets -->

+ 4 - 4
src/bin/auth/query.cc

@@ -31,7 +31,7 @@ namespace isc {
 namespace auth {
 
 void
-Query::getAdditional(const ZoneFinder& zone, const RRset& rrset) const {
+Query::getAdditional(ZoneFinder& zone, const RRset& rrset) const {
     RdataIteratorPtr rdata_iterator(rrset.getRdataIterator());
     for (; !rdata_iterator->isLast(); rdata_iterator->next()) {
         const Rdata& rdata(rdata_iterator->getCurrent());
@@ -47,7 +47,7 @@ Query::getAdditional(const ZoneFinder& zone, const RRset& rrset) const {
 }
 
 void
-Query::findAddrs(const ZoneFinder& zone, const Name& qname,
+Query::findAddrs(ZoneFinder& zone, const Name& qname,
                  const ZoneFinder::FindOptions options) const
 {
     // Out of zone name
@@ -86,7 +86,7 @@ Query::findAddrs(const ZoneFinder& zone, const Name& qname,
 }
 
 void
-Query::putSOA(const ZoneFinder& zone) const {
+Query::putSOA(ZoneFinder& zone) const {
     ZoneFinder::FindResult soa_result(zone.find(zone.getOrigin(),
         RRType::SOA()));
     if (soa_result.code != ZoneFinder::SUCCESS) {
@@ -104,7 +104,7 @@ Query::putSOA(const ZoneFinder& zone) const {
 }
 
 void
-Query::getAuthAdditional(const ZoneFinder& zone) const {
+Query::getAuthAdditional(ZoneFinder& zone) const {
     // Fill in authority and addtional sections.
     ZoneFinder::FindResult ns_result = zone.find(zone.getOrigin(),
                                                  RRType::NS());

+ 4 - 4
src/bin/auth/query.h

@@ -69,7 +69,7 @@ private:
     /// Adds a SOA of the zone into the authority zone of response_.
     /// Can throw NoSOA.
     ///
-    void putSOA(const isc::datasrc::ZoneFinder& zone) const;
+    void putSOA(isc::datasrc::ZoneFinder& zone) const;
 
     /// \brief Look up additional data (i.e., address records for the names
     /// included in NS or MX records).
@@ -85,7 +85,7 @@ private:
     /// query is to be found.
     /// \param rrset The RRset (i.e., NS or MX rrset) which require additional
     /// processing.
-    void getAdditional(const isc::datasrc::ZoneFinder& zone,
+    void getAdditional(isc::datasrc::ZoneFinder& zone,
                        const isc::dns::RRset& rrset) const;
 
     /// \brief Find address records for a specified name.
@@ -104,7 +104,7 @@ private:
     /// be found.
     /// \param qname The name in rrset RDATA.
     /// \param options The search options.
-    void findAddrs(const isc::datasrc::ZoneFinder& zone,
+    void findAddrs(isc::datasrc::ZoneFinder& zone,
                    const isc::dns::Name& qname,
                    const isc::datasrc::ZoneFinder::FindOptions options
                    = isc::datasrc::ZoneFinder::FIND_DEFAULT) const;
@@ -127,7 +127,7 @@ private:
     ///
     /// \param zone The \c ZoneFinder through which the NS and additional data
     /// for the query are to be found.
-    void getAuthAdditional(const isc::datasrc::ZoneFinder& zone) const;
+    void getAuthAdditional(isc::datasrc::ZoneFinder& zone) const;
 
 public:
     /// Constructor from query parameters.

+ 2 - 2
src/bin/auth/tests/query_unittest.cc

@@ -127,7 +127,7 @@ public:
     virtual FindResult find(const isc::dns::Name& name,
                             const isc::dns::RRType& type,
                             RRsetList* target = NULL,
-                            const FindOptions options = FIND_DEFAULT) const;
+                            const FindOptions options = FIND_DEFAULT);
 
     // If false is passed, it makes the zone broken as if it didn't have the
     // SOA.
@@ -165,7 +165,7 @@ private:
 
 ZoneFinder::FindResult
 MockZoneFinder::find(const Name& name, const RRType& type,
-                     RRsetList* target, const FindOptions options) const
+                     RRsetList* target, const FindOptions options)
 {
     // Emulating a broken zone: mandatory apex RRs are missing if specifically
     // configured so (which are rare cases).

+ 123 - 0
src/bin/bind10/creatorapi.txt

@@ -0,0 +1,123 @@
+Socket creator API
+==================
+
+This API is between Boss and other modules to allow them requesting of sockets.
+For simplicity, we will use the socket creator for all (even non-privileged)
+ports for now, but we should have some function where we can abstract it later.
+
+Goals
+-----
+* Be able to request a socket of any combination IPv4/IPv6 UDP/TCP bound to given
+  port and address (sockets that are not bound to anything can be created
+  without privileges, therefore are not requested from the socket creator).
+* Allow to provide the same socket to multiple modules (eg. multiple running
+  auth servers).
+* Allow releasing the sockets (in case all modules using it give it up,
+  terminate or crash).
+* Allow restricting of the sharing (don't allow shared socket between auth
+  and recursive, as the packets would often get to the wrong application,
+  show error instead).
+* Get the socket to the application.
+
+Transport of sockets
+--------------------
+It seems we are stuck with current msgq for a while and there's a chance the
+new replacement will not be able to send sockets inbound. So, we need another
+channel.
+
+The boss will create a unix-domain socket and listen on it. When something
+requests a socket over the command channel and the socket is created, some kind
+of token is returned to the application (which will represent the future
+socket). The application then connects to the unix-domain socket, sends the
+token over the connection (so Boss will know which socket to send there, in case
+multiple applications ask for sockets simultaneously) and Boss sends the socket
+in return.
+
+In theory, we could send the requests directly over the unix-domain
+socket, but it has two disadvantages:
+* The msgq handles serializing/deserializing of structured
+  information (like the parameters to be used), we would have to do it
+  manually on the socket.
+* We could place some kind of security in front of msgq (in case file
+  permissions are not enough, for example if they are not honored on
+  socket files, as indicated in the first paragraph of:
+  http://lkml.indiana.edu/hypermail/linux/kernel/0505.2/0008.html).
+  The socket would have to be secured separately. With the tokens,
+  there's some level of security already - someone not having the
+  token can't request a priviledged socket.
+
+Caching of sockets
+------------------
+To allow sending the same socket to multiple application, the Boss process will
+hold a cache. Each socket that is created and sent is kept open in Boss and
+preserved there as well. A reference count is kept with each of them.
+
+When another application asks for the same socket, it is simply sent from the
+cache instead of creating it again by the creator.
+
+When application gives the socket willingly (by sending a message over the
+command channel), the reference count can be decreased without problems. But
+when the application terminates or crashes, we need to decrease it as well.
+There's a problem, since we don't know which command channel connection (eg.
+lname) belongs to which PID. Furthermore, the applications don't need to be
+started by boss.
+
+There are two possibilities:
+* Let the msgq send messages about disconnected clients (eg. group message to
+  some name). This one is better if we want to migrate to dbus, since dbus
+  already has this capability as well as sending the sockets inbound (at least it
+  seems so on unix) and we could get rid of the unix-domain socket completely.
+* Keep the unix-domain connections open forever. Boss can remember which socket
+  was sent to which connection and when the connection closes (because the
+  application crashed), it can drop all the references on the sockets. This
+  seems easier to implement.
+
+The commands
+------------
+* Command to release a socket. This one would have single parameter, the token
+  used to get the socket. After this, boss would decrease its reference count
+  and if it drops to zero, close its own copy of the socket. This should be used
+  when the module stops using the socket (and after closes it). The
+  library could remember the file-descriptor to token mapping (for
+  common applications that don't request the same socket multiple
+  times in parallel).
+* Command to request a socket. It would have parameters to specify which socket
+  (IP address, address family, port) and how to allow sharing. Sharing would be
+  one of:
+  - None
+  - Same kind of application (however, it is not entirely clear what
+    this means, in case it won't work out intuitively, we'll need to
+    define it somehow)
+  - Any kind of application
+  And a kind of application would be provided, to decide if the sharing is
+  possible (eg. if auth allows sharing with the same kind and something else
+  allows sharing with anything, the sharing is not possible, two auths can).
+
+  It would return either error (the socket can't be created or sharing is not
+  possible) or the token. Then there would be some time for the application to
+  pick up the requested socket.
+
+Examples
+--------
+We probably would have a library with blocking calls to request the
+sockets, so a code could look like:
+
+(socket_fd, token) = request_socket(address, port, 'UDP', SHARE_SAMENAME, 'test-application')
+sock = socket.fromfd(socket_fd)
+
+# Some sock.send and sock.recv stuff here
+
+sock.close()
+release_socket(socket_fd) # or release_socket(token)
+
+Known limitations
+-----------------
+Currently the socket creator doesn't support specifying any socket
+options. If it turns out there are any options that need to be set
+before bind(), we'll need to extend it (and extend the protocol as
+well). If we want to support them, we'll have to solve a possible
+conflict (what to do when two applications request the same socket and
+want to share it, but want different options).
+
+The current socket creator doesn't know raw sockets, but if they are
+needed, it should be easy to add.

+ 229 - 6
src/lib/datasrc/database.cc

@@ -12,6 +12,8 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <vector>
+
 #include <datasrc/database.h>
 #include <datasrc/data_source.h>
 #include <datasrc/iterator.h>
@@ -19,7 +21,13 @@
 #include <exceptions/exceptions.h>
 #include <dns/name.h>
 #include <dns/rrclass.h>
-#include <dns/rrttl.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <datasrc/data_source.h>
+#include <datasrc/logger.h>
+
+#include <boost/foreach.hpp>
 
 #include <string>
 
@@ -68,14 +76,229 @@ DatabaseClient::Finder::Finder(boost::shared_ptr<DatabaseAccessor>
     zone_id_(zone_id)
 { }
 
+namespace {
+// Adds the given Rdata to the given RRset
+// If the rrset is an empty pointer, a new one is
+// created with the given name, class, type and ttl
+// The type is checked if the rrset exists, but the
+// name is not.
+//
+// Then adds the given rdata to the set
+//
+// Raises a DataSourceError if the type does not
+// match, or if the given rdata string does not
+// parse correctly for the given type and class
+//
+// The DatabaseAccessor is passed to print the
+// database name in the log message if the TTL is
+// modified
+void addOrCreate(isc::dns::RRsetPtr& rrset,
+                    const isc::dns::Name& name,
+                    const isc::dns::RRClass& cls,
+                    const isc::dns::RRType& type,
+                    const isc::dns::RRTTL& ttl,
+                    const std::string& rdata_str,
+                    const DatabaseAccessor& db
+                )
+{
+    if (!rrset) {
+        rrset.reset(new isc::dns::RRset(name, cls, type, ttl));
+    } else {
+        // This is a check to make sure find() is not messing things up
+        assert(type == rrset->getType());
+        if (ttl != rrset->getTTL()) {
+            if (ttl < rrset->getTTL()) {
+                rrset->setTTL(ttl);
+            }
+            logger.info(DATASRC_DATABASE_FIND_TTL_MISMATCH)
+                .arg(db.getDBName()).arg(name).arg(cls)
+                .arg(type).arg(rrset->getTTL());
+        }
+    }
+    try {
+        rrset->addRdata(isc::dns::rdata::createRdata(type, cls, rdata_str));
+    } catch (const isc::dns::rdata::InvalidRdataText& ivrt) {
+        // at this point, rrset may have been initialised for no reason,
+        // and won't be used. But the caller would drop the shared_ptr
+        // on such an error anyway, so we don't care.
+        isc_throw(DataSourceError,
+                    "bad rdata in database for " << name << " "
+                    << type << ": " << ivrt.what());
+    }
+}
+
+// This class keeps a short-lived store of RRSIG records encountered
+// during a call to find(). If the backend happens to return signatures
+// before the actual data, we might not know which signatures we will need
+// So if they may be relevant, we store the in this class.
+//
+// (If this class seems useful in other places, we might want to move
+// it to util. That would also provide an opportunity to add unit tests)
+class RRsigStore {
+public:
+    // Adds the given signature Rdata to the store
+    // The signature rdata MUST be of the RRSIG rdata type
+    // (the caller must make sure of this).
+    // NOTE: if we move this class to a public namespace,
+    // we should add a type_covered argument, so as not
+    // to have to do this cast here.
+    void addSig(isc::dns::rdata::RdataPtr sig_rdata) {
+        const isc::dns::RRType& type_covered =
+            static_cast<isc::dns::rdata::generic::RRSIG*>(
+                sig_rdata.get())->typeCovered();
+        sigs[type_covered].push_back(sig_rdata);
+    }
+
+    // If the store contains signatures for the type of the given
+    // rrset, they are appended to it.
+    void appendSignatures(isc::dns::RRsetPtr& rrset) const {
+        std::map<isc::dns::RRType,
+                 std::vector<isc::dns::rdata::RdataPtr> >::const_iterator
+            found = sigs.find(rrset->getType());
+        if (found != sigs.end()) {
+            BOOST_FOREACH(isc::dns::rdata::RdataPtr sig, found->second) {
+                rrset->addRRsig(sig);
+            }
+        }
+    }
+
+private:
+    std::map<isc::dns::RRType, std::vector<isc::dns::rdata::RdataPtr> > sigs;
+};
+}
+
+
 ZoneFinder::FindResult
-DatabaseClient::Finder::find(const isc::dns::Name&,
-                             const isc::dns::RRType&,
+DatabaseClient::Finder::find(const isc::dns::Name& name,
+                             const isc::dns::RRType& type,
                              isc::dns::RRsetList*,
-                             const FindOptions) const
+                             const FindOptions)
 {
-    // TODO Implement
-    return (FindResult(SUCCESS, isc::dns::ConstRRsetPtr()));
+    // This variable is used to determine the difference between
+    // NXDOMAIN and NXRRSET
+    bool records_found = false;
+    isc::dns::RRsetPtr result_rrset;
+    ZoneFinder::Result result_status = SUCCESS;
+    RRsigStore sig_store;
+    logger.debug(DBG_TRACE_DETAILED, DATASRC_DATABASE_FIND_RECORDS)
+        .arg(database_->getDBName()).arg(name).arg(type);
+
+    try {
+        database_->searchForRecords(zone_id_, name.toText());
+
+        std::string columns[DatabaseAccessor::COLUMN_COUNT];
+        while (database_->getNextRecord(columns,
+                                        DatabaseAccessor::COLUMN_COUNT)) {
+            if (!records_found) {
+                records_found = true;
+            }
+
+            try {
+                const isc::dns::RRType cur_type(columns[DatabaseAccessor::
+                                                        TYPE_COLUMN]);
+                const isc::dns::RRTTL cur_ttl(columns[DatabaseAccessor::
+                                                      TTL_COLUMN]);
+                // Ths sigtype column was an optimization for finding the
+                // relevant RRSIG RRs for a lookup. Currently this column is
+                // not used in this revised datasource implementation. We
+                // should either start using it again, or remove it from use
+                // completely (i.e. also remove it from the schema and the
+                // backend implementation).
+                // Note that because we don't use it now, we also won't notice
+                // it if the value is wrong (i.e. if the sigtype column
+                // contains an rrtype that is different from the actual value
+                // of the 'type covered' field in the RRSIG Rdata).
+                //cur_sigtype(columns[SIGTYPE_COLUMN]);
+
+                if (cur_type == type) {
+                    if (result_rrset &&
+                        result_rrset->getType() == isc::dns::RRType::CNAME()) {
+                        isc_throw(DataSourceError, "CNAME found but it is not "
+                                  "the only record for " + name.toText());
+                    }
+                    addOrCreate(result_rrset, name, getClass(), cur_type,
+                                cur_ttl, columns[DatabaseAccessor::
+                                                 RDATA_COLUMN],
+                                *database_);
+                } else if (cur_type == isc::dns::RRType::CNAME()) {
+                    // There should be no other data, so result_rrset should
+                    // be empty.
+                    if (result_rrset) {
+                        isc_throw(DataSourceError, "CNAME found but it is not "
+                                  "the only record for " + name.toText());
+                    }
+                    addOrCreate(result_rrset, name, getClass(), cur_type,
+                                cur_ttl, columns[DatabaseAccessor::
+                                                 RDATA_COLUMN],
+                                *database_);
+                    result_status = CNAME;
+                } else if (cur_type == isc::dns::RRType::RRSIG()) {
+                    // If we get signatures before we get the actual data, we
+                    // can't know which ones to keep and which to drop...
+                    // So we keep a separate store of any signature that may be
+                    // relevant and add them to the final RRset when we are
+                    // done.
+                    // A possible optimization here is to not store them for
+                    // types we are certain we don't need
+                    sig_store.addSig(isc::dns::rdata::createRdata(cur_type,
+                                    getClass(),
+                                    columns[DatabaseAccessor::
+                                            RDATA_COLUMN]));
+                }
+            } catch (const isc::dns::InvalidRRType& irt) {
+                isc_throw(DataSourceError, "Invalid RRType in database for " <<
+                        name << ": " << columns[DatabaseAccessor::
+                                                TYPE_COLUMN]);
+            } catch (const isc::dns::InvalidRRTTL& irttl) {
+                isc_throw(DataSourceError, "Invalid TTL in database for " <<
+                        name << ": " << columns[DatabaseAccessor::
+                                                TTL_COLUMN]);
+            } catch (const isc::dns::rdata::InvalidRdataText& ird) {
+                isc_throw(DataSourceError, "Invalid rdata in database for " <<
+                        name << ": " << columns[DatabaseAccessor::
+                                                RDATA_COLUMN]);
+            }
+        }
+    } catch (const DataSourceError& dse) {
+        logger.error(DATASRC_DATABASE_FIND_ERROR)
+            .arg(database_->getDBName()).arg(dse.what());
+        // call cleanup and rethrow
+        database_->resetSearch();
+        throw;
+    } catch (const isc::Exception& isce) {
+        logger.error(DATASRC_DATABASE_FIND_UNCAUGHT_ISC_ERROR)
+            .arg(database_->getDBName()).arg(isce.what());
+        // cleanup, change it to a DataSourceError and rethrow
+        database_->resetSearch();
+        isc_throw(DataSourceError, isce.what());
+    } catch (const std::exception& ex) {
+        logger.error(DATASRC_DATABASE_FIND_UNCAUGHT_ERROR)
+            .arg(database_->getDBName()).arg(ex.what());
+        database_->resetSearch();
+        throw;
+    }
+
+    if (!result_rrset) {
+        if (records_found) {
+            logger.debug(DBG_TRACE_DETAILED,
+                         DATASRC_DATABASE_FOUND_NXRRSET)
+                        .arg(database_->getDBName()).arg(name)
+                        .arg(getClass()).arg(type);
+            result_status = NXRRSET;
+        } else {
+            logger.debug(DBG_TRACE_DETAILED,
+                         DATASRC_DATABASE_FOUND_NXDOMAIN)
+                        .arg(database_->getDBName()).arg(name)
+                        .arg(getClass()).arg(type);
+            result_status = NXDOMAIN;
+        }
+    } else {
+        sig_store.appendSignatures(result_rrset);
+        logger.debug(DBG_TRACE_DETAILED,
+                     DATASRC_DATABASE_FOUND_RRSET)
+                    .arg(database_->getDBName()).arg(*result_rrset);
+    }
+    return (FindResult(result_status, result_rrset));
 }
 
 Name

+ 132 - 2
src/lib/datasrc/database.h

@@ -150,6 +150,96 @@ public:
         isc_throw(isc::NotImplemented,
                   "This database datasource can't be iterated");
     }
+
+    /**
+     * \brief Starts a new search for records of the given name in the given zone
+     *
+     * The data searched by this call can be retrieved with subsequent calls to
+     * getNextRecord().
+     *
+     * \exception DataSourceError if there is a problem connecting to the
+     *                            backend database
+     *
+     * \param zone_id The zone to search in, as returned by getZone()
+     * \param name The name of the records to find
+     */
+    virtual void searchForRecords(int zone_id, const std::string& name) = 0;
+
+    /**
+     * \brief Retrieves the next record from the search started with searchForRecords()
+     *
+     * Returns a boolean specifying whether or not there was more data to read.
+     * In the case of a database error, a DatasourceError is thrown.
+     *
+     * The columns passed is an array of std::strings consisting of
+     * DatabaseConnection::COLUMN_COUNT elements, the elements of which
+     * are defined in DatabaseConnection::RecordColumns, in their basic
+     * string representation.
+     *
+     * If you are implementing a derived database connection class, you
+     * should have this method check the column_count value, and fill the
+     * array with strings conforming to their description in RecordColumn.
+     *
+     * \exception DatasourceError if there was an error reading from the database
+     *
+     * \param columns The elements of this array will be filled with the data
+     *                for one record as defined by RecordColumns
+     *                If there was no data, the array is untouched.
+     * \return true if there was a next record, false if there was not
+     */
+    virtual bool getNextRecord(std::string columns[], size_t column_count) = 0;
+
+    /**
+     * \brief Resets the current search initiated with searchForRecords()
+     *
+     * This method will be called when the called of searchForRecords() and
+     * getNextRecord() finds bad data, and aborts the current search.
+     * It should clean up whatever handlers searchForRecords() created, and
+     * any other state modified or needed by getNextRecord()
+     *
+     * Of course, the implementation of getNextRecord may also use it when
+     * it is done with a search. If it does, the implementation of this
+     * method should make sure it can handle being called multiple times.
+     *
+     * The implementation for this method should make sure it never throws.
+     */
+    virtual void resetSearch() = 0;
+
+    /**
+     * Definitions of the fields as they are required to be filled in
+     * by getNextRecord()
+     *
+     * When implementing getNextRecord(), the columns array should
+     * be filled with the values as described in this enumeration,
+     * in this order, i.e. TYPE_COLUMN should be the first element
+     * (index 0) of the array, TTL_COLUMN should be the second element
+     * (index 1), etc.
+     */
+    enum RecordColumns {
+        TYPE_COLUMN = 0,    ///< The RRType of the record (A/NS/TXT etc.)
+        TTL_COLUMN = 1,     ///< The TTL of the record (a
+        SIGTYPE_COLUMN = 2, ///< For RRSIG records, this contains the RRTYPE
+                            ///< the RRSIG covers. In the current implementation,
+                            ///< this field is ignored.
+        RDATA_COLUMN = 3    ///< Full text representation of the record's RDATA
+    };
+
+    /// The number of fields the columns array passed to getNextRecord should have
+    static const size_t COLUMN_COUNT = 4;
+
+    /**
+     * \brief Returns a string identifying this dabase backend
+     *
+     * The returned string is mainly intended to be used for
+     * debugging/logging purposes.
+     *
+     * Any implementation is free to choose the exact string content,
+     * but it is advisable to make it a name that is distinguishable
+     * from the others.
+     *
+     * \return the name of the database
+     */
+    virtual const std::string& getDBName() const = 0;
 };
 
 /**
@@ -212,11 +302,51 @@ public:
         // ZoneFinder's pure virtual methods.
         virtual isc::dns::Name getOrigin() const;
         virtual isc::dns::RRClass getClass() const;
+
+        /**
+         * \brief Find an RRset in the datasource
+         *
+         * Searches the datasource for an RRset of the given name and
+         * type. If there is a CNAME at the given name, the CNAME rrset
+         * is returned.
+         * (this implementation is not complete, and currently only
+         * does full matches, CNAMES, and the signatures for matches and
+         * CNAMEs)
+         * \note target was used in the original design to handle ANY
+         *       queries. This is not implemented yet, and may use
+         *       target again for that, but it might also use something
+         *       different. It is left in for compatibility at the moment.
+         * \note options are ignored at this moment
+         *
+         * \note Maybe counter intuitively, this method is not a const member
+         * function.  This is intentional; some of the underlying implementations
+         * are expected to use a database backend, and would internally contain
+         * some abstraction of "database connection".  In the most strict sense
+         * any (even read only) operation might change the internal state of
+         * such a connection, and in that sense the operation cannot be considered
+         * "const".  In order to avoid giving a false sense of safety to the
+         * caller, we indicate a call to this method may have a surprising
+         * side effect.  That said, this view may be too strict and it may
+         * make sense to say the internal database connection doesn't affect
+         * external behavior in terms of the interface of this method.  As
+         * we gain more experiences with various kinds of backends we may
+         * revisit the constness.
+         *
+         * \exception DataSourceError when there is a problem reading
+         *                            the data from the dabase backend.
+         *                            This can be a connection, code, or
+         *                            data (parse) error.
+         *
+         * \param name The name to find
+         * \param type The RRType to find
+         * \param target Unused at this moment
+         * \param options Unused at this moment
+         */
         virtual FindResult find(const isc::dns::Name& name,
                                 const isc::dns::RRType& type,
                                 isc::dns::RRsetList* target = NULL,
-                                const FindOptions options = FIND_DEFAULT)
-            const;
+                                const FindOptions options = FIND_DEFAULT);
+
         /**
          * \brief The zone ID
          *

+ 40 - 0
src/lib/datasrc/datasrc_messages.mes

@@ -63,6 +63,46 @@ The maximum allowed number of items of the hotspot cache is set to the given
 number. If there are too many, some of them will be dropped. The size of 0
 means no limit.
 
+% DATASRC_DATABASE_FIND_ERROR error retrieving data from datasource %1: %2
+This was an internal error while reading data from a datasource. This can either
+mean the specific data source implementation is not behaving correctly, or the
+data it provides is invalid. The current search is aborted.
+The error message contains specific information about the error.
+
+% DATASRC_DATABASE_FIND_RECORDS looking in datasource %1 for record %2/%3
+Debug information. The database data source is looking up records with the given
+name and type in the database.
+
+% DATASRC_DATABASE_FIND_TTL_MISMATCH TTL values differ in %1 for elements of %2/%3/%4, setting to %5
+The datasource backend provided resource records for the given RRset with
+different TTL values. The TTL of the RRSET is set to the lowest value, which
+is printed in the log message.
+
+% DATASRC_DATABASE_FIND_UNCAUGHT_ERROR uncaught general error retrieving data from datasource %1: %2
+There was an uncaught general exception while reading data from a datasource.
+This most likely points to a logic error in the code, and can be considered a
+bug. The current search is aborted. Specific information about the exception is
+printed in this error message.
+
+% DATASRC_DATABASE_FIND_UNCAUGHT_ISC_ERROR uncaught error retrieving data from datasource %1: %2
+There was an uncaught ISC exception while reading data from a datasource. This
+most likely points to a logic error in the code, and can be considered a bug.
+The current search is aborted. Specific information about the exception is
+printed in this error message.
+
+% DATASRC_DATABASE_FOUND_NXDOMAIN search in datasource %1 resulted in NXDOMAIN for %2/%3/%4
+The data returned by the database backend did not contain any data for the given
+domain name, class and type.
+
+% DATASRC_DATABASE_FOUND_NXRRSET search in datasource %1 resulted in NXRRSET for %2/%3/%4
+The data returned by the database backend contained data for the given domain
+name and class, but not for the given type.
+
+% DATASRC_DATABASE_FOUND_RRSET search in datasource %1 resulted in RRset %2
+The data returned by the database backend contained data for the given domain
+name, and it either matches the type or has a relevant type. The RRset that is
+returned is printed.
+
 % DATASRC_DO_QUERY handling query for '%1/%2'
 A debug message indicating that a query for the given name and RR type is being
 processed.

+ 1 - 1
src/lib/datasrc/memory_datasrc.cc

@@ -622,7 +622,7 @@ InMemoryZoneFinder::getClass() const {
 
 ZoneFinder::FindResult
 InMemoryZoneFinder::find(const Name& name, const RRType& type,
-                 RRsetList* target, const FindOptions options) const
+                 RRsetList* target, const FindOptions options)
 {
     return (impl_->find(name, type, target, options));
 }

+ 1 - 1
src/lib/datasrc/memory_datasrc.h

@@ -73,7 +73,7 @@ public:
     virtual FindResult find(const isc::dns::Name& name,
                             const isc::dns::RRType& type,
                             isc::dns::RRsetList* target = NULL,
-                            const FindOptions options = FIND_DEFAULT) const;
+                            const FindOptions options = FIND_DEFAULT);
 
     /// \brief Inserts an rrset into the zone.
     ///

+ 105 - 15
src/lib/datasrc/sqlite3_accessor.cc

@@ -17,6 +17,7 @@
 #include <datasrc/sqlite3_accessor.h>
 #include <datasrc/logger.h>
 #include <datasrc/data_source.h>
+#include <util/filename.h>
 
 #include <boost/lexical_cast.hpp>
 
@@ -26,19 +27,20 @@ namespace datasrc {
 struct SQLite3Parameters {
     SQLite3Parameters() :
         db_(NULL), version_(-1),
-        q_zone_(NULL) /*, q_record_(NULL), q_addrs_(NULL), q_referral_(NULL),
-        q_any_(NULL), q_count_(NULL), q_previous_(NULL), q_nsec3_(NULL),
+        q_zone_(NULL), q_any_(NULL)
+        /*q_record_(NULL), q_addrs_(NULL), q_referral_(NULL),
+        q_count_(NULL), q_previous_(NULL), q_nsec3_(NULL),
         q_prevnsec3_(NULL) */
     {}
     sqlite3* db_;
     int version_;
     sqlite3_stmt* q_zone_;
+    sqlite3_stmt* q_any_;
     /*
     TODO: Yet unneeded statements
     sqlite3_stmt* q_record_;
     sqlite3_stmt* q_addrs_;
     sqlite3_stmt* q_referral_;
-    sqlite3_stmt* q_any_;
     sqlite3_stmt* q_count_;
     sqlite3_stmt* q_previous_;
     sqlite3_stmt* q_nsec3_;
@@ -49,7 +51,9 @@ struct SQLite3Parameters {
 SQLite3Database::SQLite3Database(const std::string& filename,
                                      const isc::dns::RRClass& rrclass) :
     dbparameters_(new SQLite3Parameters),
-    class_(rrclass.toText())
+    class_(rrclass.toText()),
+    database_name_("sqlite3_" +
+                   isc::util::Filename(filename).nameAndExtension())
 {
     LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_NEWCONN);
 
@@ -71,6 +75,9 @@ public:
         if (params_.q_zone_ != NULL) {
             sqlite3_finalize(params_.q_zone_);
         }
+        if (params_.q_any_ != NULL) {
+            sqlite3_finalize(params_.q_any_);
+        }
         /*
         if (params_.q_record_ != NULL) {
             sqlite3_finalize(params_.q_record_);
@@ -81,9 +88,6 @@ public:
         if (params_.q_referral_ != NULL) {
             sqlite3_finalize(params_.q_referral_);
         }
-        if (params_.q_any_ != NULL) {
-            sqlite3_finalize(params_.q_any_);
-        }
         if (params_.q_count_ != NULL) {
             sqlite3_finalize(params_.q_count_);
         }
@@ -134,6 +138,9 @@ const char* const SCHEMA_LIST[] = {
 
 const char* const q_zone_str = "SELECT id FROM zones WHERE name=?1 AND rdclass = ?2";
 
+const char* const q_any_str = "SELECT rdtype, ttl, sigtype, rdata "
+    "FROM records WHERE zone_id=?1 AND name=?2";
+
 const char* const q_iterate_str = "SELECT name, rdtype, ttl, rdata FROM records "
                                   "WHERE zone_id = ?1 "
                                   "ORDER BY name, rdtype";
@@ -154,9 +161,6 @@ const char* const q_referral_str = "SELECT rdtype, ttl, sigtype, rdata FROM "
     "(rdtype='NS' OR sigtype='NS' OR rdtype='DS' OR sigtype='DS' OR "
     "rdtype='DNAME' OR sigtype='DNAME')";
 
-const char* const q_any_str = "SELECT rdtype, ttl, sigtype, rdata "
-    "FROM records WHERE zone_id=?1 AND name=?2";
-
 const char* const q_count_str = "SELECT COUNT(*) FROM records "
     "WHERE zone_id=?1 AND rname LIKE (?2 || '%');";
 
@@ -206,11 +210,11 @@ checkAndSetupSchema(Initializer* initializer) {
     }
 
     initializer->params_.q_zone_ = prepare(db, q_zone_str);
+    initializer->params_.q_any_ = prepare(db, q_any_str);
     /* TODO: Yet unneeded statements
     initializer->params_.q_record_ = prepare(db, q_record_str);
     initializer->params_.q_addrs_ = prepare(db, q_addrs_str);
     initializer->params_.q_referral_ = prepare(db, q_referral_str);
-    initializer->params_.q_any_ = prepare(db, q_any_str);
     initializer->params_.q_count_ = prepare(db, q_count_str);
     initializer->params_.q_previous_ = prepare(db, q_previous_str);
     initializer->params_.q_nsec3_ = prepare(db, q_nsec3_str);
@@ -258,6 +262,9 @@ SQLite3Database::close(void) {
     sqlite3_finalize(dbparameters_->q_zone_);
     dbparameters_->q_zone_ = NULL;
 
+    sqlite3_finalize(dbparameters_->q_any_);
+    dbparameters_->q_any_ = NULL;
+
     /* TODO: Once they are needed or not, uncomment or drop
     sqlite3_finalize(dbparameters->q_record_);
     dbparameters->q_record_ = NULL;
@@ -268,9 +275,6 @@ SQLite3Database::close(void) {
     sqlite3_finalize(dbparameters->q_referral_);
     dbparameters->q_referral_ = NULL;
 
-    sqlite3_finalize(dbparameters->q_any_);
-    dbparameters->q_any_ = NULL;
-
     sqlite3_finalize(dbparameters->q_count_);
     dbparameters->q_count_ = NULL;
 
@@ -296,7 +300,7 @@ SQLite3Database::getZone(const isc::dns::Name& name) const {
     // and prepare it (bind the parameters to it)
     sqlite3_reset(dbparameters_->q_zone_);
     rc = sqlite3_bind_text(dbparameters_->q_zone_, 1, name.toText().c_str(),
-                           -1, SQLITE_STATIC);
+                           -1, SQLITE_TRANSIENT);
     if (rc != SQLITE_OK) {
         isc_throw(SQLite3Error, "Could not bind " << name <<
                   " to SQL statement (zone)");
@@ -375,5 +379,91 @@ SQLite3Database::getIteratorContext(const isc::dns::Name&, int id) const {
     return (IteratorContextPtr(new Context(shared_from_this(), id)));
 }
 
+void
+SQLite3Database::searchForRecords(int zone_id, const std::string& name) {
+    resetSearch();
+    if (sqlite3_bind_int(dbparameters_->q_any_, 1, zone_id) != SQLITE_OK) {
+        isc_throw(DataSourceError,
+                  "Error in sqlite3_bind_int() for zone_id " <<
+                  zone_id << ": " << sqlite3_errmsg(dbparameters_->db_));
+    }
+    // use transient since name is a ref and may disappear
+    if (sqlite3_bind_text(dbparameters_->q_any_, 2, name.c_str(), -1,
+                               SQLITE_TRANSIENT) != SQLITE_OK) {
+        isc_throw(DataSourceError,
+                  "Error in sqlite3_bind_text() for name " <<
+                  name << ": " << sqlite3_errmsg(dbparameters_->db_));
+    }
+}
+
+namespace {
+// This helper function converts from the unsigned char* type (used by
+// sqlite3) to char* (wanted by std::string). Technically these types
+// might not be directly convertable
+// In case sqlite3_column_text() returns NULL, we just make it an
+// empty string.
+// The sqlite3parameters value is only used to check the error code if
+// ucp == NULL
+const char*
+convertToPlainChar(const unsigned char* ucp,
+                   SQLite3Parameters* dbparameters) {
+    if (ucp == NULL) {
+        // The field can really be NULL, in which case we return an
+        // empty string, or sqlite may have run out of memory, in
+        // which case we raise an error
+        if (dbparameters != NULL &&
+            sqlite3_errcode(dbparameters->db_) == SQLITE_NOMEM) {
+            isc_throw(DataSourceError,
+                      "Sqlite3 backend encountered a memory allocation "
+                      "error in sqlite3_column_text()");
+        } else {
+            return ("");
+        }
+    }
+    const void* p = ucp;
+    return (static_cast<const char*>(p));
+}
+}
+
+bool
+SQLite3Database::getNextRecord(std::string columns[], size_t column_count) {
+    if (column_count != COLUMN_COUNT) {
+            isc_throw(DataSourceError,
+                    "Datasource backend caller did not pass a column array "
+                    "of size " << COLUMN_COUNT << " to getNextRecord()");
+    }
+
+    sqlite3_stmt* current_stmt = dbparameters_->q_any_;
+    const int rc = sqlite3_step(current_stmt);
+
+    if (rc == SQLITE_ROW) {
+        for (int column = 0; column < column_count; ++column) {
+            try {
+                columns[column] = convertToPlainChar(sqlite3_column_text(
+                                                     current_stmt, column),
+                                                     dbparameters_);
+            } catch (const std::bad_alloc&) {
+                isc_throw(DataSourceError,
+                        "bad_alloc in Sqlite3Connection::getNextRecord");
+            }
+        }
+        return (true);
+    } else if (rc == SQLITE_DONE) {
+        // reached the end of matching rows
+        resetSearch();
+        return (false);
+    }
+    isc_throw(DataSourceError, "Unexpected failure in sqlite3_step: " <<
+                               sqlite3_errmsg(dbparameters_->db_));
+    // Compilers might not realize isc_throw always throws
+    return (false);
+}
+
+void
+SQLite3Database::resetSearch() {
+    sqlite3_reset(dbparameters_->q_any_);
+    sqlite3_clear_bindings(dbparameters_->q_any_);
+}
+
 }
 }

+ 54 - 0
src/lib/datasrc/sqlite3_accessor.h

@@ -94,6 +94,59 @@ public:
     /// \brief Implementation of DatabaseAbstraction::getIteratorContext
     virtual IteratorContextPtr getIteratorContext(const isc::dns::Name&,
                                                   int id) const;
+    /**
+     * \brief Start a new search for the given name in the given zone.
+     *
+     * This implements the searchForRecords from DatabaseConnection.
+     * This particular implementation does not raise DataSourceError.
+     *
+     * \exception DataSourceError when sqlite3_bind_int() or
+     *                            sqlite3_bind_text() fails
+     *
+     * \param zone_id The zone to seach in, as returned by getZone()
+     * \param name The name to find records for
+     */
+    virtual void searchForRecords(int zone_id, const std::string& name);
+
+    /**
+     * \brief Retrieve the next record from the search started with
+     *        searchForRecords
+     *
+     * This implements the getNextRecord from DatabaseConnection.
+     * See the documentation there for more information.
+     *
+     * If this method raises an exception, the contents of columns are undefined.
+     *
+     * \exception DataSourceError if there is an error returned by sqlite_step()
+     *                            When this exception is raised, the current
+     *                            search as initialized by searchForRecords() is
+     *                            NOT reset, and the caller is expected to take
+     *                            care of that.
+     * \param columns This vector will be cleared, and the fields of the record will
+     *                be appended here as strings (in the order rdtype, ttl, sigtype,
+     *                and rdata). If there was no data (i.e. if this call returns
+     *                false), the vector is untouched.
+     * \return true if there was a next record, false if there was not
+     */
+    virtual bool getNextRecord(std::string columns[], size_t column_count);
+
+    /**
+     * \brief Resets any state created by searchForRecords
+     *
+     * This implements the resetSearch from DatabaseConnection.
+     * See the documentation there for more information.
+     *
+     * This function never throws.
+     */
+    virtual void resetSearch();
+
+    /// The SQLite3 implementation of this method returns a string starting
+    /// with a fixed prefix of "sqlite3_" followed by the DB file name
+    /// removing any path name.  For example, for the DB file
+    /// /somewhere/in/the/system/bind10.sqlite3, this method will return
+    /// "sqlite3_bind10.sqlite3".
+    virtual const std::string& getDBName() const { return (database_name_); }
+
 private:
     /// \brief Private database data
     SQLite3Parameters* dbparameters_;
@@ -106,6 +159,7 @@ private:
     /// \brief SQLite3 implementation of IteratorContext
     class Context;
     friend class Context;
+    const std::string database_name_;
 };
 
 }

+ 595 - 1
src/lib/datasrc/tests/database_unittest.cc

@@ -16,12 +16,18 @@
 
 #include <dns/name.h>
 #include <dns/rrttl.h>
+#include <dns/rrset.h>
 #include <exceptions/exceptions.h>
 
 #include <datasrc/database.h>
+#include <datasrc/zone.h>
 #include <datasrc/data_source.h>
 #include <datasrc/iterator.h>
 
+#include <testutils/dnsmessage_test.h>
+
+#include <map>
+
 using namespace isc::datasrc;
 using namespace std;
 using namespace boost;
@@ -35,6 +41,9 @@ namespace {
  */
 class NopAccessor : public DatabaseAccessor {
 public:
+    NopAccessor() : database_name_("mock_database")
+    { }
+
     virtual std::pair<bool, int> getZone(const Name& name) const {
         if (name == Name("example.org")) {
             return (std::pair<bool, int>(true, 42));
@@ -48,6 +57,20 @@ public:
             return (std::pair<bool, int>(false, 0));
         }
     }
+
+    virtual const std::string& getDBName() const {
+        return (database_name_);
+    }
+
+    // These are just to compile, they won't be called
+    virtual void searchForRecords(int, const std::string&) { }
+    virtual bool getNextRecord(string*, size_t) {
+        return (false);
+    }
+    virtual void resetSearch() { }
+private:
+    const std::string database_name_;
+
 };
 
 /*
@@ -58,6 +81,11 @@ public:
  * implementation of the optional functionality.
  */
 class MockAccessor : public NopAccessor {
+public:
+    MockAccessor() : search_running_(false)
+    {
+        fillData();
+    }
 private:
     class MockIteratorContext : public IteratorContext {
     private:
@@ -156,6 +184,211 @@ public:
             isc_throw(isc::Unexpected, "Unknown zone ID");
         }
     }
+
+    virtual void searchForRecords(int zone_id, const std::string& name) {
+        search_running_ = true;
+
+        // 'hardcoded' name to trigger exceptions (for testing
+        // the error handling of find() (the other on is below in
+        // if the name is "exceptiononsearch" it'll raise an exception here
+        if (name == "dsexception.in.search.") {
+            isc_throw(DataSourceError, "datasource exception on search");
+        } else if (name == "iscexception.in.search.") {
+            isc_throw(isc::Exception, "isc exception on search");
+        } else if (name == "basicexception.in.search.") {
+            throw std::exception();
+        }
+        searched_name_ = name;
+
+        // we're not aiming for efficiency in this test, simply
+        // copy the relevant vector from records
+        cur_record = 0;
+        if (zone_id == 42) {
+            if (records.count(name) > 0) {
+                cur_name = records.find(name)->second;
+            } else {
+                cur_name.clear();
+            }
+        } else {
+            cur_name.clear();
+        }
+    };
+
+    virtual bool getNextRecord(std::string columns[], size_t column_count) {
+        if (searched_name_ == "dsexception.in.getnext.") {
+            isc_throw(DataSourceError, "datasource exception on getnextrecord");
+        } else if (searched_name_ == "iscexception.in.getnext.") {
+            isc_throw(isc::Exception, "isc exception on getnextrecord");
+        } else if (searched_name_ == "basicexception.in.getnext.") {
+            throw std::exception();
+        }
+
+        if (column_count != DatabaseAccessor::COLUMN_COUNT) {
+            isc_throw(DataSourceError, "Wrong column count in getNextRecord");
+        }
+        if (cur_record < cur_name.size()) {
+            for (size_t i = 0; i < column_count; ++i) {
+                columns[i] = cur_name[cur_record][i];
+            }
+            cur_record++;
+            return (true);
+        } else {
+            resetSearch();
+            return (false);
+        }
+    };
+
+    virtual void resetSearch() {
+        search_running_ = false;
+    };
+
+    bool searchRunning() const {
+        return (search_running_);
+    }
+private:
+    std::map<std::string, std::vector< std::vector<std::string> > > records;
+    // used as internal index for getNextRecord()
+    size_t cur_record;
+    // used as temporary storage after searchForRecord() and during
+    // getNextRecord() calls, as well as during the building of the
+    // fake data
+    std::vector< std::vector<std::string> > cur_name;
+
+    // This boolean is used to make sure find() calls resetSearch
+    // when it encounters an error
+    bool search_running_;
+
+    // We store the name passed to searchForRecords, so we can
+    // hardcode some exceptions into getNextRecord
+    std::string searched_name_;
+
+    // Adds one record to the current name in the database
+    // The actual data will not be added to 'records' until
+    // addCurName() is called
+    void addRecord(const std::string& name,
+                   const std::string& type,
+                   const std::string& sigtype,
+                   const std::string& rdata) {
+        std::vector<std::string> columns;
+        columns.push_back(name);
+        columns.push_back(type);
+        columns.push_back(sigtype);
+        columns.push_back(rdata);
+        cur_name.push_back(columns);
+    }
+
+    // Adds all records we just built with calls to addRecords
+    // to the actual fake database. This will clear cur_name,
+    // so we can immediately start adding new records.
+    void addCurName(const std::string& name) {
+        ASSERT_EQ(0, records.count(name));
+        records[name] = cur_name;
+        cur_name.clear();
+    }
+
+    // Fills the database with zone data.
+    // This method constructs a number of resource records (with addRecord),
+    // which will all be added for one domain name to the fake database
+    // (with addCurName). So for instance the first set of calls create
+    // data for the name 'www.example.org', which will consist of one A RRset
+    // of one record, and one AAAA RRset of two records.
+    // The order in which they are added is the order in which getNextRecord()
+    // will return them (so we can test whether find() etc. support data that
+    // might not come in 'normal' order)
+    // It shall immediately fail if you try to add the same name twice.
+    void fillData() {
+        // some plain data
+        addRecord("A", "3600", "", "192.0.2.1");
+        addRecord("AAAA", "3600", "", "2001:db8::1");
+        addRecord("AAAA", "3600", "", "2001:db8::2");
+        addCurName("www.example.org.");
+
+        addRecord("A", "3600", "", "192.0.2.1");
+        addRecord("AAAA", "3600", "", "2001:db8::1");
+        addRecord("A", "3600", "", "192.0.2.2");
+        addCurName("www2.example.org.");
+
+        addRecord("CNAME", "3600", "", "www.example.org.");
+        addCurName("cname.example.org.");
+
+        // some DNSSEC-'signed' data
+        addRecord("A", "3600", "", "192.0.2.1");
+        addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+        addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE");
+        addRecord("AAAA", "3600", "", "2001:db8::1");
+        addRecord("AAAA", "3600", "", "2001:db8::2");
+        addRecord("RRSIG", "3600", "", "AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+        addCurName("signed1.example.org.");
+        addRecord("CNAME", "3600", "", "www.example.org.");
+        addRecord("RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+        addCurName("signedcname1.example.org.");
+        // special case might fail; sig is for cname, which isn't there (should be ignored)
+        // (ignoring of 'normal' other type is done above by www.)
+        addRecord("A", "3600", "", "192.0.2.1");
+        addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+        addRecord("RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+        addCurName("acnamesig1.example.org.");
+
+        // let's pretend we have a database that is not careful
+        // about the order in which it returns data
+        addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+        addRecord("AAAA", "3600", "", "2001:db8::2");
+        addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE");
+        addRecord("A", "3600", "", "192.0.2.1");
+        addRecord("RRSIG", "3600", "", "AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+        addRecord("AAAA", "3600", "", "2001:db8::1");
+        addCurName("signed2.example.org.");
+        addRecord("RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+        addRecord("CNAME", "3600", "", "www.example.org.");
+        addCurName("signedcname2.example.org.");
+
+        addRecord("RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+        addRecord("A", "3600", "", "192.0.2.1");
+        addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+        addCurName("acnamesig2.example.org.");
+
+        addRecord("RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+        addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+        addRecord("A", "3600", "", "192.0.2.1");
+        addCurName("acnamesig3.example.org.");
+
+        addRecord("A", "3600", "", "192.0.2.1");
+        addRecord("A", "360", "", "192.0.2.2");
+        addCurName("ttldiff1.example.org.");
+        addRecord("A", "360", "", "192.0.2.1");
+        addRecord("A", "3600", "", "192.0.2.2");
+        addCurName("ttldiff2.example.org.");
+
+        // also add some intentionally bad data
+        addRecord("A", "3600", "", "192.0.2.1");
+        addRecord("CNAME", "3600", "", "www.example.org.");
+        addCurName("badcname1.example.org.");
+
+        addRecord("CNAME", "3600", "", "www.example.org.");
+        addRecord("A", "3600", "", "192.0.2.1");
+        addCurName("badcname2.example.org.");
+
+        addRecord("CNAME", "3600", "", "www.example.org.");
+        addRecord("CNAME", "3600", "", "www.example2.org.");
+        addCurName("badcname3.example.org.");
+
+        addRecord("A", "3600", "", "bad");
+        addCurName("badrdata.example.org.");
+
+        addRecord("BAD_TYPE", "3600", "", "192.0.2.1");
+        addCurName("badtype.example.org.");
+
+        addRecord("A", "badttl", "", "192.0.2.1");
+        addCurName("badttl.example.org.");
+
+        addRecord("A", "badttl", "", "192.0.2.1");
+        addRecord("RRSIG", "3600", "", "A 5 3 3600 somebaddata 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+        addCurName("badsig.example.org.");
+
+        addRecord("A", "3600", "", "192.0.2.1");
+        addRecord("RRSIG", "3600", "TXT", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+        addCurName("badsigtype.example.org.");
+    }
 };
 
 // This tests the default getIteratorContext behaviour, throwing NotImplemented
@@ -182,6 +415,8 @@ public:
     // Will be deleted by client_, just keep the current value for comparison.
     MockAccessor* current_database_;
     shared_ptr<DatabaseClient> client_;
+    const std::string database_name_;
+
     /**
      * Check the zone finder is a valid one and references the zone ID and
      * database available here.
@@ -216,7 +451,9 @@ TEST_F(DatabaseClientTest, superZone) {
 }
 
 TEST_F(DatabaseClientTest, noAccessorException) {
-    EXPECT_THROW(DatabaseClient(shared_ptr<DatabaseAccessor>()),
+    // We need a dummy variable here; some compiler would regard it a mere
+    // declaration instead of an instantiation and make the test fail.
+    EXPECT_THROW(DatabaseClient dummy((shared_ptr<DatabaseAccessor>())),
                  isc::InvalidParameter);
 }
 
@@ -307,4 +544,361 @@ TEST_F(DatabaseClientTest, badIterator) {
     EXPECT_THROW(it->getNextRRset(), DataSourceError);
 }
 
+// checks if the given rrset matches the
+// given name, class, type and rdatas
+void
+checkRRset(isc::dns::ConstRRsetPtr rrset,
+           const isc::dns::Name& name,
+           const isc::dns::RRClass& rrclass,
+           const isc::dns::RRType& rrtype,
+           const isc::dns::RRTTL& rrttl,
+           const std::vector<std::string>& rdatas) {
+    isc::dns::RRsetPtr expected_rrset(
+        new isc::dns::RRset(name, rrclass, rrtype, rrttl));
+    for (unsigned int i = 0; i < rdatas.size(); ++i) {
+        expected_rrset->addRdata(
+            isc::dns::rdata::createRdata(rrtype, rrclass,
+                                         rdatas[i]));
+    }
+    isc::testutils::rrsetCheck(expected_rrset, rrset);
+}
+
+void
+doFindTest(shared_ptr<DatabaseClient::Finder> finder,
+           const isc::dns::Name& name,
+           const isc::dns::RRType& type,
+           const isc::dns::RRType& expected_type,
+           const isc::dns::RRTTL expected_ttl,
+           ZoneFinder::Result expected_result,
+           const std::vector<std::string>& expected_rdatas,
+           const std::vector<std::string>& expected_sig_rdatas)
+{
+    ZoneFinder::FindResult result =
+        finder->find(name, type, NULL, ZoneFinder::FIND_DEFAULT);
+    ASSERT_EQ(expected_result, result.code) << name << " " << type;
+    if (expected_rdatas.size() > 0) {
+        checkRRset(result.rrset, name, finder->getClass(),
+                   expected_type, expected_ttl, expected_rdatas);
+
+        if (expected_sig_rdatas.size() > 0) {
+            checkRRset(result.rrset->getRRsig(), name,
+                       finder->getClass(), isc::dns::RRType::RRSIG(),
+                       expected_ttl, expected_sig_rdatas);
+        } else {
+            EXPECT_EQ(isc::dns::RRsetPtr(), result.rrset->getRRsig());
+        }
+    } else {
+        EXPECT_EQ(isc::dns::RRsetPtr(), result.rrset);
+    }
+}
+
+TEST_F(DatabaseClientTest, find) {
+    DataSourceClient::FindResult zone(client_->findZone(Name("example.org")));
+    ASSERT_EQ(result::SUCCESS, zone.code);
+    shared_ptr<DatabaseClient::Finder> finder(
+        dynamic_pointer_cast<DatabaseClient::Finder>(zone.zone_finder));
+    EXPECT_EQ(42, finder->zone_id());
+    EXPECT_FALSE(current_database_->searchRunning());
+    std::vector<std::string> expected_rdatas;
+    std::vector<std::string> expected_sig_rdatas;
+
+    expected_rdatas.clear();
+    expected_sig_rdatas.clear();
+    expected_rdatas.push_back("192.0.2.1");
+    doFindTest(finder, isc::dns::Name("www.example.org."),
+               isc::dns::RRType::A(), isc::dns::RRType::A(),
+               isc::dns::RRTTL(3600),
+               ZoneFinder::SUCCESS,
+               expected_rdatas, expected_sig_rdatas);
+    EXPECT_FALSE(current_database_->searchRunning());
+
+    expected_rdatas.clear();
+    expected_sig_rdatas.clear();
+    expected_rdatas.push_back("192.0.2.1");
+    expected_rdatas.push_back("192.0.2.2");
+    doFindTest(finder, isc::dns::Name("www2.example.org."),
+               isc::dns::RRType::A(), isc::dns::RRType::A(),
+               isc::dns::RRTTL(3600),
+               ZoneFinder::SUCCESS,
+               expected_rdatas, expected_sig_rdatas);
+    EXPECT_FALSE(current_database_->searchRunning());
+
+    expected_rdatas.clear();
+    expected_sig_rdatas.clear();
+    expected_rdatas.push_back("2001:db8::1");
+    expected_rdatas.push_back("2001:db8::2");
+    doFindTest(finder, isc::dns::Name("www.example.org."),
+               isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
+               isc::dns::RRTTL(3600),
+               ZoneFinder::SUCCESS,
+               expected_rdatas, expected_sig_rdatas);
+    EXPECT_FALSE(current_database_->searchRunning());
+
+    expected_rdatas.clear();
+    expected_sig_rdatas.clear();
+    doFindTest(finder, isc::dns::Name("www.example.org."),
+               isc::dns::RRType::TXT(), isc::dns::RRType::TXT(),
+               isc::dns::RRTTL(3600),
+               ZoneFinder::NXRRSET,
+               expected_rdatas, expected_sig_rdatas);
+    EXPECT_FALSE(current_database_->searchRunning());
+
+    expected_rdatas.clear();
+    expected_sig_rdatas.clear();
+    expected_rdatas.push_back("www.example.org.");
+    doFindTest(finder, isc::dns::Name("cname.example.org."),
+               isc::dns::RRType::A(), isc::dns::RRType::CNAME(),
+               isc::dns::RRTTL(3600),
+               ZoneFinder::CNAME,
+               expected_rdatas, expected_sig_rdatas);
+    EXPECT_FALSE(current_database_->searchRunning());
+
+    expected_rdatas.clear();
+    expected_sig_rdatas.clear();
+    expected_rdatas.push_back("www.example.org.");
+    doFindTest(finder, isc::dns::Name("cname.example.org."),
+               isc::dns::RRType::CNAME(), isc::dns::RRType::CNAME(),
+               isc::dns::RRTTL(3600),
+               ZoneFinder::SUCCESS,
+               expected_rdatas, expected_sig_rdatas);
+    EXPECT_FALSE(current_database_->searchRunning());
+
+    expected_rdatas.clear();
+    expected_sig_rdatas.clear();
+    doFindTest(finder, isc::dns::Name("doesnotexist.example.org."),
+               isc::dns::RRType::A(), isc::dns::RRType::A(),
+               isc::dns::RRTTL(3600),
+               ZoneFinder::NXDOMAIN,
+               expected_rdatas, expected_sig_rdatas);
+    EXPECT_FALSE(current_database_->searchRunning());
+
+    expected_rdatas.clear();
+    expected_sig_rdatas.clear();
+    expected_rdatas.push_back("192.0.2.1");
+    expected_sig_rdatas.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+    expected_sig_rdatas.push_back("A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE");
+    doFindTest(finder, isc::dns::Name("signed1.example.org."),
+               isc::dns::RRType::A(), isc::dns::RRType::A(),
+               isc::dns::RRTTL(3600),
+               ZoneFinder::SUCCESS,
+               expected_rdatas, expected_sig_rdatas);
+    EXPECT_FALSE(current_database_->searchRunning());
+
+    expected_rdatas.clear();
+    expected_sig_rdatas.clear();
+    expected_rdatas.push_back("2001:db8::1");
+    expected_rdatas.push_back("2001:db8::2");
+    expected_sig_rdatas.push_back("AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+    doFindTest(finder, isc::dns::Name("signed1.example.org."),
+               isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
+               isc::dns::RRTTL(3600),
+               ZoneFinder::SUCCESS,
+               expected_rdatas, expected_sig_rdatas);
+    EXPECT_FALSE(current_database_->searchRunning());
+
+    expected_rdatas.clear();
+    expected_sig_rdatas.clear();
+    doFindTest(finder, isc::dns::Name("signed1.example.org."),
+               isc::dns::RRType::TXT(), isc::dns::RRType::TXT(),
+               isc::dns::RRTTL(3600),
+               ZoneFinder::NXRRSET,
+               expected_rdatas, expected_sig_rdatas);
+    EXPECT_FALSE(current_database_->searchRunning());
+
+    expected_rdatas.clear();
+    expected_sig_rdatas.clear();
+    expected_rdatas.push_back("www.example.org.");
+    expected_sig_rdatas.push_back("CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+    doFindTest(finder, isc::dns::Name("signedcname1.example.org."),
+               isc::dns::RRType::A(), isc::dns::RRType::CNAME(),
+               isc::dns::RRTTL(3600),
+               ZoneFinder::CNAME,
+               expected_rdatas, expected_sig_rdatas);
+    EXPECT_FALSE(current_database_->searchRunning());
+
+    expected_rdatas.clear();
+    expected_sig_rdatas.clear();
+    expected_rdatas.push_back("192.0.2.1");
+    expected_sig_rdatas.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+    expected_sig_rdatas.push_back("A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE");
+    doFindTest(finder, isc::dns::Name("signed2.example.org."),
+               isc::dns::RRType::A(), isc::dns::RRType::A(),
+               isc::dns::RRTTL(3600),
+               ZoneFinder::SUCCESS,
+               expected_rdatas, expected_sig_rdatas);
+    EXPECT_FALSE(current_database_->searchRunning());
+
+    expected_rdatas.clear();
+    expected_sig_rdatas.clear();
+    expected_rdatas.push_back("2001:db8::2");
+    expected_rdatas.push_back("2001:db8::1");
+    expected_sig_rdatas.push_back("AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+    doFindTest(finder, isc::dns::Name("signed2.example.org."),
+               isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
+               isc::dns::RRTTL(3600),
+               ZoneFinder::SUCCESS,
+               expected_rdatas, expected_sig_rdatas);
+    EXPECT_FALSE(current_database_->searchRunning());
+
+    expected_rdatas.clear();
+    expected_sig_rdatas.clear();
+    doFindTest(finder, isc::dns::Name("signed2.example.org."),
+               isc::dns::RRType::TXT(), isc::dns::RRType::TXT(),
+               isc::dns::RRTTL(3600),
+               ZoneFinder::NXRRSET,
+               expected_rdatas, expected_sig_rdatas);
+    EXPECT_FALSE(current_database_->searchRunning());
+
+    expected_rdatas.clear();
+    expected_sig_rdatas.clear();
+    expected_rdatas.push_back("www.example.org.");
+    expected_sig_rdatas.push_back("CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+    doFindTest(finder, isc::dns::Name("signedcname2.example.org."),
+               isc::dns::RRType::A(), isc::dns::RRType::CNAME(),
+               isc::dns::RRTTL(3600),
+               ZoneFinder::CNAME,
+               expected_rdatas, expected_sig_rdatas);
+    EXPECT_FALSE(current_database_->searchRunning());
+
+
+    expected_rdatas.clear();
+    expected_sig_rdatas.clear();
+    expected_rdatas.push_back("192.0.2.1");
+    expected_sig_rdatas.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+    doFindTest(finder, isc::dns::Name("acnamesig1.example.org."),
+               isc::dns::RRType::A(), isc::dns::RRType::A(),
+               isc::dns::RRTTL(3600),
+               ZoneFinder::SUCCESS,
+               expected_rdatas, expected_sig_rdatas);
+    EXPECT_FALSE(current_database_->searchRunning());
+
+    expected_rdatas.clear();
+    expected_sig_rdatas.clear();
+    expected_rdatas.push_back("192.0.2.1");
+    expected_sig_rdatas.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+    doFindTest(finder, isc::dns::Name("acnamesig2.example.org."),
+               isc::dns::RRType::A(), isc::dns::RRType::A(),
+               isc::dns::RRTTL(3600),
+               ZoneFinder::SUCCESS,
+               expected_rdatas, expected_sig_rdatas);
+    EXPECT_FALSE(current_database_->searchRunning());
+
+    expected_rdatas.clear();
+    expected_sig_rdatas.clear();
+    expected_rdatas.push_back("192.0.2.1");
+    expected_sig_rdatas.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+    doFindTest(finder, isc::dns::Name("acnamesig3.example.org."),
+               isc::dns::RRType::A(), isc::dns::RRType::A(),
+               isc::dns::RRTTL(3600),
+               ZoneFinder::SUCCESS,
+               expected_rdatas, expected_sig_rdatas);
+    EXPECT_FALSE(current_database_->searchRunning());
+
+    expected_rdatas.clear();
+    expected_sig_rdatas.clear();
+    expected_rdatas.push_back("192.0.2.1");
+    expected_rdatas.push_back("192.0.2.2");
+    doFindTest(finder, isc::dns::Name("ttldiff1.example.org."),
+               isc::dns::RRType::A(), isc::dns::RRType::A(),
+               isc::dns::RRTTL(360),
+               ZoneFinder::SUCCESS,
+               expected_rdatas, expected_sig_rdatas);
+    EXPECT_FALSE(current_database_->searchRunning());
+
+    expected_rdatas.clear();
+    expected_sig_rdatas.clear();
+    expected_rdatas.push_back("192.0.2.1");
+    expected_rdatas.push_back("192.0.2.2");
+    doFindTest(finder, isc::dns::Name("ttldiff2.example.org."),
+               isc::dns::RRType::A(), isc::dns::RRType::A(),
+               isc::dns::RRTTL(360),
+               ZoneFinder::SUCCESS,
+               expected_rdatas, expected_sig_rdatas);
+    EXPECT_FALSE(current_database_->searchRunning());
+
+
+    EXPECT_THROW(finder->find(isc::dns::Name("badcname1.example.org."),
+                                              isc::dns::RRType::A(),
+                                              NULL, ZoneFinder::FIND_DEFAULT),
+                 DataSourceError);
+    EXPECT_FALSE(current_database_->searchRunning());
+    EXPECT_THROW(finder->find(isc::dns::Name("badcname2.example.org."),
+                                              isc::dns::RRType::A(),
+                                              NULL, ZoneFinder::FIND_DEFAULT),
+                 DataSourceError);
+    EXPECT_FALSE(current_database_->searchRunning());
+    EXPECT_THROW(finder->find(isc::dns::Name("badcname3.example.org."),
+                                              isc::dns::RRType::A(),
+                                              NULL, ZoneFinder::FIND_DEFAULT),
+                 DataSourceError);
+    EXPECT_FALSE(current_database_->searchRunning());
+    EXPECT_THROW(finder->find(isc::dns::Name("badrdata.example.org."),
+                                              isc::dns::RRType::A(),
+                                              NULL, ZoneFinder::FIND_DEFAULT),
+                 DataSourceError);
+    EXPECT_FALSE(current_database_->searchRunning());
+    EXPECT_THROW(finder->find(isc::dns::Name("badtype.example.org."),
+                                              isc::dns::RRType::A(),
+                                              NULL, ZoneFinder::FIND_DEFAULT),
+                 DataSourceError);
+    EXPECT_FALSE(current_database_->searchRunning());
+    EXPECT_THROW(finder->find(isc::dns::Name("badttl.example.org."),
+                                              isc::dns::RRType::A(),
+                                              NULL, ZoneFinder::FIND_DEFAULT),
+                 DataSourceError);
+    EXPECT_FALSE(current_database_->searchRunning());
+    EXPECT_THROW(finder->find(isc::dns::Name("badsig.example.org."),
+                                              isc::dns::RRType::A(),
+                                              NULL, ZoneFinder::FIND_DEFAULT),
+                 DataSourceError);
+    EXPECT_FALSE(current_database_->searchRunning());
+
+    // 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);
+    EXPECT_FALSE(current_database_->searchRunning());
+    EXPECT_THROW(finder->find(isc::dns::Name("iscexception.in.search."),
+                                              isc::dns::RRType::A(),
+                                              NULL, ZoneFinder::FIND_DEFAULT),
+                 DataSourceError);
+    EXPECT_FALSE(current_database_->searchRunning());
+    EXPECT_THROW(finder->find(isc::dns::Name("basicexception.in.search."),
+                                              isc::dns::RRType::A(),
+                                              NULL, ZoneFinder::FIND_DEFAULT),
+                 std::exception);
+    EXPECT_FALSE(current_database_->searchRunning());
+
+    EXPECT_THROW(finder->find(isc::dns::Name("dsexception.in.getnext."),
+                                              isc::dns::RRType::A(),
+                                              NULL, ZoneFinder::FIND_DEFAULT),
+                 DataSourceError);
+    EXPECT_FALSE(current_database_->searchRunning());
+    EXPECT_THROW(finder->find(isc::dns::Name("iscexception.in.getnext."),
+                                              isc::dns::RRType::A(),
+                                              NULL, ZoneFinder::FIND_DEFAULT),
+                 DataSourceError);
+    EXPECT_FALSE(current_database_->searchRunning());
+    EXPECT_THROW(finder->find(isc::dns::Name("basicexception.in.getnext."),
+                                              isc::dns::RRType::A(),
+                                              NULL, ZoneFinder::FIND_DEFAULT),
+                 std::exception);
+    EXPECT_FALSE(current_database_->searchRunning());
+
+    // This RRSIG has the wrong sigtype field, which should be
+    // an error if we decide to keep using that field
+    // Right now the field is ignored, so it does not error
+    expected_rdatas.clear();
+    expected_sig_rdatas.clear();
+    expected_rdatas.push_back("192.0.2.1");
+    expected_sig_rdatas.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+    doFindTest(finder, isc::dns::Name("badsigtype.example.org."),
+               isc::dns::RRType::A(), isc::dns::RRType::A(),
+               isc::dns::RRTTL(3600),
+               ZoneFinder::SUCCESS,
+               expected_rdatas, expected_sig_rdatas);
+    EXPECT_FALSE(current_database_->searchRunning());
+}
+
 }

+ 143 - 1
src/lib/datasrc/tests/sqlite3_accessor_unittest.cc

@@ -11,13 +11,14 @@
 // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
-
 #include <datasrc/sqlite3_accessor.h>
+
 #include <datasrc/data_source.h>
 
 #include <dns/rrclass.h>
 
 #include <gtest/gtest.h>
+#include <boost/scoped_ptr.hpp>
 
 using namespace isc::datasrc;
 using isc::data::ConstElementPtr;
@@ -29,7 +30,9 @@ namespace {
 // Some test data
 std::string SQLITE_DBFILE_EXAMPLE = TEST_DATA_DIR "/test.sqlite3";
 std::string SQLITE_DBFILE_EXAMPLE2 = TEST_DATA_DIR "/example2.com.sqlite3";
+std::string SQLITE_DBNAME_EXAMPLE2 = "sqlite3_example2.com.sqlite3";
 std::string SQLITE_DBFILE_EXAMPLE_ROOT = TEST_DATA_DIR "/test-root.sqlite3";
+std::string SQLITE_DBNAME_EXAMPLE_ROOT = "sqlite3_test-root.sqlite3";
 std::string SQLITE_DBFILE_BROKENDB = TEST_DATA_DIR "/brokendb.sqlite3";
 std::string SQLITE_DBFILE_MEMORY = ":memory:";
 
@@ -123,4 +126,143 @@ TEST_F(SQLite3Access, iterator) {
     EXPECT_FALSE(context->getNext(data));
 }
 
+TEST(SQLite3Open, getDBNameExample2) {
+    SQLite3Database db(SQLITE_DBFILE_EXAMPLE2, RRClass::IN());
+    EXPECT_EQ(SQLITE_DBNAME_EXAMPLE2, db.getDBName());
 }
+
+TEST(SQLite3Open, getDBNameExampleROOT) {
+    SQLite3Database db(SQLITE_DBFILE_EXAMPLE_ROOT, RRClass::IN());
+    EXPECT_EQ(SQLITE_DBNAME_EXAMPLE_ROOT, db.getDBName());
+}
+
+// Simple function to cound the number of records for
+// any name
+void
+checkRecordRow(const std::string columns[],
+               const std::string& field0,
+               const std::string& field1,
+               const std::string& field2,
+               const std::string& field3)
+{
+    EXPECT_EQ(field0, columns[0]);
+    EXPECT_EQ(field1, columns[1]);
+    EXPECT_EQ(field2, columns[2]);
+    EXPECT_EQ(field3, columns[3]);
+}
+
+TEST_F(SQLite3Access, getRecords) {
+    const std::pair<bool, int> zone_info(db->getZone(Name("example.com")));
+    ASSERT_TRUE(zone_info.first);
+
+    const int zone_id = zone_info.second;
+    ASSERT_EQ(1, zone_id);
+
+    const size_t column_count = DatabaseAccessor::COLUMN_COUNT;
+    std::string columns[column_count];
+
+    // without search, getNext() should return false
+    EXPECT_FALSE(db->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "", "", "", "");
+
+    db->searchForRecords(zone_id, "foo.bar.");
+    EXPECT_FALSE(db->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "", "", "", "");
+
+    db->searchForRecords(zone_id, "");
+    EXPECT_FALSE(db->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "", "", "", "");
+
+    // Should error on a bad number of columns
+    EXPECT_THROW(db->getNextRecord(columns, 3), DataSourceError);
+    EXPECT_THROW(db->getNextRecord(columns, 5), DataSourceError);
+
+    // now try some real searches
+    db->searchForRecords(zone_id, "foo.example.com.");
+    ASSERT_TRUE(db->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "CNAME", "3600", "",
+                   "cnametest.example.org.");
+    ASSERT_TRUE(db->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "RRSIG", "3600", "CNAME",
+                   "CNAME 5 3 3600 20100322084538 20100220084538 33495 "
+                   "example.com. FAKEFAKEFAKEFAKE");
+    ASSERT_TRUE(db->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "NSEC", "7200", "",
+                   "mail.example.com. CNAME RRSIG NSEC");
+    ASSERT_TRUE(db->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "RRSIG", "7200", "NSEC",
+                   "NSEC 5 3 7200 20100322084538 20100220084538 33495 "
+                   "example.com. FAKEFAKEFAKEFAKE");
+    EXPECT_FALSE(db->getNextRecord(columns, column_count));
+    // with no more records, the array should not have been modified
+    checkRecordRow(columns, "RRSIG", "7200", "NSEC",
+                   "NSEC 5 3 7200 20100322084538 20100220084538 33495 "
+                   "example.com. FAKEFAKEFAKEFAKE");
+
+    db->searchForRecords(zone_id, "example.com.");
+    ASSERT_TRUE(db->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "SOA", "3600", "",
+                   "master.example.com. admin.example.com. "
+                   "1234 3600 1800 2419200 7200");
+    ASSERT_TRUE(db->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "RRSIG", "3600", "SOA",
+                   "SOA 5 2 3600 20100322084538 20100220084538 "
+                   "33495 example.com. FAKEFAKEFAKEFAKE");
+    ASSERT_TRUE(db->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "NS", "1200", "", "dns01.example.com.");
+    ASSERT_TRUE(db->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "NS", "3600", "", "dns02.example.com.");
+    ASSERT_TRUE(db->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "NS", "1800", "", "dns03.example.com.");
+    ASSERT_TRUE(db->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "RRSIG", "3600", "NS",
+                   "NS 5 2 3600 20100322084538 20100220084538 "
+                   "33495 example.com. FAKEFAKEFAKEFAKE");
+    ASSERT_TRUE(db->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "MX", "3600", "", "10 mail.example.com.");
+    ASSERT_TRUE(db->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "MX", "3600", "",
+                   "20 mail.subzone.example.com.");
+    ASSERT_TRUE(db->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "RRSIG", "3600", "MX",
+                   "MX 5 2 3600 20100322084538 20100220084538 "
+                   "33495 example.com. FAKEFAKEFAKEFAKE");
+    ASSERT_TRUE(db->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "NSEC", "7200", "",
+                   "cname-ext.example.com. NS SOA MX RRSIG NSEC DNSKEY");
+    ASSERT_TRUE(db->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "RRSIG", "7200", "NSEC",
+                   "NSEC 5 2 7200 20100322084538 20100220084538 "
+                   "33495 example.com. FAKEFAKEFAKEFAKE");
+    ASSERT_TRUE(db->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "DNSKEY", "3600", "",
+                   "256 3 5 AwEAAcOUBllYc1hf7ND9uDy+Yz1BF3sI0m4q NGV7W"
+                   "cTD0WEiuV7IjXgHE36fCmS9QsUxSSOV o1I/FMxI2PJVqTYHkX"
+                   "FBS7AzLGsQYMU7UjBZ SotBJ6Imt5pXMu+lEDNy8TOUzG3xm7g"
+                   "0qcbW YF6qCEfvZoBtAqi5Rk7Mlrqs8agxYyMx");
+    ASSERT_TRUE(db->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "DNSKEY", "3600", "",
+                   "257 3 5 AwEAAe5WFbxdCPq2jZrZhlMj7oJdff3W7syJ tbvzg"
+                   "62tRx0gkoCDoBI9DPjlOQG0UAbj+xUV 4HQZJStJaZ+fHU5AwV"
+                   "NT+bBZdtV+NujSikhd THb4FYLg2b3Cx9NyJvAVukHp/91HnWu"
+                   "G4T36 CzAFrfPwsHIrBz9BsaIQ21VRkcmj7DswfI/i DGd8j6b"
+                   "qiODyNZYQ+ZrLmF0KIJ2yPN3iO6Zq 23TaOrVTjB7d1a/h31OD"
+                   "fiHAxFHrkY3t3D5J R9Nsl/7fdRmSznwtcSDgLXBoFEYmw6p86"
+                   "Acv RyoYNcL1SXjaKVLG5jyU3UR+LcGZT5t/0xGf oIK/aKwEN"
+                   "rsjcKZZj660b1M=");
+    ASSERT_TRUE(db->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "RRSIG", "3600", "DNSKEY",
+                   "DNSKEY 5 2 3600 20100322084538 20100220084538 "
+                   "4456 example.com. FAKEFAKEFAKEFAKE");
+    ASSERT_TRUE(db->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "RRSIG", "3600", "DNSKEY",
+                   "DNSKEY 5 2 3600 20100322084538 20100220084538 "
+                   "33495 example.com. FAKEFAKEFAKEFAKE");
+    EXPECT_FALSE(db->getNextRecord(columns, column_count));
+    // getnextrecord returning false should mean array is not altered
+    checkRecordRow(columns, "RRSIG", "3600", "DNSKEY",
+                   "DNSKEY 5 2 3600 20100322084538 20100220084538 "
+                   "33495 example.com. FAKEFAKEFAKEFAKE");
+}
+
+} // end anonymous namespace

+ 1 - 1
src/lib/datasrc/zone.h

@@ -197,7 +197,7 @@ public:
                             const isc::dns::RRType& type,
                             isc::dns::RRsetList* target = NULL,
                             const FindOptions options
-                            = FIND_DEFAULT) const = 0;
+                            = FIND_DEFAULT) = 0;
     //@}
 };
 

+ 5 - 0
src/lib/dns/rdata/generic/rrsig_46.cc

@@ -243,5 +243,10 @@ RRSIG::compare(const Rdata& other) const {
     }
 }
 
+const RRType&
+RRSIG::typeCovered() {
+    return (impl_->covered_);
+}
+
 // END_RDATA_NAMESPACE
 // END_ISC_NAMESPACE

+ 3 - 0
src/lib/dns/rdata/generic/rrsig_46.h

@@ -38,6 +38,9 @@ public:
     // END_COMMON_MEMBERS
     RRSIG& operator=(const RRSIG& source);
     ~RRSIG();
+
+    // specialized methods
+    const RRType& typeCovered();
 private:
     RRSIGImpl* impl_;
 };

+ 1 - 1
src/lib/dns/tests/rdata_rrsig_unittest.cc

@@ -47,7 +47,7 @@ TEST_F(Rdata_RRSIG_Test, fromText) {
                      "f49t+sXKPzbipN9g+s1ZPiIyofc=");
     generic::RRSIG rdata_rrsig(rrsig_txt);
     EXPECT_EQ(rrsig_txt, rdata_rrsig.toText());
-
+    EXPECT_EQ(isc::dns::RRType::A(), rdata_rrsig.typeCovered());
 }
 
 TEST_F(Rdata_RRSIG_Test, badText) {

+ 5 - 0
src/lib/util/filename.h

@@ -103,6 +103,11 @@ public:
         return (extension_);
     }
 
+    /// \return Name + extension of Given File Name
+    std::string nameAndExtension() const {
+        return (name_ + extension_);
+    }
+
     /// \brief Expand Name with Default
     ///
     /// A default file specified is supplied and used to fill in any missing

+ 15 - 0
src/lib/util/tests/filename_unittest.cc

@@ -51,42 +51,49 @@ TEST_F(FilenameTest, Components) {
     EXPECT_EQ("/alpha/beta/", fname.directory());
     EXPECT_EQ("gamma", fname.name());
     EXPECT_EQ(".delta", fname.extension());
+    EXPECT_EQ("gamma.delta", fname.nameAndExtension());
 
     // Directory only
     fname.setName("/gamma/delta/");
     EXPECT_EQ("/gamma/delta/", fname.directory());
     EXPECT_EQ("", fname.name());
     EXPECT_EQ("", fname.extension());
+    EXPECT_EQ("", fname.nameAndExtension());
 
     // Filename only
     fname.setName("epsilon");
     EXPECT_EQ("", fname.directory());
     EXPECT_EQ("epsilon", fname.name());
     EXPECT_EQ("", fname.extension());
+    EXPECT_EQ("epsilon", fname.nameAndExtension());
 
     // Extension only
     fname.setName(".zeta");
     EXPECT_EQ("", fname.directory());
     EXPECT_EQ("", fname.name());
     EXPECT_EQ(".zeta", fname.extension());
+    EXPECT_EQ(".zeta", fname.nameAndExtension());
 
     // Missing directory
     fname.setName("eta.theta");
     EXPECT_EQ("", fname.directory());
     EXPECT_EQ("eta", fname.name());
     EXPECT_EQ(".theta", fname.extension());
+    EXPECT_EQ("eta.theta", fname.nameAndExtension());
 
     // Missing filename
     fname.setName("/iota/.kappa");
     EXPECT_EQ("/iota/", fname.directory());
     EXPECT_EQ("", fname.name());
     EXPECT_EQ(".kappa", fname.extension());
+    EXPECT_EQ(".kappa", fname.nameAndExtension());
 
     // Missing extension
     fname.setName("lambda/mu/nu");
     EXPECT_EQ("lambda/mu/", fname.directory());
     EXPECT_EQ("nu", fname.name());
     EXPECT_EQ("", fname.extension());
+    EXPECT_EQ("nu", fname.nameAndExtension());
 
     // Check that the decomposition can occur in the presence of leading and
     // trailing spaces
@@ -94,18 +101,21 @@ TEST_F(FilenameTest, Components) {
     EXPECT_EQ("lambda/mu/", fname.directory());
     EXPECT_EQ("nu", fname.name());
     EXPECT_EQ("", fname.extension());
+    EXPECT_EQ("nu", fname.nameAndExtension());
 
     // Empty string
     fname.setName("");
     EXPECT_EQ("", fname.directory());
     EXPECT_EQ("", fname.name());
     EXPECT_EQ("", fname.extension());
+    EXPECT_EQ("", fname.nameAndExtension());
 
     // ... and just spaces
     fname.setName("  ");
     EXPECT_EQ("", fname.directory());
     EXPECT_EQ("", fname.name());
     EXPECT_EQ("", fname.extension());
+    EXPECT_EQ("", fname.nameAndExtension());
 
     // Check corner cases - where separators are present, but strings are
     // absent.
@@ -113,16 +123,19 @@ TEST_F(FilenameTest, Components) {
     EXPECT_EQ("/", fname.directory());
     EXPECT_EQ("", fname.name());
     EXPECT_EQ("", fname.extension());
+    EXPECT_EQ("", fname.nameAndExtension());
 
     fname.setName(".");
     EXPECT_EQ("", fname.directory());
     EXPECT_EQ("", fname.name());
     EXPECT_EQ(".", fname.extension());
+    EXPECT_EQ(".", fname.nameAndExtension());
 
     fname.setName("/.");
     EXPECT_EQ("/", fname.directory());
     EXPECT_EQ("", fname.name());
     EXPECT_EQ(".", fname.extension());
+    EXPECT_EQ(".", fname.nameAndExtension());
 
     // Note that the space is a valid filename here; only leading and trailing
     // spaces should be trimmed.
@@ -130,11 +143,13 @@ TEST_F(FilenameTest, Components) {
     EXPECT_EQ("/", fname.directory());
     EXPECT_EQ(" ", fname.name());
     EXPECT_EQ(".", fname.extension());
+    EXPECT_EQ(" .", fname.nameAndExtension());
 
     fname.setName(" / . ");
     EXPECT_EQ("/", fname.directory());
     EXPECT_EQ(" ", fname.name());
     EXPECT_EQ(".", fname.extension());
+    EXPECT_EQ(" .", fname.nameAndExtension());
 }
 
 // Check that the expansion with a default works.