sqlite3_datasrc.cc 23.5 KB
Newer Older
JINMEI Tatuya's avatar
JINMEI Tatuya committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// 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.

// $Id$
16

17
#include <string>
18
#include <sstream>
19

20 21
#include <sqlite3.h>

22
#include <datasrc/sqlite3_datasrc.h>
Evan Hunt's avatar
Evan Hunt committed
23

24 25 26 27 28 29 30 31 32 33 34
#include <dns/rrttl.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
#include <dns/rrset.h>
#include <dns/rrsetlist.h>

using namespace std;
using namespace isc::dns;
using namespace isc::dns::rdata;

namespace isc {
Evan Hunt's avatar
Evan Hunt committed
35
namespace datasrc {
36

37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
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_prevnsec3_(NULL)
    {}
    sqlite3* db_;
    int version_;
    sqlite3_stmt* q_zone_;
    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_;
    sqlite3_stmt* q_prevnsec3_;
};

56
namespace {
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
const char* const SCHEMA_LIST[] = {
    "CREATE TABLE schema_version (version INTEGER NOT NULL)",
    "INSERT INTO schema_version VALUES (1)",
    "CREATE TABLE zones (id INTEGER PRIMARY KEY, "
    "name STRING NOT NULL COLLATE NOCASE, "
    "rdclass STRING NOT NULL COLLATE NOCASE DEFAULT 'IN', "
    "dnssec BOOLEAN NOT NULL DEFAULT 0)",
    "CREATE INDEX zones_byname ON zones (name)",
    "CREATE TABLE records (id INTEGER PRIMARY KEY, "
    "zone_id INTEGER NOT NULL, name STRING NOT NULL COLLATE NOCASE, "
    "rname STRING NOT NULL COLLATE NOCASE, ttl INTEGER NOT NULL, "
    "rdtype STRING NOT NULL COLLATE NOCASE, sigtype STRING COLLATE NOCASE, "
    "rdata STRING NOT NULL)",
    "CREATE INDEX records_byname ON records (name)",
    "CREATE INDEX records_byrname ON records (rname)",
    "CREATE TABLE nsec3 (id INTEGER PRIMARY KEY, zone_id INTEGER NOT NULL, "
73 74
    "hash STRING NOT NULL COLLATE NOCASE, "
    "owner STRING NOT NULL COLLATE NOCASE, "
75 76 77 78 79 80 81
    "ttl INTEGER NOT NULL, rdtype STRING NOT NULL COLLATE NOCASE, "
    "rdata STRING NOT NULL)",
    "CREATE INDEX nsec3_byhash ON nsec3 (hash)",
    NULL
};

const char* const q_zone_str = "SELECT id FROM zones WHERE name=?1";
82

83 84 85 86 87
const char* const q_record_str = "SELECT rdtype, ttl, sigtype, rdata "
    "FROM records WHERE zone_id=?1 AND name=?2 AND "
    "((rdtype=?3 OR sigtype=?3) OR "
    "(rdtype='CNAME' OR sigtype='CNAME') OR "
    "(rdtype='NS' OR sigtype='NS'))";
88

89 90 91
const char* const q_addrs_str = "SELECT rdtype, ttl, sigtype, rdata "
    "FROM records WHERE zone_id=?1 AND name=?2 AND "
    "(rdtype='A' OR sigtype='A' OR rdtype='AAAA' OR sigtype='AAAA')";
92

93 94 95 96
const char* const q_referral_str = "SELECT rdtype, ttl, sigtype, rdata FROM "
    "records WHERE zone_id=?1 AND name=?2 AND"
    "(rdtype='NS' OR sigtype='NS' OR rdtype='DS' OR sigtype='DS' OR "
    "rdtype='DNAME' OR sigtype='DNAME')";
97

98 99
const char* const q_any_str = "SELECT rdtype, ttl, sigtype, rdata "
    "FROM records WHERE zone_id=?1 AND name=?2";
100

101 102
const char* const q_count_str = "SELECT COUNT(*) FROM records "
    "WHERE zone_id=?1 AND rname LIKE (?2 || '%');";
103

104 105 106
const char* const q_previous_str = "SELECT name FROM records "
    "WHERE zone_id=?1 AND rdtype = 'NSEC' AND "
    "rname < $2 ORDER BY rname DESC LIMIT 1";
107

108 109
const char* const q_nsec3_str = "SELECT rdtype, ttl, rdata FROM nsec3 "
    "WHERE zone_id = ?1 AND hash = $2";
110

111 112
const char* const q_prevnsec3_str = "SELECT hash FROM nsec3 "
    "WHERE zone_id = ?1 AND hash <= $2 ORDER BY hash DESC LIMIT 1";
113 114 115 116 117 118 119

}

//
//  Find the exact zone match.  Return -1 if not found, or the zone's
//  ID if found.  This will always be >= 0 if found.
//
JINMEI Tatuya's avatar
JINMEI Tatuya committed
120
int
JINMEI Tatuya's avatar
JINMEI Tatuya committed
121
Sqlite3DataSrc::hasExactZone(const char* const name) const {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
122 123
    int rc;

124 125
    sqlite3_reset(dbparameters->q_zone_);
    rc = sqlite3_bind_text(dbparameters->q_zone_, 1, name, -1, SQLITE_STATIC);
126
    if (rc != SQLITE_OK) {
127 128
        isc_throw(Sqlite3Error, "Could not bind " << name <<
                  " to SQL statement (zone)");
129
    }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
130

131 132 133 134
    rc = sqlite3_step(dbparameters->q_zone_);
    const int i = (rc == SQLITE_ROW) ?
        sqlite3_column_int(dbparameters->q_zone_, 0) : -1; 
    sqlite3_reset(dbparameters->q_zone_);
135 136 137
    return (i);
}

JINMEI Tatuya's avatar
JINMEI Tatuya committed
138 139
namespace {
int
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
importSqlite3Rows(sqlite3_stmt* query, const Name& qname, const RRClass& qclass,
                  const RRType& qtype, const bool nsec3_tree,
                  RRsetList& result_sets, uint32_t& flags)
{
    int rows = 0;
    int rc = sqlite3_step(query);
    const bool qtype_is_any = (qtype == RRType::ANY());

    while (rc == SQLITE_ROW) {
        const char* type = (const char*)sqlite3_column_text(query, 0);
        int ttl = sqlite3_column_int(query, 1);
        const char* sigtype = NULL;
        const char* rdata;

        if (nsec3_tree) {
            rdata = (const char*)sqlite3_column_text(query, 2);
            if (RRType(type) == RRType::RRSIG()) {
                sigtype = "NSEC3";
            }
        } else {
            sigtype = (const char*)sqlite3_column_text(query, 2);
            rdata = (const char*)sqlite3_column_text(query, 3);
        }

        const RRType base_rrtype(sigtype != NULL ? sigtype : type);

        // found an NS; we need to inform the caller that this might be a
        // referral, but we do not return the NS RRset to the caller
        // unless asked for it.
169
        if (base_rrtype == RRType::NS()) {
170
            flags |= DataSrc::REFERRAL;
171
            if (!qtype_is_any && qtype != RRType::NS()) {
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
                rc = sqlite3_step(query);
                continue;
            }
        }

        ++rows;

        // Looking for something else but found CNAME
        if (base_rrtype == RRType::CNAME() && qtype != RRType::CNAME()) {
            if (qtype == RRType::NSEC()) {
                // NSEC query, just skip the CNAME
                rc = sqlite3_step(query);
                continue;
            } else if (!qtype_is_any) {
                // include the CNAME, but don't flag it for chasing if
                // this is an ANY query
                flags |= DataSrc::CNAME_FOUND;
            }
        }

192
        RRsetPtr rrset = result_sets.findRRset(base_rrtype, qclass);
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
        if (rrset == NULL) {
            rrset = RRsetPtr(new RRset(qname, qclass, base_rrtype, RRTTL(ttl)));
            result_sets.addRRset(rrset);
        }

        if (sigtype == NULL && base_rrtype == rrset->getType()) {
            rrset->addRdata(createRdata(rrset->getType(), qclass, rdata));
            if (ttl > rrset->getTTL().getValue()) {
                rrset->setTTL(RRTTL(ttl));
            }
        } else if (sigtype != NULL && base_rrtype == rrset->getType()) {
            RdataPtr rrsig = createRdata(RRType::RRSIG(), qclass, rdata);
            if (rrset->getRRsig()) {
                rrset->getRRsig()->addRdata(rrsig);
            } else {
                RRsetPtr sigs = RRsetPtr(new RRset(qname, qclass,
                                                   RRType::RRSIG(),
                                                   RRTTL(ttl)));
                sigs->addRdata(rrsig);
                rrset->addRRsig(sigs);
            }

            if (ttl > rrset->getRRsig()->getTTL().getValue()) {
                rrset->getRRsig()->setTTL(RRTTL(ttl));
            }
        }

        rc = sqlite3_step(query);
    }

    return (rows);
}
JINMEI Tatuya's avatar
JINMEI Tatuya committed
225
}
226

227
int
228 229 230
Sqlite3DataSrc::findRecords(const Name& name, const RRType& rdtype,
                            RRsetList& target, const Name* zonename,
                            const Mode mode, uint32_t& flags) const
231
{
232 233 234 235 236 237 238
    flags = 0;
    int zone_id = (zonename == NULL) ? findClosest(name, NULL) :
        findClosest(*zonename, NULL);
    if (zone_id < 0) {
        flags = NO_SUCH_ZONE;
        return (0);
    }
239

240
    sqlite3_stmt* query;
241 242
    switch (mode) {
    case ADDRESS:
243
        query = dbparameters->q_addrs_;
244 245
        break;
    case DELEGATION:
246
        query = dbparameters->q_referral_;
247 248 249
        break;
    default:
        if (rdtype == RRType::ANY()) {
250
            query = dbparameters->q_any_;
251
        } else {
252
            query = dbparameters->q_record_;
253 254 255 256 257 258 259
        }
        break;
    }
    
    sqlite3_reset(query);
    sqlite3_clear_bindings(query);

JINMEI Tatuya's avatar
JINMEI Tatuya committed
260
    int rc;
261 262
    rc = sqlite3_bind_int(query, 1, zone_id);
    if (rc != SQLITE_OK) {
263 264
        isc_throw(Sqlite3Error, "Could not bind zone ID " << zone_id <<
                  " to SQL statement (query)");
265
    }
266 267
    const string name_text = name.toText();
    rc = sqlite3_bind_text(query, 2, name_text.c_str(), -1, SQLITE_STATIC);
268
    if (rc != SQLITE_OK) {
269
        isc_throw(Sqlite3Error, "Could not bind name " << name_text <<
270
                  " to SQL statement (query)");
271 272
    }

273
    const string rdtype_text = rdtype.toText();
274
    if (query == dbparameters->q_record_) {
275
        rc = sqlite3_bind_text(query, 3, rdtype_text.c_str(), -1,
JINMEI Tatuya's avatar
JINMEI Tatuya committed
276
                               SQLITE_STATIC);
277
        if (rc != SQLITE_OK) {
278 279
            isc_throw(Sqlite3Error, "Could not bind RR type " <<
                      rdtype.toText() << " to SQL statement (query)");
280 281 282
        }
    }

283 284
    const int rows = importSqlite3Rows(query, name, getClass(), rdtype, false,
                                       target, flags);
285 286 287 288 289 290 291 292 293 294
    sqlite3_reset(query);
    if (rows > 0) {
        return (rows);
    }

    //
    // No rows were found.  We need to find out whether there are
    // any RRs with that name to determine whether this is NXDOMAIN or
    // NXRRSET
    //
295 296 297 298
    sqlite3_reset(dbparameters->q_count_);
    sqlite3_clear_bindings(dbparameters->q_count_);
    
    rc = sqlite3_bind_int(dbparameters->q_count_, 1, zone_id);
299
    if (rc != SQLITE_OK) {
300 301
        isc_throw(Sqlite3Error, "Could not bind zone ID " << zone_id <<
                  " to SQL statement (qcount)");
302 303
    }

304 305 306
    const string revname_text = name.reverse().toText();
    rc = sqlite3_bind_text(dbparameters->q_count_, 2, revname_text.c_str(),
                           -1, SQLITE_STATIC);
307
    if (rc != SQLITE_OK) {
308 309
        isc_throw(Sqlite3Error, "Could not bind name " << name.reverse() <<
                  " to SQL statement (qcount)");
310 311
    }

312
    rc = sqlite3_step(dbparameters->q_count_);
JINMEI Tatuya's avatar
JINMEI Tatuya committed
313
    if (rc == SQLITE_ROW) {
314
        if (sqlite3_column_int(dbparameters->q_count_, 0) != 0) {
315
            flags |= TYPE_NOT_FOUND;
316
            sqlite3_reset(dbparameters->q_count_);
317 318 319 320 321
            return (0);
        }
    }

    flags |= NAME_NOT_FOUND;
322
    sqlite3_reset(dbparameters->q_count_);
323 324 325 326 327 328 329 330
    return (0);
}

//
//  Search for the closest enclosing zone.  Will return -1 if not found,
//  >= 0 if found.  If position is not NULL, it will be filled in with the
//  longest match found.
//
JINMEI Tatuya's avatar
JINMEI Tatuya committed
331
int
332 333 334
Sqlite3DataSrc::findClosest(const Name& name, unsigned int* position) const {
    const unsigned int nlabels = name.getLabelCount();
    for (unsigned int i = 0; i < nlabels; ++i) {
335
        const Name matchname(name.split(i));
336
        const int rc = hasExactZone(matchname.toText().c_str());
337 338
        if (rc >= 0) {
            if (position != NULL) {
339
                *position = i;
340 341 342 343 344 345 346 347 348
            }
            return (rc);
        }
    }

    return (-1);
}

void
Evan Hunt's avatar
Evan Hunt committed
349 350
Sqlite3DataSrc::findClosestEnclosure(DataSrcMatch& match) const {
    if (match.getClass() != getClass() && match.getClass() != RRClass::ANY()) {
351 352 353
        return;
    }

354
    unsigned int position;
Evan Hunt's avatar
Evan Hunt committed
355
    if (findClosest(match.getName(), &position) == -1) {
356 357 358
        return;
    }

Evan Hunt's avatar
Evan Hunt committed
359
    match.update(*this, match.getName().split(position));
360 361 362
}

DataSrc::Result
363
Sqlite3DataSrc::findPreviousName(const Name& qname,
364
                                 Name& target,
365
                                 const Name* zonename) const
366
{
367 368
    const int zone_id = (zonename == NULL) ?
        findClosest(qname, NULL) : findClosest(*zonename, NULL);
369 370 371 372
    if (zone_id < 0) {
        return (ERROR);
    }
    
373 374
    sqlite3_reset(dbparameters->q_previous_);
    sqlite3_clear_bindings(dbparameters->q_previous_);
375

376
    int rc = sqlite3_bind_int(dbparameters->q_previous_, 1, zone_id);
377
    if (rc != SQLITE_OK) {
378 379
        isc_throw(Sqlite3Error, "Could not bind zone ID " << zone_id <<
                  " to SQL statement (qprevious)");        
380
    }
381
    const string revname_text = qname.reverse().toText();
382
    rc = sqlite3_bind_text(dbparameters->q_previous_, 2,
383
                           revname_text.c_str(), -1, SQLITE_STATIC);
384
    if (rc != SQLITE_OK) {
385 386
        isc_throw(Sqlite3Error, "Could not bind name " << qname <<
                  " to SQL statement (qprevious)");
387
    }
Evan Hunt's avatar
Evan Hunt committed
388
  
389
    rc = sqlite3_step(dbparameters->q_previous_);
390
    if (rc != SQLITE_ROW) {
391
        sqlite3_reset(dbparameters->q_previous_);
392 393 394
        return (ERROR);
    }

JINMEI Tatuya's avatar
JINMEI Tatuya committed
395
    // XXX: bad cast.  we should revisit this.
396 397 398
    target = Name((const char*)sqlite3_column_text(dbparameters->q_previous_,
                                                   0));
    sqlite3_reset(dbparameters->q_previous_);
399 400 401
    return (SUCCESS);
}

Evan Hunt's avatar
Evan Hunt committed
402
DataSrc::Result
403
Sqlite3DataSrc::findCoveringNSEC3(const Name& zonename,
404
                                  string& hashstr,
Evan Hunt's avatar
Evan Hunt committed
405 406
                                  RRsetList& target) const
{
407
    const int zone_id = findClosest(zonename, NULL);
Evan Hunt's avatar
Evan Hunt committed
408 409 410 411
    if (zone_id < 0) {
        return (ERROR);
    }

412 413
    sqlite3_reset(dbparameters->q_prevnsec3_);
    sqlite3_clear_bindings(dbparameters->q_prevnsec3_);
Evan Hunt's avatar
Evan Hunt committed
414

415
    int rc = sqlite3_bind_int(dbparameters->q_prevnsec3_, 1, zone_id);
Evan Hunt's avatar
Evan Hunt committed
416
    if (rc != SQLITE_OK) {
417 418
        isc_throw(Sqlite3Error, "Could not bind zone ID " << zone_id <<
                  " to SQL statement (previous NSEC3)");        
Evan Hunt's avatar
Evan Hunt committed
419 420
    }

421 422
    rc = sqlite3_bind_text(dbparameters->q_prevnsec3_, 2, hashstr.c_str(),
                           -1, SQLITE_STATIC);
Evan Hunt's avatar
Evan Hunt committed
423
    if (rc != SQLITE_OK) {
424 425
        isc_throw(Sqlite3Error, "Could not bind hash " << hashstr <<
                  " to SQL statement (previous NSEC3)");
Evan Hunt's avatar
Evan Hunt committed
426 427
    }

428
    rc = sqlite3_step(dbparameters->q_prevnsec3_);
Evan Hunt's avatar
Evan Hunt committed
429 430
    const char* hash;
    if (rc == SQLITE_ROW) {
431
        hash = (const char*) sqlite3_column_text(dbparameters->q_prevnsec3_, 0);
Evan Hunt's avatar
Evan Hunt committed
432 433 434 435
    } else {
        // We need to find the final NSEC3 in the chain.
        // A valid NSEC3 hash is in base32, which contains no
        // letters higher than V, so a search for the previous 
Evan Hunt's avatar
Evan Hunt committed
436
        // NSEC3 from "w" will always find it.
437 438 439
        sqlite3_reset(dbparameters->q_prevnsec3_);
        rc = sqlite3_bind_text(dbparameters->q_prevnsec3_, 2, "w", -1,
                               SQLITE_STATIC);
Evan Hunt's avatar
Evan Hunt committed
440
        if (rc != SQLITE_OK) {
441 442
            isc_throw(Sqlite3Error, "Could not bind \"w\""
                      " to SQL statement (previous NSEC3)");
Evan Hunt's avatar
Evan Hunt committed
443 444
        }

445
        rc = sqlite3_step(dbparameters->q_prevnsec3_);
Evan Hunt's avatar
Evan Hunt committed
446 447 448 449
        if (rc != SQLITE_ROW) {
            return (ERROR);
        }

450
        hash = (const char*) sqlite3_column_text(dbparameters->q_prevnsec3_, 0);
Evan Hunt's avatar
Evan Hunt committed
451 452
    }

453 454
    sqlite3_reset(dbparameters->q_nsec3_);
    sqlite3_clear_bindings(dbparameters->q_nsec3_);
Evan Hunt's avatar
Evan Hunt committed
455

456
    rc = sqlite3_bind_int(dbparameters->q_nsec3_, 1, zone_id);
Evan Hunt's avatar
Evan Hunt committed
457
    if (rc != SQLITE_OK) {
458 459
        isc_throw(Sqlite3Error, "Could not bind zone ID " << zone_id <<
                  " to SQL statement (NSEC3)");        
Evan Hunt's avatar
Evan Hunt committed
460 461
    }

462
    rc = sqlite3_bind_text(dbparameters->q_nsec3_, 2, hash, -1, SQLITE_STATIC);
Evan Hunt's avatar
Evan Hunt committed
463
    if (rc != SQLITE_OK) {
464 465
        isc_throw(Sqlite3Error, "Could not bind hash " << hash <<
                  " to SQL statement (NSEC3)");
Evan Hunt's avatar
Evan Hunt committed
466 467
    }

468 469
    DataSrc::Result result = SUCCESS;
    uint32_t flags = 0;
470 471
    if (importSqlite3Rows(dbparameters->q_nsec3_,
                          Name(hash).concatenate(zonename),
472 473 474
                          getClass(), RRType::NSEC3(), true, target,
                          flags) == 0 || flags != 0) {
        result = ERROR;
Evan Hunt's avatar
Evan Hunt committed
475
    }
476
    hashstr = string(hash);
477
    sqlite3_reset(dbparameters->q_nsec3_);
478
    return (result);
Evan Hunt's avatar
Evan Hunt committed
479 480
}

481
DataSrc::Result
482
Sqlite3DataSrc::findRRset(const Name& qname,
483 484 485 486
                          const RRClass& qclass,
                          const RRType& qtype,
                          RRsetList& target,
                          uint32_t& flags,
487
                          const Name* zonename) const
488
{
489
    if (qclass != getClass() && qclass != RRClass::ANY()) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
490 491
        return (ERROR);
    }
492
    findRecords(qname, qtype, target, zonename, NORMAL, flags);
493 494 495 496
    return (SUCCESS);
}

