/*
 * This file is part of PowerDNS or dnsdist.
 * Copyright -- PowerDNS.COM B.V. and its contributors
 * originally authored by Maik Zumstrull
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * In addition, for the avoidance of any doubt, permission is granted to
 * link this program with OpenSSL and to (re)distribute the binaries
 * produced as the result of such linking.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string>
#include <stdexcept>

#include "pdns/namespaces.hh"

#include "pdns/dns.hh"
#include "pdns/dnsbackend.hh"
#include "oraclebackend.hh"
#include "pdns/logger.hh"
#include "pdns/arguments.hh"
#include "pdns/lock.hh"

#include <oci.h>

static const char *basicQueryKey = "PDNS_Basic_Query";
static const char *basicQueryDefaultAuthSQL =
  "SELECT fqdn, ttl, type, content, zone_id, last_change, auth "
  "FROM Records "
  "WHERE type = :type AND fqdn = lower(:name)";

static const char *basicQueryDefaultSQL = "SELECT fqdn, ttl, type, content, zone_id, last_change "
  "FROM Records "
  "WHERE type = :type AND fqdn = lower(:name)";

static const char *basicIdQueryKey = "PDNS_Basic_Id_Query";
static const char *basicIdQueryDefaultAuthSQL =
  "SELECT fqdn, ttl, type, content, zone_id, last_change, auth "
  "FROM Records "
  "WHERE type = :type AND fqdn = lower(:name) AND zone_id = :zoneid";

static const char *basicIdQueryDefaultSQL = 
  "SELECT fqdn, ttl, type, content, zone_id, last_change "
  "FROM Records "
  "WHERE type = :type AND fqdn = lower(:name) AND zone_id = :zoneid";

static const char *anyQueryKey = "PDNS_ANY_Query";
static const char *anyQueryDefaultAuthSQL =
  "SELECT fqdn, ttl, type, content, zone_id, last_change, auth "
  "FROM Records "
  "WHERE fqdn = lower(:name)"
  "  AND type IS NOT NULL "
  "ORDER BY type";

static const char *anyQueryDefaultSQL =
  "SELECT fqdn, ttl, type, content, zone_id, last_change "
  "FROM Records "
  "WHERE fqdn = lower(:name)"
  "  AND type IS NOT NULL "
  "ORDER BY type";

static const char *anyIdQueryKey = "PDNS_ANY_Id_Query";
static const char *anyIdQueryDefaultAuthSQL =
  "SELECT fqdn, ttl, type, content, zone_id, last_change, auth "
  "FROM Records "
  "WHERE fqdn = lower(:name)"
  "  AND zone_id = :zoneid"
  "  AND type IS NOT NULL "
  "ORDER BY type";

static const char *anyIdQueryDefaultSQL =
  "SELECT fqdn, ttl, type, content, zone_id, last_change "
  "FROM Records "
  "WHERE fqdn = lower(:name)"
  "  AND zone_id = :zoneid"
  "  AND type IS NOT NULL "
  "ORDER BY type";


static const char *listQueryKey = "PDNS_List_Query";
static const char *listQueryDefaultAuthSQL =
  "SELECT fqdn, ttl, type, content, zone_id, last_change, auth "
  "FROM Records "
  "WHERE zone_id = :zoneid"
  "  AND type IS NOT NULL "
  "ORDER BY fqdn, type";

static const char *listQueryDefaultSQL =
  "SELECT fqdn, ttl, type, content, zone_id, last_change "
  "FROM Records "
  "WHERE zone_id = :zoneid"
  "  AND type IS NOT NULL "
  "ORDER BY fqdn, type";


static const char *zoneInfoQueryKey = "PDNS_Zone_Info_Query";
static const char *zoneInfoQueryDefaultSQL =
  "SELECT id, name, type, last_check, serial, notified_serial "
  "FROM Zones "
  "WHERE name = lower(:name)";

static const char *alsoNotifyQueryKey = "PDNS_Also_Notify_Query";
static const char *alsoNotifyQueryDefaultSQL =
  "SELECT an.hostaddr "
  "FROM Zones z JOIN ZoneAlsoNotify an ON z.id = an.zone_id "
  "WHERE z.name = lower(:name)";

static const char *zoneMastersQueryKey = "PDNS_Zone_Masters_Query";
static const char *zoneMastersQueryDefaultSQL =
  "SELECT master "
  "FROM Zonemasters "
  "WHERE zone_id = :zoneid";

static const char *isZoneMasterQueryKey = "PDNS_Is_Zone_Master_Query";
static const char *isZoneMasterQueryDefaultSQL =
  "SELECT zm.master "
  "FROM Zones z JOIN Zonemasters zm ON z.id = zm.zone_id "
  "WHERE z.name = lower(:name) AND zm.master = :master";

static const char *deleteZoneQueryKey = "PDNS_Delete_Zone_Query";
static const char *deleteZoneQueryDefaultSQL =
  "DELETE FROM Records WHERE zone_id = :zoneid";

static const char *zoneSetLastCheckQueryKey = "PDNS_Zone_Set_Last_Check_Query";
static const char *zoneSetLastCheckQueryDefaultSQL =
  "UPDATE Zones SET last_check = :lastcheck WHERE id = :zoneid";

static const char *zoneSetNotifiedSerialQueryKey = "PDNS_Zone_Set_NSerial_Query";
static const char *zoneSetNotifiedSerialQueryDefaultSQL =
  "UPDATE Zones SET notified_serial = :serial WHERE id = :zoneid";

static const char *insertRecordQueryKey = "PDNS_Insert_Record_Query";
static const char *insertRecordQueryDefaultSQL =
  "INSERT INTO Records (id, fqdn, zone_id, ttl, type, content) "
  "VALUES (records_id_seq.NEXTVAL, lower(:name), :zoneid, :ttl, :type, :content)";

static const char *finalizeAXFRQueryKey = "PDNS_Finalize_AXFR";
static const char *finalizeAXFRQueryDefaultSQL =
  "DECLARE\n"
  "  zone_id INTEGER := :zoneid;\n"
  "BEGIN\n"
  "  NULL;\n"
  "END;";

static const char *unfreshZonesQueryKey = "PDNS_Unfresh_Zones_Query";
static const char *unfreshZonesQueryDefaultSQL =
  "SELECT z.id, z.name, z.last_check, z.serial, zm.master "
  "FROM Zones z JOIN Zonemasters zm ON z.id = zm.zone_id "
  "WHERE z.type = 'SLAVE' "
  "  AND (z.last_check IS NULL OR z.last_check + z.refresh < :ts)"
  "ORDER BY z.id";

static const char *updatedMastersQueryKey = "PDNS_Updated_Masters_Query";
static const char *updatedMastersQueryDefaultSQL =
  "SELECT id, name, serial, notified_serial "
  "FROM Zones "
  "WHERE type = 'MASTER' "
  "AND (notified_serial IS NULL OR notified_serial < serial)";

static const char *acceptSupernotificationQueryKey = "PDNS_Accept_Supernotification_Query";
static const char *acceptSupernotificationQueryDefaultSQL =
  "SELECT name "
  "FROM Supermasters "
  "WHERE ip = :ip AND nameserver = lower(:ns)";

static const char *insertSlaveQueryKey = "PDNS_Insert_Slave_Query";
static const char *insertSlaveQueryDefaultSQL =
  "INSERT INTO Zones (id, name, type) "
  "VALUES (zones_id_seq.NEXTVAL, lower(:zone), 'SLAVE') "
  "RETURNING id INTO :zoneid";

static const char *insertMasterQueryKey = "PDNS_Insert_Master_Query";
static const char *insertMasterQueryDefaultSQL =
  "INSERT INTO Zonemasters (zone_id, master) "
  "VALUES (:zoneid, :ip)";

static const char *prevNextNameQueryKey = "PDNS_Prev_Next_Name_Query";
static const char *prevNextNameQueryDefaultSQL =
  "BEGIN\n"
  "  get_canonical_prev_next(:zoneid, :name, :prev, :next);\n"
  "END;";

static const char *prevNextHashQueryKey = "PDNS_Prev_Next_Hash_Query";
static const char *prevNextHashQueryDefaultSQL =
  "BEGIN\n"
  "  get_hashed_prev_next(:zoneid, :hash, :unhashed, :prev, :next);\n"
  "END;";

static const char *getAllZoneMetadataQueryKey = "PDNS_Get_All_Zone_Metadata";
static const char *getAllZoneMetadataQueryDefaultSQL =
  "SELECT md.meta_type, md.meta_content "
  "FROM Zones z JOIN ZoneMetadata md ON z.id = md.zone_id "
  "WHERE z.name = lower(:name) "
  "ORDER BY md.meta_ind";

static const char *getZoneMetadataQueryKey = "PDNS_Get_Zone_Metadata";
static const char *getZoneMetadataQueryDefaultSQL =
  "SELECT md.meta_content "
  "FROM Zones z JOIN ZoneMetadata md ON z.id = md.zone_id "
  "WHERE z.name = lower(:name) AND md.meta_type = :kind "
  "ORDER BY md.meta_ind";

static const char *delZoneMetadataQueryKey = "PDNS_Del_Zone_Metadata";
static const char *delZoneMetadataQueryDefaultSQL =
  "DELETE FROM ZoneMetadata md "
  "WHERE zone_id = (SELECT id FROM Zones z WHERE z.name = lower(:name)) "
  "  AND md.meta_type = :kind";

static const char *setZoneMetadataQueryKey = "PDNS_Set_Zone_Metadata";
static const char *setZoneMetadataQueryDefaultSQL =
  "INSERT INTO ZoneMetadata (zone_id, meta_type, meta_ind, meta_content) "
  "VALUES ("
  "  (SELECT id FROM Zones WHERE name = lower(:name)),"
  "  :kind, :i, :content"
  ")";

static const char *getTSIGKeyQueryKey = "PDNS_Get_TSIG_Key";
static const char *getTSIGKeyQueryDefaultSQL =
  "SELECT algorithm, secret "
  "FROM TSIGKeys "
  "WHERE name = :name";

static const char *delTSIGKeyQueryKey = "PDNS_Del_TSIG_Key";
static const char *delTSIGKeyQueryDefaultSQL =
  "DELETE FROM TSIGKeys "
  "WHERE name = :name";

static const char *setTSIGKeyQueryKey = "PDNS_Set_TSIG_Key";
static const char *setTSIGKeyQueryDefaultSQL =
  "INSERT INTO TSIGKeys (name, algorithm, secret) "
  "VALUES (:name, :algorithm, :secret)";

static const char *getTSIGKeysQueryKey = "PDNS_Get_TSIG_Keys";
static const char *getTSIGKeysQueryDefaultSQL =
  "SELECT name, algorithm, secret "
  "FROM TSIGKeys";

static const char *getZoneKeysQueryKey = "PDNS_Get_Zone_Keys";
static const char *getZoneKeysQueryDefaultSQL =
  "SELECT k.id, k.flags, k.active, k.keydata "
  "FROM ZoneDNSKeys k JOIN Zones z ON z.id = k.zone_id "
  "WHERE z.name = lower(:name)";

static const char *delZoneKeyQueryKey = "PDNS_Del_Zone_Key";
static const char *delZoneKeyQueryDefaultSQL =
  "DELETE FROM ZoneDNSKeys WHERE id = :keyid";

static const char *addZoneKeyQueryKey = "PDNS_Add_Zone_Key";
static const char *addZoneKeyQueryDefaultSQL =
  "INSERT INTO ZoneDNSKeys (id, zone_id, flags, active, keydata) "
  "VALUES ("
  "  zonednskeys_id_seq.NEXTVAL,"
  "  (SELECT id FROM Zones WHERE name = lower(:name)),"
  "  :flags,"
  "  :active,"
  "  :content"
  ") RETURNING id INTO :keyid";

static const char *setZoneKeyStateQueryKey = "PDNS_Set_Zone_Key_State";
static const char *setZoneKeyStateQueryDefaultSQL =
  "UPDATE ZoneDNSKeys SET active = :active WHERE id = :keyid";


static void
string_to_cbuf (char *buf, const string& s, size_t bufsize)
{
  if (s.size() >= bufsize) {
    throw std::overflow_error("OracleBackend: string does not fit into char buffer");
  }
  strncpy(buf, s.c_str(), bufsize);
}

static void
DNSName_to_cbuf (char *buf, const DNSName& n, size_t bufsize)
{
  string s = toLower(n.toStringNoDot());
  if (s.size() >= bufsize) {
    throw std::overflow_error("OracleBackend: DNSName does not fit into char buffer");
  }
  strncpy(buf, s.c_str(), bufsize);
}

OracleBackend::OracleBackend (const string &suffix, OCIEnv *envh,
                              char *poolname)
{
  setArgPrefix(string("oracle") + suffix);
  sword err;

  // Initialize everything in a known state
  oraenv = envh;
  oraerr = NULL;
  pooledSvcCtx = NULL;
  masterAuthHandle = NULL;
  masterSvcCtx = NULL;
  curStmtHandle = NULL;
  openTransactionZoneID = -1;

  try
  {
    d_dnssecQueries = mustDo("dnssec");
  }
  catch (ArgException e)
  {
    d_dnssecQueries = false;
  }

  // Process configuration options
  string_to_cbuf(myServerName, getArg("nameserver-name"), sizeof(myServerName));

  if (d_dnssecQueries) {
    basicQuerySQL = getArg("basic-query-auth");
    basicIdQuerySQL = getArg("basic-id-query-auth");
    anyQuerySQL = getArg("any-query-auth");
    anyIdQuerySQL = getArg("any-id-query-auth");
    listQuerySQL = getArg("list-query-auth");
  } else {
    basicQuerySQL = getArg("basic-query");
    basicIdQuerySQL = getArg("basic-id-query");
    anyQuerySQL = getArg("any-query");
    anyIdQuerySQL = getArg("any-id-query");
    listQuerySQL = getArg("list-query");
  }

  zoneInfoQuerySQL = getArg("zone-info-query");
  alsoNotifyQuerySQL = getArg("also-notify-query");
  zoneMastersQuerySQL = getArg("zone-masters-query");
  isZoneMasterQuerySQL = getArg("is-zone-master-query");
  deleteZoneQuerySQL = getArg("delete-zone-query");
  zoneSetLastCheckQuerySQL = getArg("zone-set-last-check-query");
  insertRecordQuerySQL = getArg("insert-record-query");
  finalizeAXFRQuerySQL = getArg("finalize-axfr-query");
  unfreshZonesQuerySQL = getArg("unfresh-zones-query");
  updatedMastersQuerySQL = getArg("updated-masters-query");
  acceptSupernotificationQuerySQL = getArg("accept-supernotification-query");
  insertSlaveQuerySQL = getArg("insert-slave-query");
  insertMasterQuerySQL = getArg("insert-master-query");
  zoneSetNotifiedSerialQuerySQL = getArg("zone-set-notified-serial-query");
  prevNextNameQuerySQL = getArg("prev-next-name-query");
  prevNextHashQuerySQL = getArg("prev-next-hash-query");
  getAllZoneMetadataQuerySQL = getArg("get-all-zone-metadata-query");
  getZoneMetadataQuerySQL = getArg("get-zone-metadata-query");
  delZoneMetadataQuerySQL = getArg("del-zone-metadata-query");
  setZoneMetadataQuerySQL = getArg("set-zone-metadata-query");
  getTSIGKeyQuerySQL = getArg("get-tsig-key-query");
  delTSIGKeyQuerySQL = getArg("del-tsig-key-query");
  setTSIGKeyQuerySQL = getArg("set-tsig-key-query");
  getTSIGKeysQuerySQL = getArg("get-tsig-keys-query");
  getZoneKeysQuerySQL = getArg("get-zone-keys-query");
  delZoneKeyQuerySQL = getArg("del-zone-key-query");
  addZoneKeyQuerySQL = getArg("add-zone-key-query");
  setZoneKeyStateQuerySQL = getArg("set-zone-key-state-query");

  // Allocate an error handle
  err = OCIHandleAlloc(oraenv, (void**) &oraerr,
                       OCI_HTYPE_ERROR, 0, NULL);
  if (err == OCI_ERROR) {
    throw OracleException("OCIHandleAlloc");
  }

  // Logon to the database
  err = OCISessionGet(oraenv, oraerr, &pooledSvcCtx, NULL, (OraText*) poolname, strlen(poolname), NULL, 0, NULL, NULL, NULL, OCI_SESSGET_SPOOL);

  if (err == OCI_ERROR) {
    throw OracleException("Opening Oracle session", oraerr);
  }
}

void
OracleBackend::openMasterConnection ()
{
  sword err;

  if (masterSvcCtx == NULL) {
    err = OCIHandleAlloc(oraenv, (void**) &masterAuthHandle, OCI_HTYPE_AUTHINFO, 0, NULL);
    if (err == OCI_ERROR) {
      throw OracleException("openMasterConnection: allocating auth handle");
    }

    string database = getArg("master-database");
    string username = getArg("master-username");
    string password = getArg("master-password");

    err = OCIAttrSet(masterAuthHandle, OCI_HTYPE_AUTHINFO, (void*)username.c_str(), username.size(), OCI_ATTR_USERNAME, oraerr);
    if (err == OCI_ERROR) {
      throw OracleException("openMasterConnection: setting username");
    }

    err = OCIAttrSet(masterAuthHandle, OCI_HTYPE_AUTHINFO, (void*)password.c_str(), password.size(), OCI_ATTR_PASSWORD, oraerr);
    if (err == OCI_ERROR) {
      throw OracleException("openMasterConnection: setting password");
    }

    err = OCISessionGet(oraenv, oraerr, &masterSvcCtx, masterAuthHandle,
        (OraText*)database.c_str(), database.size(),
        NULL, 0, NULL, NULL, NULL, OCI_SESSGET_STMTCACHE);
    if (err == OCI_ERROR) {
      throw OracleException("openMasterConnection OCISessionGet");
    }
  }
}

OracleBackend::~OracleBackend ()
{
  Cleanup();
}

void
OracleBackend::lookup (const QType &qtype, const DNSName& qname,
                       DNSPacket *p, int zoneId)
{
  sword rc;

  if (qtype.getCode() != QType::ANY) {
    if (zoneId < 0) {
      if (curStmtHandle != NULL) throw OracleException("Invalid state");
      curStmtHandle = prepare_query(pooledSvcCtx, basicQuerySQL, basicQueryKey);
      curStmtKey = basicQueryKey;
      define_fwd_query(curStmtHandle);
      bind_str_failokay(curStmtHandle, ":nsname", myServerName, sizeof(myServerName));
      bind_str(curStmtHandle, ":name", mQueryName, sizeof(mQueryName));
      bind_str(curStmtHandle, ":type", mQueryType, sizeof(mQueryType));
    } else {
      if (curStmtHandle != NULL) throw OracleException("Invalid state");
      curStmtHandle = prepare_query(pooledSvcCtx, basicIdQuerySQL, basicIdQueryKey);
      curStmtKey = basicIdQueryKey;
      define_fwd_query(curStmtHandle);
      bind_str_failokay(curStmtHandle, ":nsname", myServerName, sizeof(myServerName));
      bind_str(curStmtHandle, ":name", mQueryName, sizeof(mQueryName));
      bind_str(curStmtHandle, ":type", mQueryType, sizeof(mQueryType));
      bind_int(curStmtHandle, ":zoneid", &mQueryZoneId);
    }
  } else {
    if (zoneId < 0) {
      if (curStmtHandle != NULL) throw OracleException("Invalid state");
      curStmtHandle = prepare_query(pooledSvcCtx, anyQuerySQL, anyQueryKey);
      curStmtKey = anyQueryKey;
      define_fwd_query(curStmtHandle);
      bind_str_failokay(curStmtHandle, ":nsname", myServerName, sizeof(myServerName));
      bind_str(curStmtHandle, ":name", mQueryName, sizeof(mQueryName));
    } else {
      if (curStmtHandle != NULL) throw OracleException("Invalid state");
      curStmtHandle = prepare_query(pooledSvcCtx, anyIdQuerySQL, anyIdQueryKey);
      curStmtKey = anyIdQueryKey;
      define_fwd_query(curStmtHandle);
      bind_str_failokay(curStmtHandle, ":nsname", myServerName, sizeof(myServerName));
      bind_str(curStmtHandle, ":name", mQueryName, sizeof(mQueryName));
      bind_int(curStmtHandle, ":zoneid", &mQueryZoneId);
    }
  }

  DNSName_to_cbuf(mQueryName, qname, sizeof(mQueryName));
  string_to_cbuf(mQueryType, qtype.getName(), sizeof(mQueryType));
  mQueryZoneId = zoneId;

  rc = OCIStmtExecute(pooledSvcCtx, curStmtHandle, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle Lookup", oraerr);
  }

  if (rc == OCI_NO_DATA) {
    release_query(curStmtHandle, curStmtKey);
    curStmtHandle = NULL;
  }
}

bool
OracleBackend::getBeforeAndAfterNames (
  uint32_t zoneId, const DNSName& zone,
  const DNSName& name, DNSName& before, DNSName& after)
{
  if(!d_dnssecQueries)
    return -1;

  sword rc;
  OCIStmt *stmt;

  (void)zone;

  stmt = prepare_query(pooledSvcCtx, prevNextNameQuerySQL, prevNextNameQueryKey);
  bind_str_failokay(stmt, ":nsname", myServerName, sizeof(myServerName));
  bind_str(stmt, ":name", mQueryName, sizeof(mQueryName));
  bind_str_ind(stmt, ":prev", mResultPrevName, sizeof(mResultPrevName), &mResultPrevNameInd);
  bind_str_ind(stmt, ":next", mResultNextName, sizeof(mResultNextName), &mResultNextNameInd);
  bind_uint32(stmt, ":zoneid", &zoneId);
  DNSName_to_cbuf(mQueryName, name, sizeof(mQueryName));
  mResultPrevNameInd = -1;
  mResultNextNameInd = -1;

  rc = OCIStmtExecute(pooledSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException(
      "Oracle getBeforeAndAfterNames", oraerr
    );
  }

  check_indicator(mResultPrevNameInd, false);
  check_indicator(mResultNextNameInd, false);

  before = DNSName(mResultPrevName);
  after = DNSName(mResultNextName);

  release_query(stmt, prevNextNameQueryKey);
  return true;
}

bool
OracleBackend::getBeforeAndAfterNamesAbsolute(uint32_t zoneId,
  const DNSName& name, DNSName& unhashed, DNSName& before, DNSName& after)
{
  if(!d_dnssecQueries)
    return -1; 

  sword rc;
  OCIStmt *stmt;

  stmt = prepare_query(pooledSvcCtx, prevNextHashQuerySQL, prevNextHashQueryKey);
  bind_str_failokay(stmt, ":nsname", myServerName, sizeof(myServerName));
  bind_str(stmt, ":hash", mQueryName, sizeof(mQueryName));
  bind_str_ind(stmt, ":unhashed", mResultName, sizeof(mResultName), &mResultNameInd);
  bind_str_ind(stmt, ":prev", mResultPrevName, sizeof(mResultPrevName), &mResultPrevNameInd);
  bind_str_ind(stmt, ":next", mResultNextName, sizeof(mResultNextName), &mResultNextNameInd);
  bind_uint32(stmt, ":zoneid", &zoneId);
  string_to_cbuf(mQueryName, name.labelReverse().toString(" ", false)), sizeof(mQueryName));
  mResultNameInd = -1;
  mResultPrevNameInd = -1;
  mResultNextNameInd = -1;

  rc = OCIStmtExecute(pooledSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException(
      "Oracle getBeforeAndAfterNamesAbsolute", oraerr
    );
  }

  check_indicator(mResultNameInd, false);
  check_indicator(mResultPrevNameInd, false);
  check_indicator(mResultNextNameInd, false);

  unhashed = DNSName(mResultName);
  before = DNSName(boost::replace_all_copy(mResultPrevName," ",".")).labelReverse();
  after = DNSName(boost::replace_all_copy(mResultNextName," ",".")).labelReverse();

  release_query(stmt, prevNextHashQueryKey);
  return true;
}

vector<string>
OracleBackend::getDomainMasters (const DNSName& domain, int zoneId)
{
  sword rc;
  OCIStmt *stmt;

  (void)domain;

  vector<string> masters;
  char master[512];
  sb2 master_ind;

  openMasterConnection();

  stmt = prepare_query(masterSvcCtx, zoneMastersQuerySQL, zoneMastersQueryKey);
  bind_str_failokay(stmt, ":nsname", myServerName, sizeof(myServerName));
  bind_int(stmt, ":zoneid", &mQueryZoneId);

  mQueryZoneId = zoneId;
  define_output_str(stmt, 1, &master_ind, master, sizeof(master));

  rc = OCIStmtExecute(masterSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle getDomainMasters", oraerr);
  }

  while (rc != OCI_NO_DATA) {
    check_indicator(master_ind, false);

    masters.push_back(master);

    rc = OCIStmtFetch2(stmt, oraerr, 1, OCI_FETCH_NEXT, 0, OCI_DEFAULT);

    if (rc == OCI_ERROR) {
      throw OracleException(
        "OracleBackend, fetching next zone master", oraerr
      );
    }
  }

  release_query(stmt, zoneMastersQueryKey);

  return masters;
}

bool
OracleBackend::isMaster (const DNSName& domain, const string &master)
{
  sword rc;
  OCIStmt *stmt;

  openMasterConnection();

  stmt = prepare_query(masterSvcCtx, isZoneMasterQuerySQL, isZoneMasterQueryKey);

  DNSName_to_cbuf(mQueryZone, domain, sizeof(mQueryZone));
  string_to_cbuf(mQueryName, master, sizeof(mQueryName));

  char res_master[512];
  sb2 res_master_ind;

  bind_str_failokay(stmt, ":nsname", myServerName, sizeof(myServerName));
  bind_str(stmt, ":name", mQueryZone, sizeof(mQueryZone));
  bind_str(stmt, ":master", mQueryName, sizeof(mQueryName));
  define_output_str(stmt, 1, &res_master_ind, res_master, sizeof(res_master));

  rc = OCIStmtExecute(masterSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle isMaster", oraerr);
  }

  release_query(stmt, isZoneMasterQueryKey);

  if (rc != OCI_NO_DATA) {
    check_indicator(res_master_ind, false);
    return true;
  }

  return false;
}

bool
OracleBackend::getDomainInfo (const DNSName& domain, DomainInfo &di)
{
  sword rc;
  OCIStmt *stmt;

  int zone_id;
  sb2 zone_id_ind;
  int last_check;
  sb2 last_check_ind;
  uint32_t serial;
  sb2 serial_ind;
  uint32_t notified_serial;
  sb2 notified_serial_ind;

  openMasterConnection();

  stmt = prepare_query(masterSvcCtx, zoneInfoQuerySQL, zoneInfoQueryKey);
  bind_str_failokay(stmt, ":nsname", myServerName, sizeof(myServerName));
  define_output_int(stmt, 1, &zone_id_ind, &zone_id);
  define_output_str(stmt, 2, &mResultNameInd, mResultName, sizeof(mResultName));
  define_output_str(stmt, 3, &mResultTypeInd, mResultType, sizeof(mResultType));
  define_output_int(stmt, 4, &last_check_ind, &last_check);
  define_output_uint32(stmt, 5, &serial_ind, &serial);
  define_output_uint32(stmt, 6, &notified_serial_ind, &notified_serial);

  DNSName_to_cbuf(mQueryZone, domain, sizeof(mQueryZone));
  bind_str(stmt, ":name", mQueryZone, sizeof(mQueryZone));

  rc = OCIStmtExecute(masterSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle getDomainInfo", oraerr);
  }

  if (rc == OCI_NO_DATA) {
    release_query(stmt, zoneInfoQueryKey);
    return false;
  }

  check_indicator(zone_id_ind, false);
  check_indicator(mResultNameInd, false);
  check_indicator(serial_ind, true);

  if (zone_id < 0) throw std::underflow_error("OracleBackend: Zone ID < 0 when writing into uint32_t");

  di.id = zone_id;
  di.zone = DNSName(mResultName);
  di.serial = serial;
  di.backend = this;

  check_indicator(mResultTypeInd, false);
  if (strcasecmp(mResultType, "NATIVE") == 0) {
    di.kind = DomainInfo::Native;
  } else if (strcasecmp(mResultType, "MASTER") == 0) {
    di.kind = DomainInfo::Master;
    check_indicator(notified_serial_ind, false);
    di.notified_serial = notified_serial;
  } else if (strcasecmp(mResultType, "SLAVE") == 0) {
    di.kind = DomainInfo::Slave;
    check_indicator(last_check_ind, true);
    di.last_check = last_check;
    di.masters = getDomainMasters(DNSName(mResultName), zone_id);
  } else {
    throw OracleException("Unknown zone type in Oracle backend");
  }

  di.kind = DomainInfo::Native;

  release_query(stmt, zoneInfoQueryKey);
  return true;
}

void OracleBackend::alsoNotifies(const DNSName& domain, set<string> *addrs)
{
  sword rc;
  OCIStmt *stmt;

  char hostaddr[512];
  sb2 hostaddr_ind;

  openMasterConnection();

  stmt = prepare_query(masterSvcCtx, alsoNotifyQuerySQL, alsoNotifyQueryKey);
  bind_str_failokay(stmt, ":nsname", myServerName, sizeof(myServerName));
  bind_str(stmt, ":name", mQueryZone, sizeof(mQueryZone));

  DNSName_to_cbuf(mQueryZone, domain, sizeof(mQueryZone));

  define_output_str(stmt, 1, &hostaddr_ind, hostaddr, sizeof(hostaddr));

  rc = OCIStmtExecute(masterSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle alsoNotifies", oraerr);
  }

  while (rc != OCI_NO_DATA) {
    check_indicator(hostaddr_ind, false);

    addrs->insert(hostaddr);

    rc = OCIStmtFetch2(stmt, oraerr, 1, OCI_FETCH_NEXT, 0, OCI_DEFAULT);

    if (rc == OCI_ERROR) {
      throw OracleException(
        "OracleBackend alsoNotifies fetch", oraerr
      );
    }
  }

  release_query(stmt, alsoNotifyQueryKey);
}

void
OracleBackend::getUnfreshSlaveInfos (vector<DomainInfo>* domains)
{
  sword rc;
  OCIStmt *stmt;

  struct timeval now;
  gettimeofday(&now, NULL);
  mQueryTimestamp = now.tv_sec;

  int       last_check;
  sb2       last_check_ind;
  uint32_t  serial;
  sb2       serial_ind;
  char      master[512];
  sb2       master_ind;

  openMasterConnection();

  stmt = prepare_query(masterSvcCtx, unfreshZonesQuerySQL, unfreshZonesQueryKey);
  bind_str_failokay(stmt, ":nsname", myServerName, sizeof(myServerName));
  bind_int(stmt, ":ts", &mQueryTimestamp);
  define_output_int(stmt, 1, &mResultZoneIdInd, &mResultZoneId);
  define_output_str(stmt, 2, &mResultNameInd, mResultName, sizeof(mResultName));
  define_output_int(stmt, 3, &last_check_ind, &last_check);
  define_output_uint32(stmt, 4, &serial_ind, &serial);
  define_output_str(stmt, 5, &master_ind, master, sizeof(master));

  rc = OCIStmtExecute(masterSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle getUnfreshSlaveInfos", oraerr);
  }

  while (rc != OCI_NO_DATA) {
    check_indicator(mResultZoneIdInd, false);
    check_indicator(mResultNameInd, false);
    check_indicator(serial_ind, true);
    check_indicator(last_check_ind, true);
    int zoneId = mResultZoneId;

    if (mResultZoneId < 0) throw std::underflow_error("OracleBackend: Zone ID < 0 when writing into uint32_t");

    DomainInfo di;
    di.id = mResultZoneId;
    di.zone = DNSName(mResultName);
    di.last_check = last_check;
    di.kind = DomainInfo::Slave;
    di.backend = this;
    if (serial_ind == 0) {
      di.serial = serial;
    }

    while (rc != OCI_NO_DATA && zoneId == mResultZoneId) {
      check_indicator(master_ind, false);
      di.masters.push_back(master);

      rc = OCIStmtFetch2(stmt, oraerr, 1, OCI_FETCH_NEXT, 0, OCI_DEFAULT);

      if (rc == OCI_ERROR) {
        throw OracleException(
          "OracleBackend, fetching next unfresh slave master", oraerr
        );
      }

      check_indicator(mResultZoneIdInd, false);
    }

    domains->push_back(di);
  }

  release_query(stmt, unfreshZonesQueryKey);
}

void
OracleBackend::getUpdatedMasters (vector<DomainInfo>* domains)
{
  sword rc;
  OCIStmt *stmt;

  uint32_t  serial;
  sb2       serial_ind;
  uint32_t  notified_serial;
  sb2       notified_serial_ind;

  openMasterConnection();

  stmt = prepare_query(masterSvcCtx, updatedMastersQuerySQL, updatedMastersQueryKey);
  bind_str_failokay(stmt, ":nsname", myServerName, sizeof(myServerName));
  define_output_int(stmt, 1, &mResultZoneIdInd, &mResultZoneId);
  define_output_str(stmt, 2, &mResultNameInd, mResultName, sizeof(mResultName));
  define_output_uint32(stmt, 3, &serial_ind, &serial);
  define_output_uint32(stmt, 4, &notified_serial_ind, &notified_serial);

  rc = OCIStmtExecute(masterSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle getUpdatedMasters", oraerr);
  }

  while (rc != OCI_NO_DATA) {
    check_indicator(mResultZoneIdInd, false);
    check_indicator(mResultNameInd, false);
    check_indicator(serial_ind, false);
    check_indicator(notified_serial_ind, true);

    if (mResultZoneId < 0) throw std::underflow_error("OracleBackend: Zone ID < 0 when writing into uint32_t");

    DomainInfo di;
    di.id = mResultZoneId;
    di.zone = DNSName(mResultName);
    di.serial = serial;
    di.notified_serial = notified_serial;
    di.kind = DomainInfo::Master;
    di.backend = this;

    domains->push_back(di);

    rc = OCIStmtFetch2(stmt, oraerr, 1, OCI_FETCH_NEXT, 0, OCI_DEFAULT);

    if (rc == OCI_ERROR) {
      throw OracleException(
        "OracleBackend, fetching next updated master", oraerr
      );
    }
  }

  release_query(stmt, updatedMastersQueryKey);
}

void
OracleBackend::setFresh (uint32_t zoneId)
{
  sword rc;
  OCIStmt *stmt;

  mQueryZoneId = zoneId;

  struct timeval now;
  gettimeofday(&now, NULL);
  mQueryTimestamp = now.tv_sec;

  openMasterConnection();

  rc = OCITransStart(masterSvcCtx, oraerr, 60, OCI_TRANS_NEW);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle setFresh BEGIN", oraerr);
  }

  stmt = prepare_query(masterSvcCtx, zoneSetLastCheckQuerySQL, zoneSetLastCheckQueryKey);
  bind_str_failokay(stmt, ":nsname", myServerName, sizeof(myServerName));
  bind_int(stmt, ":zoneid", &mQueryZoneId);
  bind_int(stmt, ":lastcheck", &mQueryTimestamp);

  rc = OCIStmtExecute(masterSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle setFresh", oraerr);
  }

  release_query(stmt, zoneSetLastCheckQueryKey);

  rc = OCITransCommit(masterSvcCtx, oraerr, OCI_DEFAULT);

  if (rc) {
    throw OracleException("Oracle setFresh COMMIT", oraerr);
  }
}

void
OracleBackend::setNotified (uint32_t zoneId, uint32_t serial)
{
  sword rc;
  OCIStmt *stmt;

  openMasterConnection();

  rc = OCITransStart(masterSvcCtx, oraerr, 60, OCI_TRANS_NEW);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle setNotified BEGIN", oraerr);
  }

  stmt = prepare_query(masterSvcCtx, zoneSetNotifiedSerialQuerySQL, zoneSetNotifiedSerialQueryKey);
  bind_str_failokay(stmt, ":nsname", myServerName, sizeof(myServerName));
  bind_uint32(stmt, ":serial", &serial);
  bind_uint32(stmt, ":zoneid", &zoneId);

  rc = OCIStmtExecute(masterSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle setNotified", oraerr);
  }

  release_query(stmt, zoneSetNotifiedSerialQueryKey);

  rc = OCITransCommit(masterSvcCtx, oraerr, OCI_DEFAULT);

  if (rc) {
    throw OracleException("Oracle setNotified COMMIT", oraerr);
  }
}

bool
OracleBackend::list (const DNSName& domain, int zoneId, bool include_disabled)
{
  sword rc;

  // This is only for backends that cannot lookup by zoneId,
  // we can discard
  (void)domain;

  if (curStmtHandle != NULL) throw OracleException("Invalid state");
  curStmtHandle = prepare_query(pooledSvcCtx, listQuerySQL, listQueryKey);
  curStmtKey = listQueryKey;
  define_fwd_query(curStmtHandle);
  bind_str_failokay(curStmtHandle, ":nsname", myServerName, sizeof(myServerName));
  bind_int(curStmtHandle, ":zoneid", &mQueryZoneId);

  mQueryZoneId = zoneId;

  rc = OCIStmtExecute(pooledSvcCtx, curStmtHandle, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle List", oraerr);
  }

  if (rc == OCI_SUCCESS || rc == OCI_SUCCESS_WITH_INFO) {
    return true;
  }

  if (rc == OCI_NO_DATA) {
    release_query(curStmtHandle, curStmtKey);
    curStmtHandle = NULL;
  }

  return false;
}

bool OracleBackend::get (DNSResourceRecord &rr)
{
  sword rc;

  if (curStmtHandle == NULL) {
    return false;
  }

  check_indicator(mResultNameInd, false);
  check_indicator(mResultTTLInd, false);
  check_indicator(mResultTypeInd, true);
  check_indicator(mResultContentInd, true);
  check_indicator(mResultZoneIdInd, false);
  check_indicator(mResultLastChangeInd, false);
  if (d_dnssecQueries)
    check_indicator(mResultIsAuthInd, false);

  rr.qname = DNSName(mResultName);
  rr.ttl = mResultTTL;
  rr.qtype = mResultType;
  rr.content = mResultContent;
  rr.domain_id = mResultZoneId;
  rr.last_modified = mResultLastChange;
  if (d_dnssecQueries)
    rr.auth = mResultIsAuth > 0;
  else
    rr.auth = 1;

  rc = OCIStmtFetch2(curStmtHandle, oraerr, 1, OCI_FETCH_NEXT, 0, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException("OracleBackend, fetching next row", oraerr);
  }

  if (rc == OCI_NO_DATA) {
    release_query(curStmtHandle, curStmtKey);
    curStmtHandle = NULL;
  }

  return true;
}

bool
OracleBackend::startTransaction (const DNSName& domain, int zoneId)
{
  sword rc;
  OCIStmt *stmt;

  (void)domain;

  openMasterConnection();

  rc = OCITransStart(masterSvcCtx, oraerr, 60, OCI_TRANS_NEW);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle startTransaction", oraerr);
  }

  if (zoneId >= 0) {
    if (openTransactionZoneID >= 0) {
      throw OracleException("Attempt to start AXFR during AXFR");
    }

    mQueryZoneId = openTransactionZoneID = zoneId;

    stmt = prepare_query(masterSvcCtx, deleteZoneQuerySQL, deleteZoneQueryKey);
    bind_str_failokay(stmt, ":nsname", myServerName, sizeof(myServerName));
    bind_int(stmt, ":zoneid", &mQueryZoneId);

    rc = OCIStmtExecute(masterSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

    if (rc == OCI_ERROR) {
      throw OracleException("Oracle startTransaction deleteZone", oraerr);
    }

    release_query(stmt, deleteZoneQueryKey);
  }

  return true;
}

bool
OracleBackend::feedRecord (const DNSResourceRecord &rr, const DNSName ordername)
{
  sword rc;
  OCIStmt *stmt;

  uint32_t ttl;

  stmt = prepare_query(masterSvcCtx, insertRecordQuerySQL, insertRecordQueryKey);
  bind_str_failokay(stmt, ":nsname", myServerName, sizeof(myServerName));
  bind_int(stmt, ":zoneid", &mQueryZoneId);
  bind_str(stmt, ":name", mQueryName, sizeof(mQueryName));
  bind_str(stmt, ":type", mQueryType, sizeof(mQueryType));
  bind_uint32(stmt, ":ttl", &ttl);
  bind_str(stmt, ":content", mQueryContent, sizeof(mQueryContent));

  mQueryZoneId = rr.domain_id;
  DNSName_to_cbuf(mQueryName, rr.qname, sizeof(mQueryName));
  ttl = rr.ttl;
  string_to_cbuf(mQueryType, rr.qtype.getName(), sizeof(mQueryType));
  string_to_cbuf(mQueryContent, rr.content, sizeof(mQueryContent));

  rc = OCIStmtExecute(masterSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle feedRecord", oraerr);
  }

  release_query(stmt, insertRecordQueryKey);

  return true;
}

bool
OracleBackend::commitTransaction ()
{
  sword rc;
  OCIStmt *stmt;

  if (openTransactionZoneID >= 0) {
    stmt = prepare_query(masterSvcCtx, finalizeAXFRQuerySQL, finalizeAXFRQueryKey);
    bind_str_failokay(stmt, ":nsname", myServerName, sizeof(myServerName));
    bind_int(stmt, ":zoneid", &openTransactionZoneID);

    rc = OCIStmtExecute(masterSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

    if (rc == OCI_ERROR) {
      throw OracleException("Oracle commitTransaction finalizeAXFR", oraerr);
    }

    release_query(stmt, finalizeAXFRQueryKey);

    openTransactionZoneID = -1;
  }

  rc = OCITransCommit(masterSvcCtx, oraerr, OCI_DEFAULT);

  if (rc) {
    throw OracleException("Oracle commitTransaction", oraerr);
  }

  return true;
}

bool
OracleBackend::abortTransaction ()
{
  sword err;

  err = OCITransRollback(masterSvcCtx, oraerr, OCI_DEFAULT);

  if (err) {
    throw OracleException("Oracle abortTransaction", oraerr);
  }

  return true;
}

bool
OracleBackend::superMasterBackend (const string &ip, const DNSName& domain,
                                   const vector<DNSResourceRecord> &nsset,
                                   string *nameserver, string *account,
                                   DNSBackend **backend)
{
  sword rc;
  OCIStmt *stmt;

  bool result = false;

  (void)domain;

  string_to_cbuf(mQueryAddr, ip, sizeof(mQueryAddr));

  openMasterConnection();

  stmt = prepare_query(masterSvcCtx, acceptSupernotificationQuerySQL, acceptSupernotificationQueryKey);
  define_output_str(stmt, 1, &mResultNameInd, mResultName, sizeof(mResultName));
  bind_str_failokay(stmt, ":nsname", myServerName, sizeof(myServerName));
  bind_str(stmt, ":ns", mQueryName, sizeof(mQueryName));
  bind_str(stmt, ":ip", mQueryAddr, sizeof(mQueryAddr));

  for (vector<DNSResourceRecord>::const_iterator i=nsset.begin(); i != nsset.end(); ++i) {
    string_to_cbuf(mQueryName, i->content, sizeof(mQueryName));

    rc = OCIStmtExecute(masterSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

    if (rc == OCI_ERROR) {
      throw OracleException("Oracle superMasterBackend", oraerr);
    }

    if (rc != OCI_NO_DATA) {
      *account = mResultName;
      *backend = this;
      result = true;
      break;
    }
  }

  release_query(stmt, acceptSupernotificationQueryKey);

  return result;
}

bool
OracleBackend::createSlaveDomain(const string &ip, const DNSName& domain,
                                 const string &nameserver, const string &account)
{
  sword rc;
  OCIStmt *insertSlaveQueryHandle;
  OCIStmt *insertMasterQueryHandle;

  DNSName_to_cbuf(mQueryZone, domain, sizeof(mQueryZone));

  openMasterConnection();

  rc = OCITransStart(masterSvcCtx, oraerr, 60, OCI_TRANS_NEW);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle createSlaveDomain BEGIN", oraerr);
  }

  insertSlaveQueryHandle = prepare_query(masterSvcCtx, insertSlaveQuerySQL, insertSlaveQueryKey);
  bind_str_failokay(insertSlaveQueryHandle, ":nsname", myServerName, sizeof(myServerName));
  bind_int(insertSlaveQueryHandle, ":zoneid", &mQueryZoneId);
  bind_str(insertSlaveQueryHandle, ":zone", mQueryZone, sizeof(mQueryZone));

  insertMasterQueryHandle = prepare_query(masterSvcCtx, insertMasterQuerySQL, insertMasterQueryKey);
  bind_str_failokay(insertMasterQueryHandle, ":nsname", myServerName, sizeof(myServerName));
  bind_int(insertMasterQueryHandle, ":zoneid", &mQueryZoneId);
  bind_str(insertMasterQueryHandle, ":ip", mQueryAddr, sizeof(mQueryAddr));

  rc = OCIStmtExecute(masterSvcCtx, insertSlaveQueryHandle, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException(
      "Oracle createSlaveDomain insertSlave", oraerr);
  }

  string_to_cbuf(mQueryAddr, ip, sizeof(mQueryAddr));

  rc = OCIStmtExecute(masterSvcCtx, insertMasterQueryHandle, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException(
      "Oracle createSlaveDomain insertMaster", oraerr);
  }

  release_query(insertSlaveQueryHandle, insertSlaveQueryKey);
  release_query(insertMasterQueryHandle, insertMasterQueryKey);

  rc = OCITransCommit(masterSvcCtx, oraerr, OCI_DEFAULT);

  if (rc) {
    throw OracleException("Oracle createSlaveDomain COMMIT", oraerr);
  }

  return true;
}

bool
OracleBackend::getAllDomainMetadata (const DNSName& name, std::map<string, vector<string> >& meta)
{
  DomainInfo di;
  if (getDomainInfo(name, di) == false) return false;

  sword rc;
  OCIStmt *stmt;

  stmt = prepare_query(pooledSvcCtx, getAllZoneMetadataQuerySQL, getAllZoneMetadataQueryKey);
  bind_str_failokay(stmt, ":nsname", myServerName, sizeof(myServerName));
  bind_str(stmt, ":name", mQueryName, sizeof(mQueryName));

  define_output_str(stmt, 1, &mResultTypeInd, mResultType, sizeof(mResultType));
  define_output_str(stmt, 2, &mResultContentInd, mResultContent, sizeof(mResultContent));

  DNSName_to_cbuf(mQueryName, name, sizeof(mQueryName));

  rc = OCIStmtExecute(pooledSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  while (rc != OCI_NO_DATA) {
    if (rc == OCI_ERROR) {
      throw OracleException("Oracle getAllDomainMetadata", oraerr);
    }
    check_indicator(mResultTypeInd, true);
    check_indicator(mResultContentInd, true);

    string kind = mResultType;
    string content = mResultContent;
    if (!isDnssecDomainMetadata(content))
      meta[kind].push_back(content);

    rc = OCIStmtFetch2(stmt, oraerr, 1, OCI_FETCH_NEXT, 0, OCI_DEFAULT);
  }

  release_query(stmt, getAllZoneMetadataQueryKey);
  return true;
}

bool
OracleBackend::getDomainMetadata (const DNSName& name, const string& kind,
                                  vector<string>& meta)
{
  if(!d_dnssecQueries && isDnssecDomainMetadata(kind))
    return -1;
  DomainInfo di;
  if (getDomainInfo(name, di) == false) return false;

  sword rc;
  OCIStmt *stmt;

  stmt = prepare_query(pooledSvcCtx, getZoneMetadataQuerySQL, getZoneMetadataQueryKey);
  bind_str_failokay(stmt, ":nsname", myServerName, sizeof(myServerName));
  bind_str(stmt, ":name", mQueryName, sizeof(mQueryName));
  bind_str(stmt, ":kind", mQueryType, sizeof(mQueryType));
  define_output_str(stmt, 1, &mResultContentInd, mResultContent, sizeof(mResultContent));

  DNSName_to_cbuf(mQueryName, name, sizeof(mQueryName));
  string_to_cbuf(mQueryType, kind, sizeof(mQueryType));

  rc = OCIStmtExecute(pooledSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  while (rc != OCI_NO_DATA) {
    if (rc == OCI_ERROR) {
      throw OracleException("Oracle getDomainMetadata", oraerr);
    }

    check_indicator(mResultContentInd, true);

    string content = mResultContent;
    meta.push_back(content);

    rc = OCIStmtFetch2(stmt, oraerr, 1, OCI_FETCH_NEXT, 0, OCI_DEFAULT);
  }

  release_query(stmt, getZoneMetadataQueryKey);
  return true;
}

bool
OracleBackend::setDomainMetadata(const DNSName& name, const string& kind,
                                 const vector<string>& meta)
{
  if(!d_dnssecQueries && isDnssecDomainMetadata(kind))
    return -1;
  DomainInfo di;
  if (getDomainInfo(name, di) == false) return false;

  sword rc;
  OCIStmt *stmt;

  openMasterConnection();

  rc = OCITransStart(masterSvcCtx, oraerr, 60, OCI_TRANS_NEW);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle setDomainMetadata BEGIN", oraerr);
  }

  DNSName_to_cbuf(mQueryName, name, sizeof(mQueryName));
  string_to_cbuf(mQueryType, kind, sizeof(mQueryType));

  stmt = prepare_query(masterSvcCtx, delZoneMetadataQuerySQL, delZoneMetadataQueryKey);
  bind_str(stmt, ":name", mQueryName, sizeof(mQueryName));
  bind_str(stmt, ":kind", mQueryType, sizeof(mQueryType));

  rc = OCIStmtExecute(masterSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle setDomainMetadata DELETE", oraerr);
  }

  release_query(stmt, delZoneMetadataQueryKey);

  stmt = prepare_query(masterSvcCtx, setZoneMetadataQuerySQL, setZoneMetadataQueryKey);

  int i = 0;

  bind_str(stmt, ":name", mQueryName, sizeof(mQueryName));
  bind_str(stmt, ":kind", mQueryType, sizeof(mQueryType));
  bind_int(stmt, ":i", &i);
  bind_str(stmt, ":content", mQueryContent, sizeof(mQueryContent));

  for (vector<string>::const_iterator it = meta.begin(); it != meta.end(); ++it) {
    string_to_cbuf(mQueryContent, *it, sizeof(mQueryContent));
    rc = OCIStmtExecute(masterSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);
    if (rc == OCI_ERROR) {
      throw OracleException("Oracle setDomainMetadata INSERT", oraerr);
    }
    i++;
  }

  release_query(stmt, setZoneMetadataQueryKey);

  rc = OCITransCommit(masterSvcCtx, oraerr, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle setDomainMetadata COMMIT", oraerr);
  }

  return true;
}

bool
OracleBackend::getTSIGKey (const DNSName& name, DNSName* algorithm, string* content)
{
  sword rc;
  OCIStmt *stmt;

  stmt = prepare_query(pooledSvcCtx, getTSIGKeyQuerySQL, getTSIGKeyQueryKey);
  DNSName_to_cbuf(mQueryName, name, sizeof(mQueryName));
  bind_str(stmt, ":name", mQueryName, sizeof(mQueryName));

  define_output_str(stmt, 1, &mResultTypeInd, mResultType, sizeof(mResultType));
  define_output_str(stmt, 2, &mResultContentInd, mResultContent, sizeof(mResultContent));

  rc = OCIStmtExecute(pooledSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  content->clear();
  while (rc != OCI_NO_DATA) {

    if (rc == OCI_ERROR) {
      throw OracleException("Oracle getTSIGKey", oraerr);
    }

    check_indicator(mResultTypeInd, false);
    check_indicator(mResultContentInd, false);

    if(algorithm->empty() || *algorithm==DNSName(mResultType)) {
      *algorithm = DNSName(mResultType);
      *content = mResultContent;
    }

    rc = OCIStmtFetch2(stmt, oraerr, 1, OCI_FETCH_NEXT, 0, OCI_DEFAULT);
  }

  release_query(stmt, getTSIGKeyQueryKey);
  return !content->empty();
}

bool
OracleBackend::delTSIGKey(const DNSName& name)
{
  sword rc;
  OCIStmt *stmt;

  openMasterConnection();
  rc = OCITransStart(masterSvcCtx, oraerr, 60, OCI_TRANS_NEW);

  stmt = prepare_query(masterSvcCtx, delTSIGKeyQuerySQL, delTSIGKeyQueryKey);
  DNSName_to_cbuf(mQueryName, name, sizeof(mQueryName));

  bind_str(stmt, ":name", mQueryName, sizeof(mQueryName));

  rc = OCIStmtExecute(masterSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle delTSIGKey", oraerr);
  }

  release_query(stmt, setTSIGKeyQueryKey);

  rc = OCITransCommit(masterSvcCtx, oraerr, OCI_DEFAULT);
  if (rc == OCI_ERROR) {
    throw OracleException("Oracle delTSIGKey COMMIT", oraerr);
  }
  return true;
}

bool
OracleBackend::setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content)
{
  sword rc;
  OCIStmt *stmt;

  openMasterConnection();

  rc = OCITransStart(masterSvcCtx, oraerr, 60, OCI_TRANS_NEW);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle setTSIGKey BEGIN", oraerr);
  }

  stmt = prepare_query(masterSvcCtx, delTSIGKeyQuerySQL, delTSIGKeyQueryKey);
  DNSName_to_cbuf(mQueryName, name, sizeof(mQueryName));

  bind_str(stmt, ":name", mQueryName, sizeof(mQueryName));

  rc = OCIStmtExecute(masterSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle setTSIGKey DELETE", oraerr);
  }

  release_query(stmt, delTSIGKeyQueryKey);

  stmt = prepare_query(masterSvcCtx, setTSIGKeyQuerySQL, setTSIGKeyQueryKey);
  DNSName_to_cbuf(mQueryName, name, sizeof(mQueryName));
  DNSName_to_cbuf(mQueryType, algorithm, sizeof(mQueryType));
  string_to_cbuf(mQueryContent, content, sizeof(mQueryContent));

  bind_str(stmt, ":name", mQueryName, sizeof(mQueryName));
  bind_str(stmt, ":algorithm", mQueryType, sizeof(mQueryType));
  bind_str(stmt, ":secret", mQueryContent, sizeof(mQueryContent));

  rc = OCIStmtExecute(masterSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle setTSIGKey INSERT", oraerr);
  }

  release_query(stmt, setTSIGKeyQueryKey);

  rc = OCITransCommit(masterSvcCtx, oraerr, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle setTSIGKey COMMIT", oraerr);
  }

  return true;
}

bool
OracleBackend::getTSIGKeys(std::vector< struct TSIGKey > &keys)
{
  sword rc;
  OCIStmt *stmt;

  stmt = prepare_query(pooledSvcCtx, getTSIGKeysQuerySQL, getTSIGKeysQueryKey);
  define_output_str(stmt, 1, &mResultNameInd, mResultName, sizeof(mResultName));
  define_output_str(stmt, 2, &mResultTypeInd, mResultType, sizeof(mResultType));
  define_output_str(stmt, 3, &mResultContentInd, mResultContent, sizeof(mResultContent));

  rc = OCIStmtExecute(pooledSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  while (rc != OCI_NO_DATA) {
    if (rc == OCI_ERROR) {
      throw OracleException("Oracle getDomainMetadata", oraerr);
    }

    check_indicator(mResultNameInd, true);
    check_indicator(mResultTypeInd, true);
    check_indicator(mResultContentInd, true);

    struct TSIGKey key;

    key.name = DNSName(mResultName);
    key.algorithm = DNSName(mResultType);
    key.key = mResultContent;
    keys.push_back(key);

    rc = OCIStmtFetch2(stmt, oraerr, 1, OCI_FETCH_NEXT, 0, OCI_DEFAULT);
  }

  release_query(stmt, getTSIGKeyQueryKey);
  return true;
}

bool
OracleBackend::getDomainKeys (const DNSName& name, vector<KeyData>& keys)
{
  if(!d_dnssecQueries)
    return -1;
  DomainInfo di;
  if (getDomainInfo(name, di) == false) return false;

  sword rc;
  OCIStmt *stmt;

  stmt = prepare_query(pooledSvcCtx, getZoneKeysQuerySQL, getZoneKeysQueryKey);
  bind_str(stmt, ":name", mQueryName, sizeof(mQueryName));

  DNSName_to_cbuf(mQueryName, name, sizeof(mQueryName));

  sb2 key_id_ind = 0;
  unsigned int key_id = 0;
  sb2 key_flags_ind = 0;
  uint16_t key_flags = 0;
  sb2 key_active_ind = 0;
  int key_active = 0;

  define_output_uint(stmt, 1, &key_id_ind, &key_id);
  define_output_uint16(stmt, 2, &key_flags_ind, &key_flags);
  define_output_int(stmt, 3, &key_active_ind, &key_active);
  define_output_str(stmt, 4, &mResultContentInd, mResultContent, sizeof(mResultContent));

  rc = OCIStmtExecute(pooledSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  while (rc != OCI_NO_DATA) {
    if (rc == OCI_ERROR) {
      throw OracleException("Oracle getDomainKeys", oraerr);
    }

    check_indicator(key_id_ind, false);
    check_indicator(key_flags_ind, false);
    check_indicator(key_active_ind, false);
    check_indicator(mResultContentInd, false);

    KeyData kd;
    kd.id = key_id;
    kd.flags = key_flags;
    kd.active = key_active;
    kd.content = mResultContent;
    keys.push_back(kd);

    rc = OCIStmtFetch2(stmt, oraerr, 1, OCI_FETCH_NEXT, 0, OCI_DEFAULT);
  }

  release_query(stmt, getZoneKeysQueryKey);
  return true;
}

bool
OracleBackend::removeDomainKey (const DNSName& name, unsigned int id)
{
  if(!d_dnssecQueries)
    return -1;
  DomainInfo di;
  if (getDomainInfo(name, di) == false) return false;

  sword rc;
  OCIStmt *stmt;

  openMasterConnection();

  rc = OCITransStart(masterSvcCtx, oraerr, 60, OCI_TRANS_NEW);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle removeDomainKey BEGIN", oraerr);
  }

  stmt = prepare_query(masterSvcCtx, delZoneKeyQuerySQL, delZoneKeyQueryKey);
  bind_uint(stmt, ":keyid", &id);

  rc = OCIStmtExecute(masterSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle removeDomainKey DELETE", oraerr);
  }

  release_query(stmt, delZoneKeyQueryKey);

  rc = OCITransCommit(masterSvcCtx, oraerr, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle removeDomainKey COMMIT", oraerr);
  }

  return true;
}

bool
OracleBackend::addDomainKey (const DNSName& name, const KeyData& key, int64_t& id)
{
  if(!d_dnssecQueries)
    return false;
  DomainInfo di;
  if (getDomainInfo(name, di) == false) return false;

  sword rc;
  OCIStmt *stmt;

  int key_id = -1;
  uint16_t key_flags = key.flags;
  int key_active = key.active;

  openMasterConnection();

  rc = OCITransStart(masterSvcCtx, oraerr, 60, OCI_TRANS_NEW);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle addDomainKey BEGIN", oraerr);
  }

  DNSName_to_cbuf(mQueryName, name, sizeof(mQueryName));
  string_to_cbuf(mQueryContent, key.content, sizeof(mQueryContent));

  stmt = prepare_query(masterSvcCtx, addZoneKeyQuerySQL, addZoneKeyQueryKey);

  bind_int(stmt, ":keyid", &key_id);
  bind_str(stmt, ":name", mQueryName, sizeof(mQueryName));
  bind_uint16(stmt, ":flags", &key_flags);
  bind_int(stmt, ":active", &key_active);
  bind_str(stmt, ":content", mQueryContent, sizeof(mQueryContent));

  rc = OCIStmtExecute(masterSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle addDomainKey INSERT", oraerr);
  }

  release_query(stmt, addZoneKeyQueryKey);

  rc = OCITransCommit(masterSvcCtx, oraerr, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle addDomainKey COMMIT", oraerr);
  }

  id = key_id;
  return key_id >= 0;
}

bool
OracleBackend::setDomainKeyState (const DNSName& name, unsigned int id, int active)
{
  if(!d_dnssecQueries)
    return -1;
  DomainInfo di;
  if (getDomainInfo(name, di) == false) return false;

  sword rc;
  OCIStmt *stmt;

  openMasterConnection();

  stmt = prepare_query(masterSvcCtx, setZoneKeyStateQuerySQL, setZoneKeyStateQueryKey);
  bind_uint(stmt, ":keyid", &id);
  bind_int(stmt, ":active", &active);

  rc = OCITransStart(masterSvcCtx, oraerr, 60, OCI_TRANS_NEW);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle setDomainKeyState BEGIN", oraerr);
  }

  rc = OCIStmtExecute(masterSvcCtx, stmt, oraerr, 1, 0, NULL, NULL, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle setDomainKeyState UPDATE", oraerr);
  }

  rc = OCITransCommit(masterSvcCtx, oraerr, OCI_DEFAULT);

  if (rc == OCI_ERROR) {
    throw OracleException("Oracle setDomainKeyState COMMIT", oraerr);
  }

  release_query(stmt, setZoneKeyStateQueryKey);
  return true;
}

bool
OracleBackend::activateDomainKey (const DNSName& name, unsigned int id)
{
  return setDomainKeyState(name, id, 1);
}

bool
OracleBackend::deactivateDomainKey (const DNSName& name, unsigned int id)
{
  return setDomainKeyState(name, id, 0);
}

void
OracleBackend::Cleanup ()
{
  sword err;

  if (masterSvcCtx != NULL) {
    err = OCITransRollback(masterSvcCtx, oraerr, OCI_DEFAULT);
    // No error check, we don't care if ROLLBACK failed
    err = OCISessionRelease(masterSvcCtx, oraerr, NULL, 0, OCI_DEFAULT);
    if (err == OCI_ERROR) {
      throw OracleException("Oracle cleanup, OCISessionRelease (master)", oraerr);
    }
    masterSvcCtx = NULL;
    OCIHandleFree(masterAuthHandle, OCI_HTYPE_AUTHINFO);
    masterAuthHandle = NULL;
  }

  if (pooledSvcCtx != NULL) {
    err = OCITransRollback(pooledSvcCtx, oraerr, OCI_DEFAULT);
    // No error check, we don't care if ROLLBACK failed
    err = OCISessionRelease(pooledSvcCtx, oraerr, NULL, 0, OCI_DEFAULT);
    if (err == OCI_ERROR) {
      throw OracleException("Oracle cleanup, OCISessionRelease (pooled)", oraerr);
    }
    pooledSvcCtx = NULL;
  }

  if (oraerr != NULL) {
    OCIHandleFree(oraerr, OCI_HTYPE_ERROR);
    oraerr = NULL;
  }
}

OCIStmt*
OracleBackend::prepare_query (OCISvcCtx *orasvc, string& code, const char *key)
{
  sword err;

  OCIStmt *handle = NULL;

  err = OCIStmtPrepare2(orasvc, &handle, oraerr, (OraText*) code.c_str(), code.length(), (OraText*) key, strlen(key), OCI_NTV_SYNTAX, OCI_DEFAULT);

  if (err == OCI_ERROR) {
    throw OracleException("Preparing Oracle statement", oraerr);
  }

  return handle;
}

void
OracleBackend::release_query (OCIStmt *stmt, const char *key)
{
  sword err;

  err = OCIStmtRelease(stmt, oraerr, (OraText*)key, strlen(key), OCI_DEFAULT);

  if (err == OCI_ERROR) {
    throw OracleException("Releasing Oracle statement", oraerr);
  }
}

void
OracleBackend::define_output_str (OCIStmt *s, ub4 pos, sb2 *ind,
                                  char *buf, sb4 buflen)
{
  sword err;
  OCIDefine *handle = NULL;

  err = OCIDefineByPos(s, &handle, oraerr, pos, buf, buflen, SQLT_STR,
                       ind, NULL, NULL, OCI_DEFAULT);

  if (err == OCI_ERROR) {
    throw OracleException("Defining output for Oracle statement", oraerr);
  }
}

void
OracleBackend::define_output_int (OCIStmt *s, ub4 pos, sb2 *ind, int *buf)
{
  sword err;
  OCIDefine *handle = NULL;

  err = OCIDefineByPos(s, &handle, oraerr, pos, buf, sizeof(int),
                       SQLT_INT, ind, NULL, NULL, OCI_DEFAULT);

  if (err == OCI_ERROR) {
    throw OracleException("Defining output for Oracle statement", oraerr);
  }
}

void
OracleBackend::define_output_uint (OCIStmt *s, ub4 pos, sb2 *ind, unsigned int *buf)
{
  sword err;
  OCIDefine *handle = NULL;

  err = OCIDefineByPos(s, &handle, oraerr, pos, buf, sizeof(unsigned int),
                       SQLT_UIN, ind, NULL, NULL, OCI_DEFAULT);

  if (err == OCI_ERROR) {
    throw OracleException("Defining output for Oracle statement", oraerr);
  }
}

void
OracleBackend::define_output_uint16 (OCIStmt *s, ub4 pos, sb2 *ind,
                                     uint16_t *buf)
{
  sword err;
  OCIDefine *handle = NULL;

  err = OCIDefineByPos(s, &handle, oraerr, pos, buf, sizeof(uint16_t),
                       SQLT_UIN, ind, NULL, NULL, OCI_DEFAULT);

  if (err == OCI_ERROR) {
    throw OracleException("Defining output for Oracle statement", oraerr);
  }
}

void
OracleBackend::define_output_uint32 (OCIStmt *s, ub4 pos, sb2 *ind,
                                     uint32_t *buf)
{
  sword err;
  OCIDefine *handle = NULL;

  err = OCIDefineByPos(s, &handle, oraerr, pos, buf, sizeof(uint32_t),
                       SQLT_UIN, ind, NULL, NULL, OCI_DEFAULT);

  if (err == OCI_ERROR) {
    throw OracleException("Defining output for Oracle statement", oraerr);
  }
}

void
OracleBackend::check_indicator (sb2 ind, bool null_okay)
{
  if ((!null_okay) && (ind == -1)) {
    throw OracleException("Received NULL where a value was expected");
  }

  if ((ind < -1) || (ind > 0)) {
    throw OracleException("Return value truncated");
  }
}

void
OracleBackend::define_fwd_query (OCIStmt *s)
{
  const ub4 n = 100;
  sword err = OCIAttrSet(s, OCI_HTYPE_STMT, (void*) &n, sizeof(ub4),
                         OCI_ATTR_PREFETCH_ROWS, oraerr);

  if (err == OCI_ERROR) {
    throw OracleException("Activating row prefetching", oraerr);
  }

  define_output_str(s, 1, &mResultNameInd,
                    mResultName, sizeof(mResultName));
  define_output_uint32(s, 2, &mResultTTLInd, &mResultTTL);
  define_output_str(s, 3, &mResultTypeInd,
                    mResultType, sizeof(mResultType));
  define_output_str(s, 4, &mResultContentInd,
                    mResultContent, sizeof(mResultContent));
  define_output_int(s, 5, &mResultZoneIdInd, &mResultZoneId);
  define_output_int(s, 6, &mResultLastChangeInd, &mResultLastChange);
  if (d_dnssecQueries)
    define_output_int(s, 7, &mResultIsAuthInd, &mResultIsAuth);
}

void
OracleBackend::bind_str (OCIStmt *s, const char *name, char *buf, sb4 buflen)
{
  sword err;
  OCIBind *handle = NULL;

  err = OCIBindByName(s, &handle, oraerr,
                      (OraText*) name, strlen(name),
                      buf, buflen, SQLT_STR,
                      NULL, NULL, NULL, 0, NULL,
                      OCI_DEFAULT);

  if (err == OCI_ERROR) {
    string msg;
    msg.append("Oracle bind_str (\"");
    msg.append(name);
    msg.append("\")");
    throw OracleException(msg, oraerr);
  }
}

void
OracleBackend::bind_str_failokay (OCIStmt *s, const char *name,
                                  char *buf, sb4 buflen)
{
  sword err;
  OCIBind *handle = NULL;

  err = OCIBindByName(s, &handle, oraerr,
                      (OraText*) name, strlen(name),
                      buf, buflen, SQLT_STR,
                      NULL, NULL, NULL, 0, NULL,
                      OCI_DEFAULT);

  (void)err;
}

void
OracleBackend::bind_str_ind (OCIStmt *s, const char *name,
                             char *buf, sb4 buflen, sb2 *ind)
{
  sword err;
  OCIBind *handle = NULL;

  err = OCIBindByName(s, &handle, oraerr,
                      (OraText*) name, strlen(name),
                      buf, buflen, SQLT_STR,
                      ind, NULL, NULL, 0, NULL,
                      OCI_DEFAULT);

  if (err == OCI_ERROR) {
    string msg;
    msg.append("Oracle bind_str_ind (\"");
    msg.append(name);
    msg.append("\")");
    throw OracleException(msg, oraerr);
  }
}

void
OracleBackend::bind_int (OCIStmt *s, const char *name, int *buf)
{
  sword err;
  OCIBind *handle = NULL;

  err = OCIBindByName(s, &handle, oraerr,
                      (OraText*) name, strlen(name),
                      buf, sizeof(int), SQLT_INT,
                      NULL, NULL, NULL, 0, NULL,
                      OCI_DEFAULT);

  if (err == OCI_ERROR) {
    string msg;
    msg.append("Oracle bind_int (\"");
    msg.append(name);
    msg.append("\")");
    throw OracleException(msg, oraerr);
  }
}

void
OracleBackend::bind_uint (OCIStmt *s, const char *name, unsigned int *buf)
{
  sword err;
  OCIBind *handle = NULL;

  err = OCIBindByName(s, &handle, oraerr,
                      (OraText*) name, strlen(name),
                      buf, sizeof(unsigned int), SQLT_UIN,
                      NULL, NULL, NULL, 0, NULL,
                      OCI_DEFAULT);

  if (err == OCI_ERROR) {
    string msg;
    msg.append("Oracle bind_uint (\"");
    msg.append(name);
    msg.append("\")");
    throw OracleException(msg, oraerr);
  }
}

void
OracleBackend::bind_uint16 (OCIStmt *s, const char *name, uint16_t *buf)
{
  sword err;
  OCIBind *handle = NULL;

  err = OCIBindByName(s, &handle, oraerr,
                      (OraText*) name, strlen(name),
                      buf, sizeof(uint16_t), SQLT_UIN,
                      NULL, NULL, NULL, 0, NULL,
                      OCI_DEFAULT);

  if (err == OCI_ERROR) {
    string msg;
    msg.append("Oracle bind_uint16 (\"");
    msg.append(name);
    msg.append("\")");
    throw OracleException(msg, oraerr);
  }
}

void
OracleBackend::bind_uint16_ind (OCIStmt *s, const char *name, uint16_t *buf,
                                sb2 *ind)
{
  sword err;
  OCIBind *handle = NULL;

  err = OCIBindByName(s, &handle, oraerr,
                      (OraText*) name, strlen(name),
                      buf, sizeof(uint16_t), SQLT_UIN,
                      ind, NULL, NULL, 0, NULL,
                      OCI_DEFAULT);

  if (err == OCI_ERROR) {
    string msg;
    msg.append("Oracle bind_uint16_ind (\"");
    msg.append(name);
    msg.append("\")");
    throw OracleException(msg, oraerr);
  }
}

void
OracleBackend::bind_uint32 (OCIStmt *s, const char *name, uint32_t *buf)
{
  sword err;
  OCIBind *handle = NULL;

  err = OCIBindByName(s, &handle, oraerr,
                      (OraText*) name, strlen(name),
                      buf, sizeof(uint32_t), SQLT_UIN,
                      NULL, NULL, NULL, 0, NULL,
                      OCI_DEFAULT);

  if (err == OCI_ERROR) {
    string msg;
    msg.append("Oracle bind_uint32 (\"");
    msg.append(name);
    msg.append("\")");
    throw OracleException(msg, oraerr);
  }
}


class OracleFactory : public BackendFactory
{
private:
  pthread_mutex_t factoryLock;
  OCIEnv *oraenv;
  OCIError *oraerr;
  OCISPool *mSessionPoolHandle;
  text *mSessionPoolName;
  ub4 mSessionPoolNameLen;

  void CreateSessionPool ()
  {
    sword err;

    try {
      // set some environment variables
      setenv("ORACLE_HOME", arg()["oracle-home"].c_str(), 1);
      setenv("ORACLE_SID", arg()["oracle-sid"].c_str(), 1);
      setenv("NLS_LANG", arg()["oracle-nls-lang"].c_str(), 1);

      // Initialize and create the environment
      err = OCIEnvCreate(&oraenv, OCI_THREADED, NULL, NULL,
                         NULL, NULL, 0, NULL);
      if (err == OCI_ERROR) {
        throw OracleException("OCIEnvCreate");
      }
      // Allocate an error handle
      err = OCIHandleAlloc(oraenv, (void**) &oraerr,
                           OCI_HTYPE_ERROR, 0, NULL);
      if (err == OCI_ERROR) {
        throw OracleException("OCIHandleAlloc");
      }

      const char *dbname = arg()["oracle-pool-database"].c_str();
      const char *dbuser = arg()["oracle-pool-username"].c_str();
      const char *dbpass = arg()["oracle-pool-password"].c_str();

      ub4 sess_min = arg().asNum("oracle-session-min");
      ub4 sess_max = arg().asNum("oracle-session-max");
      ub4 sess_inc = arg().asNum("oracle-session-inc");
      ub4 get_mode = OCI_SPOOL_ATTRVAL_NOWAIT;

      // Create a session pool
      err = OCIHandleAlloc(oraenv, (void**) &mSessionPoolHandle,
                           OCI_HTYPE_SPOOL, 0, NULL);
      if (err == OCI_ERROR) {
        throw OracleException("OCIHandleAlloc");
      }
      err = OCISessionPoolCreate(oraenv, oraerr,
                                 mSessionPoolHandle,
                                 (OraText **) &mSessionPoolName,
                                 &mSessionPoolNameLen,
                                 (OraText *) dbname, strlen(dbname),
                                 sess_min, sess_max, sess_inc,
                                 (OraText *) dbuser, strlen(dbuser),
                                 (OraText *) dbpass, strlen(dbpass),
                                 OCI_SPC_STMTCACHE | OCI_SPC_HOMOGENEOUS);
      if (err == OCI_ERROR) {
        throw OracleException("Creating Oracle session pool", oraerr);
      }

      // Set session pool NOWAIT
      err = OCIAttrSet(mSessionPoolHandle, OCI_HTYPE_SPOOL, &get_mode, 0, OCI_ATTR_SPOOL_GETMODE, oraerr);
      if (err == OCI_ERROR) {
        throw OracleException("Setting session pool get mode", oraerr);
      }
    } catch (OracleException &theException) {
      L << Logger::Critical << "OracleFactory: "
        << theException.reason << endl;
      Cleanup();
      throw theException;
    }
  }

  void Cleanup ()
  {
    sword err;

    if (mSessionPoolHandle != NULL) {
      try {
        err = OCISessionPoolDestroy(mSessionPoolHandle, oraerr,
                                    OCI_SPD_FORCE);
        OCIHandleFree(mSessionPoolHandle, OCI_HTYPE_SPOOL);
        mSessionPoolHandle = NULL;
        if (err == OCI_ERROR) {
          throw OracleException("OCISessionPoolDestroy", oraerr);
        }
      } catch (OracleException &theException) {
        L << Logger::Error << "Failed to destroy Oracle session pool: "
          << theException.reason << endl;
      }
    }

    if (oraerr != NULL) {
      OCIHandleFree(oraerr, OCI_HTYPE_ERROR);
      oraerr = NULL;
    }

    if (oraenv != NULL) {
      OCIHandleFree(oraenv, OCI_HTYPE_ENV);
      oraenv = NULL;
    }
  }

public:

OracleFactory () : BackendFactory("oracle") {
    pthread_mutex_init(&factoryLock, NULL);
    oraenv = NULL;
    oraerr = NULL;
    mSessionPoolHandle = NULL;
    mSessionPoolName = NULL;
    mSessionPoolNameLen = 0;
  }

  ~OracleFactory () {
    Cleanup();
    pthread_mutex_destroy(&factoryLock);
  }

  void declareArguments (const string & suffix = "") {
    declare(suffix,"home", "Oracle home path", "");
    declare(suffix,"sid", "Oracle sid", "XE");
    declare(suffix,"nls-lang", "Oracle language", "AMERICAN_AMERICA.AL32UTF8");

    declare(suffix, "pool-database", "Database to connect to for the session pool", "powerdns");
    declare(suffix, "pool-username", "Username to connect as for the session pool", "powerdns");
    declare(suffix, "pool-password", "Password to connect with for the session pool", "");
    declare(suffix, "session-min", "Number of sessions to open at startup", "4");
    declare(suffix, "session-inc", "Number of sessions to open when growing", "2");
    declare(suffix, "session-max", "Max number of sessions to have open", "20");
    declare(suffix, "master-database", "Database to connect to for write access", "powerdns");
    declare(suffix, "master-username", "Username to connect as for write access", "powerdns");
    declare(suffix, "master-password", "Password to connect with for write access", "");
    declare(suffix, "dnssec", "Assume DNSSEC Schema is in place", "no");
    declare(suffix, "nameserver-name", "", "");

    declare(suffix, "basic-query", "", basicQueryDefaultSQL);
    declare(suffix, "basic-query-auth", "", basicQueryDefaultAuthSQL);
    declare(suffix, "basic-id-query", "", basicIdQueryDefaultSQL);
    declare(suffix, "basic-id-query-auth", "", basicIdQueryDefaultAuthSQL);
    declare(suffix, "any-query", "", anyQueryDefaultSQL);
    declare(suffix, "any-query-auth", "", anyQueryDefaultAuthSQL);
    declare(suffix, "any-id-query", "", anyIdQueryDefaultSQL);
    declare(suffix, "any-id-query-auth", "", anyIdQueryDefaultAuthSQL);
    declare(suffix, "list-query", "", listQueryDefaultSQL);
    declare(suffix, "list-query-auth", "", listQueryDefaultAuthSQL);
    declare(suffix, "zone-info-query", "", zoneInfoQueryDefaultSQL);
    declare(suffix, "also-notify-query", "", alsoNotifyQueryDefaultSQL);
    declare(suffix, "zone-masters-query", "", zoneMastersQueryDefaultSQL);
    declare(suffix, "is-zone-master-query", "", isZoneMasterQueryDefaultSQL);
    declare(suffix, "delete-zone-query", "", deleteZoneQueryDefaultSQL);
    declare(suffix, "zone-set-last-check-query", "", zoneSetLastCheckQueryDefaultSQL);
    declare(suffix, "zone-set-notified-serial-query", "", zoneSetNotifiedSerialQueryDefaultSQL);
    declare(suffix, "insert-record-query", "", insertRecordQueryDefaultSQL);
    declare(suffix, "finalize-axfr-query", "", finalizeAXFRQueryDefaultSQL);
    declare(suffix, "unfresh-zones-query", "", unfreshZonesQueryDefaultSQL);
    declare(suffix, "updated-masters-query", "", updatedMastersQueryDefaultSQL);
    declare(suffix, "accept-supernotification-query", "", acceptSupernotificationQueryDefaultSQL);
    declare(suffix, "insert-slave-query", "", insertSlaveQueryDefaultSQL);
    declare(suffix, "insert-master-query", "", insertMasterQueryDefaultSQL);
    declare(suffix, "prev-next-name-query", "", prevNextNameQueryDefaultSQL);
    declare(suffix, "prev-next-hash-query", "", prevNextHashQueryDefaultSQL);

    declare(suffix, "get-all-zone-metadata-query", "", getAllZoneMetadataQueryDefaultSQL);
    declare(suffix, "get-zone-metadata-query", "", getZoneMetadataQueryDefaultSQL);
    declare(suffix, "del-zone-metadata-query", "", delZoneMetadataQueryDefaultSQL);
    declare(suffix, "set-zone-metadata-query", "", setZoneMetadataQueryDefaultSQL);

    declare(suffix, "get-tsig-key-query", "", getTSIGKeyQueryDefaultSQL);
    declare(suffix, "del-tsig-key-query", "", delTSIGKeyQueryDefaultSQL);
    declare(suffix, "set-tsig-key-query", "", setTSIGKeyQueryDefaultSQL);
    declare(suffix, "get-tsig-keys-query", "", getTSIGKeysQueryDefaultSQL);

    declare(suffix, "get-zone-keys-query", "", getZoneKeysQueryDefaultSQL);
    declare(suffix, "del-zone-key-query", "", delZoneKeyQueryDefaultSQL);
    declare(suffix, "add-zone-key-query", "", addZoneKeyQueryDefaultSQL);
    declare(suffix, "set-zone-key-state-query", "", setZoneKeyStateQueryDefaultSQL);
  }

  DNSBackend *make (const string & suffix = "") {
    {
      Lock l(&factoryLock);
      if (oraenv == NULL) {
        CreateSessionPool();
      }
    }
    return new OracleBackend(suffix, oraenv,
                             (char *) mSessionPoolName);
  }

};


//! Magic class that is activated when the dynamic library is loaded
class OracleLoader
{
public:

  OracleLoader()
  {
    BackendMakers().report(new OracleFactory);
    L << Logger::Info << "[oraclebackend] This is the oracle backend version " VERSION
#ifndef REPRODUCIBLE
      << " (" __DATE__ " " __TIME__ ")"
#endif
      << " reporting" << endl;
  }

};

static OracleLoader loader;

/* vi: set sw=2 et : */