DataSrc::Result
497
Sqlite3DataSrc::findExactRRset(const Name& qname,
498 499 500 501
                               const RRClass& qclass,
                               const RRType& qtype,
                               RRsetList& target,
                               uint32_t& flags,
502
                               const Name* zonename) const
503
{
504
    if (qclass != getClass() && qclass != RRClass::ANY()) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
505 506
        return (ERROR);
    }
507
    findRecords(qname, qtype, target, zonename, NORMAL, flags);
508 509 510 511 512 513 514 515 516 517 518 519 520 521

    // Ignore referrals in this case
    flags &= ~REFERRAL;

    // CNAMEs don't count in this case
    if (flags & CNAME_FOUND) {
        flags &= ~CNAME_FOUND;
        flags |= TYPE_NOT_FOUND;
    }

    return (SUCCESS);
}

DataSrc::Result
522
Sqlite3DataSrc::findAddrs(const Name& qname,
523 524 525
                          const RRClass& qclass,
                          RRsetList& target,
                          uint32_t& flags,
526
                          const Name* zonename) const
527
{
528
    if (qclass != getClass() && qclass != RRClass::ANY()) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
529 530
        return (ERROR);
    }
531
    findRecords(qname, RRType::ANY(), target, zonename, ADDRESS, flags);
532 533 534 535
    return (SUCCESS);
}

DataSrc::Result
536
Sqlite3DataSrc::findReferral(const Name& qname,
537 538 539
                             const RRClass& qclass,
                             RRsetList& target,
                             uint32_t& flags,
540
                             const Name* zonename) const
541
{
542
    if (qclass != getClass() && qclass != RRClass::ANY()) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
543 544
            return (ERROR);
    }
545
    findRecords(qname, RRType::ANY(), target, zonename, DELEGATION, flags);
546 547
    return (SUCCESS);
}
548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564

Sqlite3DataSrc::Sqlite3DataSrc() :
    dbparameters(new Sqlite3Parameters)
{}

Sqlite3DataSrc::~Sqlite3DataSrc() {
    if (dbparameters->db_ != NULL) {
        close();
    }
    delete dbparameters;
}

DataSrc::Result
Sqlite3DataSrc::init(const isc::data::ElementPtr config) {
    if (config && config->contains("database_file")) {
        open(config->get("database_file")->stringValue());
    } else {
Shane Kerr's avatar
Shane Kerr committed
565
        isc_throw(DataSourceError, "No SQLite database file specified");
566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622
    }
    return (SUCCESS);
}

namespace {
// This is a helper class to initialize a Sqlite3 DB safely.  An object of
// this class encapsulates all temporary resources that are necessary for
// the initialization, and release them in the destructor.  Once everything
// is properly initialized, the move() method moves the allocated resources
// to the main object in an exception free manner.  This way, the main code
// for the initialization can be exception safe, and can provide the strong
// exception guarantee.
class Sqlite3Initializer {
public:
    ~Sqlite3Initializer() {
        if (params_.q_zone_ != NULL) {
            sqlite3_finalize(params_.q_zone_);
        }
        if (params_.q_record_ != NULL) {
            sqlite3_finalize(params_.q_record_);
        }
        if (params_.q_addrs_ != NULL) {
            sqlite3_finalize(params_.q_addrs_);
        }
        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_);
        }
        if (params_.q_previous_ != NULL) {
            sqlite3_finalize(params_.q_previous_);
        }
        if (params_.q_nsec3_ != NULL) {
            sqlite3_finalize(params_.q_nsec3_);
        }
        if (params_.q_prevnsec3_ != NULL) {
            sqlite3_finalize(params_.q_prevnsec3_);
        }
        if (params_.db_ != NULL) {
            sqlite3_close(params_.db_);
        }
    }
    void move(Sqlite3Parameters* dst) {
        *dst = params_;
        params_ = Sqlite3Parameters(); // clear everything
    }
    Sqlite3Parameters params_;
};

sqlite3_stmt*
prepare(sqlite3* const db, const char* const statement) {
    sqlite3_stmt* prepared = NULL;
    if (sqlite3_prepare_v2(db, statement, -1, &prepared, NULL) != SQLITE_OK) { 
Shane Kerr's avatar
Shane Kerr committed
623
        isc_throw(Sqlite3Error, "Could not prepare SQLite statement: " <<
624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663
                  statement);
    }
    return (prepared);
}

void
checkAndSetupSchema(Sqlite3Initializer* initializer) {
    sqlite3* const db = initializer->params_.db_;

    sqlite3_stmt* prepared = NULL;
    if (sqlite3_prepare_v2(db, "SELECT version FROM schema_version", -1,
                           &prepared, NULL) == SQLITE_OK &&
        sqlite3_step(prepared) == SQLITE_ROW) {
        initializer->params_.version_ = sqlite3_column_int(prepared, 0);
        sqlite3_finalize(prepared);
    } else {
        if (prepared != NULL) {
            sqlite3_finalize(prepared);
        }
        for (int i = 0; SCHEMA_LIST[i] != NULL; ++i) {
            if (sqlite3_exec(db, SCHEMA_LIST[i], NULL, NULL, NULL) !=
                SQLITE_OK) {
                isc_throw(Sqlite3Error,
                          "Failed to set up schema " << SCHEMA_LIST[i]);
            }
        }
    }

    initializer->params_.q_zone_ = prepare(db, q_zone_str);
    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);
    initializer->params_.q_prevnsec3_ = prepare(db, q_prevnsec3_str);
}
}

664 665 666 667
//
//  Open the database.
//
void
JINMEI Tatuya's avatar
JINMEI Tatuya committed
668
Sqlite3DataSrc::open(const string& name) {
669
    if (dbparameters->db_ != NULL) {
Shane Kerr's avatar
Shane Kerr committed
670
        isc_throw(DataSourceError, "Duplicate SQLite open with " << name);
JINMEI Tatuya's avatar
JINMEI Tatuya committed
671
    }
672 673 674 675

    Sqlite3Initializer initializer;

    if (sqlite3_open(name.c_str(), &initializer.params_.db_) != 0) {
Shane Kerr's avatar
Shane Kerr committed
676
        isc_throw(Sqlite3Error, "Cannot open SQLite database file: " << name);
677
    }
678

679 680
    checkAndSetupSchema(&initializer);
    initializer.move(dbparameters);
681 682
}

683 684 685
//
//  Close the database.
//
686
DataSrc::Result
JINMEI Tatuya's avatar
JINMEI Tatuya committed
687
Sqlite3DataSrc::close(void) {
688
    if (dbparameters->db_ == NULL) {
689
        isc_throw(DataSourceError,
Shane Kerr's avatar
Shane Kerr committed
690
                  "SQLite data source is being closed before open");
691 692
    }

693 694 695
    // XXX: sqlite3_finalize() could fail.  What should we do in that case?
    sqlite3_finalize(dbparameters->q_zone_);
    dbparameters->q_zone_ = NULL;
696

697 698
    sqlite3_finalize(dbparameters->q_record_);
    dbparameters->q_record_ = NULL;
699

700 701
    sqlite3_finalize(dbparameters->q_addrs_);
    dbparameters->q_addrs_ = NULL;
702

703 704
    sqlite3_finalize(dbparameters->q_referral_);
    dbparameters->q_referral_ = NULL;
705

706 707
    sqlite3_finalize(dbparameters->q_any_);
    dbparameters->q_any_ = NULL;
708

709 710
    sqlite3_finalize(dbparameters->q_count_);
    dbparameters->q_count_ = NULL;
711

712 713
    sqlite3_finalize(dbparameters->q_previous_);
    dbparameters->q_previous_ = NULL;
714

715 716
    sqlite3_finalize(dbparameters->q_prevnsec3_);
    dbparameters->q_prevnsec3_ = NULL;
Evan Hunt's avatar
Evan Hunt committed
717

718 719
    sqlite3_finalize(dbparameters->q_nsec3_);
    dbparameters->q_nsec3_ = NULL;
Evan Hunt's avatar
Evan Hunt committed
720

721 722
    sqlite3_close(dbparameters->db_);
    dbparameters->db_ = NULL;
723 724 725 726 727 728

    return (SUCCESS);
}

}
}