<commit_before>/* dnf-swdb.c
 *
 * Copyright (C) 2017 Red Hat, Inc.
 * Author: <NAME> <<EMAIL>>
 *
 * Licensed under the GNU Lesser General Public License Version 2.1
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "dnf-swdb.h"
#include "dnf-swdb-sql.h"
#include "dnf-swdb-trans.h"

G_DEFINE_TYPE (DnfSwdb, dnf_swdb, G_TYPE_OBJECT)
G_DEFINE_TYPE (DnfSwdbTransData, dnf_swdb_transdata, G_TYPE_OBJECT) // trans data
G_DEFINE_TYPE (DnfSwdbPkgData, dnf_swdb_pkgdata, G_TYPE_OBJECT)
G_DEFINE_TYPE (DnfSwdbRpmData, dnf_swdb_rpmdata, G_TYPE_OBJECT)

// SWDB object Destructor
static void
dnf_swdb_finalize (GObject *object)
{
    DnfSwdb *swdb = (DnfSwdb *)object;
    dnf_swdb_close (swdb);
    g_free (swdb->path);
    g_free (swdb->releasever);
    G_OBJECT_CLASS (dnf_swdb_parent_class)->finalize (object);
}

// SWDB Class initialiser
static void
dnf_swdb_class_init (DnfSwdbClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
    object_class->finalize = dnf_swdb_finalize;
}

// SWDB Object initialiser
static void
dnf_swdb_init (DnfSwdb *self)
{
    self->path = NULL;
    self->releasever = NULL;
}

/**
 * dnf_swdb_new:
 *
 * Creates a new #DnfSwdb.
 *
 * Returns: a #DnfSwdb
 **/
DnfSwdb *
dnf_swdb_new (const gchar *db_path, const gchar *releasever)
{
    DnfSwdb *swdb = g_object_new (DNF_TYPE_SWDB, NULL);
    if (releasever) {
        swdb->releasever = g_strdup (releasever);
    }
    if (db_path) {
        g_free (swdb->path);
        swdb->path = g_strdup (db_path);
    }
    return swdb;
}

// SWDB Package Data Destructor
static void
dnf_swdb_pkgdata_finalize (GObject *object)
{
    DnfSwdbPkgData *pkgdata = (DnfSwdbPkgData *)object;
    g_free (pkgdata->from_repo);
    g_free (pkgdata->from_repo_revision);
    g_free (pkgdata->installed_by);
    g_free (pkgdata->changed_by);
    g_free (pkgdata->installonly);
    G_OBJECT_CLASS (dnf_swdb_pkgdata_parent_class)->finalize (object);
}

// SWDB Package Data Class initialiser
static void
dnf_swdb_pkgdata_class_init (DnfSwdbPkgDataClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
    object_class->finalize = dnf_swdb_pkgdata_finalize;
}

// SWDB Package Data Object initialiser
static void
dnf_swdb_pkgdata_init (DnfSwdbPkgData *self)
{
    self->from_repo = NULL;
    self->from_repo_revision = NULL;
    self->from_repo_timestamp = 0;
    self->installed_by = NULL;
    self->changed_by = NULL;
    self->installonly = NULL;
}

/**
 * dnf_swdb_pkgdata_new:
 *
 * Creates a new #DnfSwdbPkgData.
 *
 * Returns: a #DnfSwdbPkgData
 **/
DnfSwdbPkgData *
dnf_swdb_pkgdata_new (const gchar *from_repo_revision,
                      gint64 from_repo_timestamp,
                      const gchar *installed_by,
                      const gchar *changed_by,
                      const gchar *installonly,
                      const gchar *from_repo)
{
    DnfSwdbPkgData *pkgdata = g_object_new (DNF_TYPE_SWDB_PKGDATA, NULL);
    pkgdata->from_repo_revision = g_strdup (from_repo_revision);
    pkgdata->from_repo_timestamp = from_repo_timestamp;
    pkgdata->installed_by = g_strdup (installed_by);
    pkgdata->changed_by = g_strdup (changed_by);
    pkgdata->installonly = g_strdup (installonly);
    pkgdata->from_repo = g_strdup (from_repo);
    return pkgdata;
}

// SWDB Transaction Data Destructor
static void
dnf_swdb_transdata_finalize (GObject *object)
{
    DnfSwdbTransData *tdata = (DnfSwdbTransData *)object;
    g_free (tdata->state);
    G_OBJECT_CLASS (dnf_swdb_transdata_parent_class)->finalize (object);
}

// SWDB Transaction Data Class initialiser
static void
dnf_swdb_transdata_class_init (DnfSwdbTransDataClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
    object_class->finalize = dnf_swdb_transdata_finalize;
}

// TRANS DATA Object initialiser
static void
dnf_swdb_transdata_init (DnfSwdbTransData *self)
{
}

/**
 * dnf_swdb_transdata_new:
 *
 * Creates a new #DnfSwdbTransData.
 *
 * Returns: a #DnfSwdbTransData
 **/
DnfSwdbTransData *
dnf_swdb_transdata_new (gint tdid,
                        gint tid,
                        gint pdid,
                        gint tgid,
                        gint done,
                        gint obsoleting,
                        DnfSwdbReason reason,
                        gchar *state)
{
    DnfSwdbTransData *data = g_object_new (DNF_TYPE_SWDB_TRANSDATA, NULL);
    data->tdid = tdid;
    data->tid = tid;
    data->pdid = pdid;
    data->tgid = tgid;
    data->done = done;
    data->obsoleting = obsoleting;
    data->reason = reason;
    data->state = g_strdup (state);
    return data;
}

// SWDB RPM data destructor
static void
dnf_swdb_rpmdata_finalize (GObject *object)
{
    DnfSwdbRpmData *rpmdata = (DnfSwdbRpmData *)object;
    g_free (rpmdata->buildhost);
    g_free (rpmdata->license);
    g_free (rpmdata->packager);
    g_free (rpmdata->size);
    g_free (rpmdata->sourcerpm);
    g_free (rpmdata->url);
    g_free (rpmdata->vendor);
    g_free (rpmdata->committer);
    G_OBJECT_CLASS (dnf_swdb_rpmdata_parent_class)->finalize (object);
}

// SWDB RPM data Class initialiser
static void
dnf_swdb_rpmdata_class_init (DnfSwdbRpmDataClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
    object_class->finalize = dnf_swdb_rpmdata_finalize;
}

// SWDB RPM data Object initialiser
static void
dnf_swdb_rpmdata_init (DnfSwdbRpmData *self)
{
    self->pid = 0;
    self->buildtime = 0;
    self->buildhost = NULL;
    self->license = NULL;
    self->packager = NULL;
    self->size = NULL;
    self->sourcerpm = NULL;
    self->url = NULL;
    self->vendor = NULL;
    self->committer = NULL;
    self->committime = 0;
}

/**
 * dnf_swdb_rpmdata_new:
 *
 * Creates a new #DnfSwdbRpmData.
 *
 * Returns: a #DnfSwdbRpmData
 **/
DnfSwdbRpmData *
dnf_swdb_rpmdata_new (gint pid,
                      gint64 buildtime,
                      const gchar *buildhost,
                      const gchar *license,
                      const gchar *packager,
                      const gchar *size,
                      const gchar *sourcerpm,
                      const gchar *url,
                      const gchar *vendor,
                      const gchar *committer,
                      gint64 committime)
{
    DnfSwdbRpmData *rpmdata = g_object_new (DNF_TYPE_SWDB_RPMDATA, NULL);
    rpmdata->pid = pid;
    rpmdata->buildtime = buildtime;
    rpmdata->buildhost = g_strdup (buildhost);
    rpmdata->license = g_strdup (license);
    rpmdata->packager = g_strdup (packager);
    rpmdata->size = g_strdup (size);
    rpmdata->sourcerpm = g_strdup (sourcerpm);
    rpmdata->url = g_strdup (url);
    rpmdata->vendor = g_strdup (vendor);
    rpmdata->committer = g_strdup (committer);
    rpmdata->committime = committime;
    return rpmdata;
}

/**
 * dnf_swdb_search:
 * @patterns: (element-type utf8) (transfer container): list of patterns
 *
 * Find transactions containing packages matched by list of patterns
 * Accepted pattern formats:
 *   name
 *   name.arch
 *   name-version-release.arch
 *   name-version
 *   epoch:name-version-release.arch
 *   name-epoch-version-release.arch
 *
 * Returns: (element-type gint32) (transfer container): list of constants
 */
GArray *
dnf_swdb_search (DnfSwdb *self, GPtrArray *patterns)
{
    if (dnf_swdb_open (self))
        return NULL;

    GArray *tids = g_array_new (0, 0, sizeof (gint));
    for (guint i = 0; i < patterns->len; ++i) {
        gchar *pattern = g_ptr_array_index (patterns, i);

        // try simple search based on package name
        GArray *simple = _simple_search (self->db, pattern);
        if (simple->len) {
            tids = g_array_append_vals (tids, simple->data, simple->len);
            g_array_free (simple, TRUE);
            continue;
        }
        g_array_free (simple, TRUE);
        // need for extended search - slower
        GArray *extended = _extended_search (self->db, pattern);
        if (extended) {
            tids = g_array_append_vals (tids, extended->data, extended->len);
        }
        g_array_free (extended, TRUE);
    }
    return tids;
}

/**
 * _pid_by_nevra:
 * @db: sqlite database handle
 * @nevra: string in format name-[epoch:]version-release.arch
 *
 * Get Package ID from SWDB by package nevra
 * Indexed nevra provides fast binding between other package objects
 *
 * Returns: id of package matched by @nevra
 **/
static gint
_pid_by_nevra (sqlite3 *db, const gchar *nevra)
{
    const gchar *sql = g_strrstr (nevra, ":") ? S_PID_BY_NEVRA : S_PID_BY_NVRA;
    sqlite3_stmt *res;
    DB_PREP (db, sql, res);
    DB_BIND (res, "@nevra", nevra);
    return DB_FIND (res);
}

/**
 * dnf_swdb_checksums:
 * @nevras: (element-type utf8) (transfer container): list of patterns
 *
 * Patch packages by name-[epoch:]version-release.arch and return list of their
 * checksums.
 * Output is in format:
 *   [nevra-1, checksum_type-1, checksum_data-1, nevra-2, checksum_type-2, checksum_data-2, ...]
 *
 * Used for performace optimization of rpmdb_version calculation in DNF
 *
 * Returns: (element-type utf8) (transfer container): list of checksums
 **/
GPtrArray *
dnf_swdb_checksums (DnfSwdb *self, GPtrArray *nevras)
{
    if (dnf_swdb_open (self))
        return NULL;
    gchar *buff1;
    gchar *buff2;
    gchar *nevra;

    GPtrArray *checksums = g_ptr_array_new ();
    const gchar *sql = S_CHECKSUM_BY_NEVRA;
    const gchar *sql2 = S_CHECKSUM_BY_NVRA;
    for (guint i = 0; i < nevras->len; i++) {
        // get nevra
        nevra = (gchar *)g_ptr_array_index (nevras, i);

        // save nevra to output list
        g_ptr_array_add (checksums, (gpointer) g_strdup (nevra)); // nevra

        sqlite3_stmt *res;
        if (g_strrstr (nevra, ":")) {
            DB_PREP (self->db, sql, res);
        } else {
            DB_PREP (self->db, sql2, res);
        }

        gboolean inserted = FALSE;

        DB_BIND (res, "@nevra", nevra);
        if (sqlite3_step (res) == SQLITE_ROW) {
            buff1 = (gchar *)sqlite3_column_text (res, 0);
            buff2 = (gchar *)sqlite3_column_text (res, 1);
            if (buff1 && buff2) {
                g_ptr_array_add (checksums, (gpointer) g_strdup (buff2)); // type
                g_ptr_array_add (checksums, (gpointer) g_strdup (buff1)); // data
                inserted = TRUE;
            }
        }
        sqlite3_finalize (res);

        if (!inserted) {
            // insert plain checksum data to keep the order correct
            g_ptr_array_add (checksums, NULL);
            g_ptr_array_add (checksums, NULL);
        }
    }
    return checksums;
}

/**
 * dnf_swdb_select_user_installed:
 * @nevras: (element-type utf8)(transfer container): list of patterns
 *
 * Match user installed packages by their name-[epoch:]version-release.arch
 * Return id's of user installed pacakges
 *
 * Example:
 *   @nevras = ["abc", "def", "ghi"]
 *   # "abc" and "ghi" are user installed packages
 *   Output:[0,2]
 *
 * Returns: (element-type gint32)(transfer container): list of matched indexes
 **/
GArray *
dnf_swdb_select_user_installed (DnfSwdb *self, GPtrArray *nevras)
{
    if (dnf_swdb_open (self))
        return NULL;

    GArray *usr_ids = g_array_new (0, 0, sizeof (gint));

    gint pid = 0;
    gint pdid = 0;
    DnfSwdbReason reason_id = DNF_SWDB_REASON_UNKNOWN;
    const gchar *sql = S_REASON_ID_BY_PDID;

    for (guint i = 0; i < nevras->len; ++i) {
        pid = _pid_by_nevra (self->db, (gchar *)g_ptr_array_index (nevras, i));
        if (!pid) {
            // better not uninstall package when not sure
            g_array_append_val (usr_ids, i);
            continue;
        }
        pdid = _pdid_from_pid (self->db, pid);
        if (!pdid) {
            g_array_append_val (usr_ids, i);
            continue;
        }
        sqlite3_stmt *res;
        DB_PREP (self->db, sql, res);
        DB_BIND_INT (res, "@pdid", pdid);
        gboolean push = TRUE;
        while (sqlite3_step (res) == SQLITE_ROW) {
            reason_id = sqlite3_column_int (res, 0);
            if (reason_id == DNF_SWDB_REASON_DEP || reason_id == DNF_SWDB_REASON_WEAK) {
                push = FALSE;
                continue;
            }
            push = TRUE;
            break;
        }
        // push even in case when no record is found
        if (push) {
            g_array_append_val (usr_ids, i);
        }
        sqlite3_finalize (res);
    }
    return usr_ids;
}

/**
 * dnf_swdb_user_installed:
 * @self: SWDB object
 * @nevra: string in format name-[epoch:]version-release.arch
 * Returns: %TRUE if package is user installed
 **/
gboolean
dnf_swdb_user_installed (DnfSwdb *self, const gchar *nevra)
{
    if (dnf_swdb_open (self))
        return TRUE;

    gint pid = _pid_by_nevra (self->db, nevra);
    if (!pid) {
        return FALSE;
    }
    gboolean rc = TRUE;
    gchar *repo = _repo_by_pid (self->db, pid);
    DnfSwdbReason reason = _reason_by_pid (self->db, pid);
    if (reason != DNF_SWDB_REASON_USER || !g_strcmp0 ("anakonda", repo)) {
        rc = FALSE;
    }
    g_free (repo);
    return rc;
}

/***************************** REPO PERSISTOR ********************************/

/**
 * _bind_repo_by_name:
 * @db: sqlite database handle
 * @name: repository name
 * Returns: repository ID
 **/
static gint
_bind_repo_by_name (sqlite3 *db, const gchar *name)
{
    sqlite3_stmt *res;
    const gchar *sql = FIND_REPO_BY_NAME;
    DB_PREP (db, sql, res);
    DB_BIND (res, "@name", name);
    gint rc = DB_FIND (res);
    if (rc) {
        return rc;
    }
    sql = INSERT_REPO;
    DB_PREP (db, sql, res);
    DB_BIND (res, "@name", name);
    DB_STEP (res);
    return sqlite3_last_insert_rowid (db);
}

/**
 * _repo_by_rid:
 * @db: sqlite database handle
 * @rid: repository ID
 * Returns: repository name
 **/
gchar *
_repo_by_rid (sqlite3 *db, gint rid)
{
    sqlite3_stmt *res;
    const gchar *sql = S_REPO_BY_RID;
    DB_PREP (db, sql, res);
    DB_BIND_INT (res, "@rid", rid);
    return DB_FIND_STR (res);
}

/**
 * _repo_by_pid:
 * @db: sqlite database handle
 * @pid: package id
 * Returns: repository name
 **/
gchar *
_repo_by_pid (sqlite3 *db, gint pid)
{
    sqlite3_stmt *res;
    const gchar *sql = S_REPO_FROM_PID2;
    DB_PREP (db, sql, res);
    DB_BIND_INT (res, "@pid", pid);
    return DB_FIND_STR (res);
}

/**
 * dnf_swdb_repo:
 * @self: SWDB object
 * @nevra: string in format name-[epoch:]version-release.arch
 * Returns: repository name
 **/
gchar *
dnf_swdb_repo (DnfSwdb *self, const gchar *nevra)
{
    if (dnf_swdb_open (self))
        return NULL;
    gint pid = _pid_by_nevra (self->db, nevra);
    if (!pid) {
        return g_strdup ("unknown");
    }
    return _repo_by_pid (self->db, pid);
}

/**
 * dnf_swdb_set_repo:
 * @self: SWDB object
 * @nevra: string in format name-[epoch:]version-release.arch
 * @repo: repository name
 *
 * Set source repository for package matched by @nevra
 *
 * Returns: 0 if source repository was set successfully
 **/
gint
dnf_swdb_set_repo (DnfSwdb *self, const gchar *nevra, const gchar *repo)
{
    if (dnf_swdb_open (self))
        return 1;
    gint pid = _pid_by_nevra (self->db, nevra);
    if (!pid) {
        return 1;
    }
    gint rid = _bind_repo_by_name (self->db, repo);
    sqlite3_stmt *res;
    const gchar *sql = U_REPO_BY_PID;
    DB_PREP (self->db, sql, res);
    DB_BIND_INT (res, "@rid", rid);
    DB_BIND_INT (res, "@pid", pid);
    DB_STEP (res);
    return 0;
}

/**************************** PACKAGE PERSISTOR ******************************/

/**
 * _package_insert:
 * @self: SWDB object
 * @package: package object
 *
 * Insert package object into swdb
 *
 * Returns: inserted package ID
 **/
static gint
_package_insert (DnfSwdb *self, DnfSwdbPkg *package)
{
    sqlite3_stmt *res;
    const gchar *sql = INSERT_PKG;
    DB_PREP (self->db, sql, res);
    DB_BIND (res, "@name", package->name);
    DB_BIND_INT (res, "@epoch", package->epoch);
    DB_BIND (res, "@version", package->version);
    DB_BIND (res, "@release", package->release);
    DB_BIND (res, "@arch", package->arch);
    DB_BIND (res, "@cdata", package->checksum_data);
    DB_BIND (res, "@ctype", package->checksum_type);
    DB_BIND_INT (res, "@type", package->type);
    DB_STEP (res);
    package->pid = sqlite3_last_insert_rowid (self->db);
    return package->pid;
}

/**
 * dnf_swdb_add_package:
 * @self: SWDB object
 * @pkg: package object
 *
 * Insert package into database
 *
 * Returns: inserted package ID or -1 if database error occurred
 **/
gint
dnf_swdb_add_package (DnfSwdb *self, DnfSwdbPkg *pkg)
{
    if (dnf_swdb_open (self))
        return -1;
    gint rc = _package_insert (self, pkg);
    return rc;
}

/**
 * dnf_swdb_update_package_data:
 * @self: SWDB object
 * @pid: package ID
 * @tid: transation ID
 * @pkgdata: package data object
 *
 * Insert or update pkgdata for package with ID @pid in transation @tid.
 * This method MUST be called after transaction data initialization by `dnf_swdb_trans_data_beg`
 *
 * Returns: 0 if successful
 **/
gint
dnf_swdb_update_package_data (DnfSwdb *self, gint pid, gint tid, DnfSwdbPkgData *pkgdata)
{
    if (dnf_swdb_open (self))
        return 1;

    gint rid = _bind_repo_by_name (self->db, pkgdata->from_repo);

    // look for old pid's package data - all values of autogenerated rows are null
    sqlite3_stmt *res;
    const gchar *sql = S_PREV_PKG_DATA;
    DB_PREP (self->db, sql, res);
    DB_BIND_INT (res, "@pid", pid);
    DB_BIND_INT (res, "@tid", tid);
    gint pdid = DB_FIND (res);

    if (!pdid) {
        // field is not available
        g_warning("Package %d is not available in transation %d", pid, tid);
        return 1;
    }

    sql = UPDATE_PKG_DATA;
    DB_PREP (self->db, sql, res);
    DB_BIND_INT (res, "@pid", pid);
    DB_BIND_INT (res, "@rid", rid);
    DB_BIND (res, "@repo_r", pkgdata->from_repo_revision);
    DB_BIND_INT (res, "@repo_t", pkgdata->from_repo_timestamp);
    DB_BIND (res, "@installed_by", pkgdata->installed_by);
    DB_BIND (res, "@changed_by", pkgdata->changed_by);
    DB_BIND (res, "@installonly", pkgdata->installonly);
    DB_STEP (res);
    return 0;
}

/**
 * _new_pdid_for_pid:
 * @db: sqlite database handle
 * @pid: package ID
 *
 * Create new record in PACKAGE_DATA table for @pid
 *
 * Returns: new pdid
 **/
static gint
_new_pdid_for_pid (sqlite3 *db, gint pid)
{
    sqlite3_stmt *res;
    const gchar *sql = INSERT_PDID;
    DB_PREP (db, sql, res);
    DB_BIND_INT (res, "@pid", pid);
    DB_STEP (res);
    return sqlite3_last_insert_rowid (db);
}

/**
 * _pdid_from_pid:
 * @db: sqlite database handle
 * @pid: package ID
 *
 * Get package data ID for package with ID @pid.
 * Create new package data record if unavailable.
 *
 * Returns: package data ID
 **/
gint
_pdid_from_pid (sqlite3 *db, gint pid)
{
    sqlite3_stmt *res;
    const gchar *sql = FIND_PDID_FROM_PID;
    DB_PREP (db, sql, res);
    DB_BIND_INT (res, "@pid", pid);
    gint rc = DB_FIND (res);
    if (rc)
        return rc;
    return _new_pdid_for_pid (db, pid);
}

/**
 * _pids_by_tid:
 * @db: sqlite database handle
 * @tid: transaction ID
 *
 * Get all package IDs connected with transation with ID @tid
 *
 * Returns: list od package IDs
 **/
static GArray *
_pids_by_tid (sqlite3 *db, gint tid)
{
    GArray *pids = g_array_new (0, 0, sizeof (gint));
    sqlite3_stmt *res;
    const gchar *sql = PID_BY_TID;
    DB_PREP (db, sql, res);
    DB_BIND_INT (res, "@tid", tid);
    gint pid;
    while ((pid = DB_FIND_MULTI (res))) {
        g_array_append_val (pids, pid);
    }
    return pids;
}

/**
 * _reason_by_pid:
 * @db: sqlite database handle
 * @pid: package ID
 *
 * Returns: reason id for package with @pid
 **/
DnfSwdbReason
_reason_by_pid (sqlite3 *db, gint pid)
{
    sqlite3_stmt *res;
    const gchar *sql = S_REASON_BY_PID;
    DB_PREP (db, sql, res);
    DB_BIND_INT (res, "@pid", pid);
    return DB_FIND (res);
}

/**
 * dnf_swdb_reason:
 * @self: SWDB object
 * @nevra: string in format name-[epoch:]version-release.arch
 *
 * Returns: reason ID for package matched by @nevra
 **/
DnfSwdbReason
dnf_swdb_reason (DnfSwdb *self, const gchar *nevra)
{
    DnfSwdbReason reason = DNF_SWDB_REASON_UNKNOWN;
    if (dnf_swdb_open (self))
        return reason;
    gint pid = _pid_by_nevra (self->db, nevra);
    if (pid) {
        reason = _reason_by_pid (self->db, pid);
    }
    return reason;
}

/**
 * _resolve_package_state:
 * @self: SWDB object
 * @pkg: package object
 * @tid: transation ID
 *
 * Set package state to match transaction given by @tid
 **/
static void
_resolve_package_state (DnfSwdb *self, DnfSwdbPkg *pkg, gint tid)
{
    sqlite3_stmt *res;
    const gchar *sql = S_PACKAGE_STATE;
    DB_PREP (self->db, sql, res);
    DB_BIND_INT (res, "@pid", pkg->pid);
    DB_BIND_INT (res, "@tid", tid);
    if (sqlite3_step (res) == SQLITE_ROW) {
        pkg->done = sqlite3_column_int (res, 0);
        gint state_code = sqlite3_column_int (res, 1);
        pkg->obsoleting = sqlite3_column_int (res, 2);
        sqlite3_finalize (res);
        pkg->state = _get_description (self->db, state_code, S_STATE_TYPE_BY_ID);
    } else {
        sqlite3_finalize (res);
    }
}

/**
 * _get_package_data_by_pid:
 * @db: sqlite database handle
 * @pid: package ID
 *
 * Returns: latest package data object for package with ID @pid
 **/
static DnfSwdbPkgData *
_get_package_data_by_pid (sqlite3 *db, gint pid)
{
    sqlite3_stmt *res;
    const gchar *sql = S_PACKAGE_DATA_BY_PID;
    DB_PREP (db, sql, res);
    DB_BIND_INT (res, "@pid", pid);
    if (sqlite3_step (res) == SQLITE_ROW) {
        DnfSwdbPkgData *pkgdata =
          dnf_swdb_pkgdata_new ((gchar *)sqlite3_column_text (res, 3), // from_repo_revision
                                sqlite3_column_int (res, 4), // from_repo_timestamp
                                (gchar *)sqlite3_column_text (res, 5), // installed_by
                                (gchar *)sqlite3_column_text (res, 6), // changed_by
                                (gchar *)sqlite3_column_text (res, 7), // installonly
                                NULL);                                 // from_repo
        pkgdata->pdid = sqlite3_column_int (res, 0); // pdid
        pkgdata->pid = pid;
        gint rid = sqlite3_column_int (res, 2);
        sqlite3_finalize (res);
        pkgdata->from_repo = _repo_by_rid (db, rid); // from repo
        return pkgdata;
    }
    sqlite3_finalize (res);
    return NULL;
}

/**
 * _get_package_by_pid:
 * @db: sqlite database handle
 * @pid: package ID
 *
 * Returns: package object for @pid
 **/
DnfSwdbPkg *
_get_package_by_pid (sqlite3 *db, gint pid)
{
    sqlite3_stmt *res;
    const gchar *sql = S_PACKAGE_BY_PID;
    DB_PREP (db, sql, res);
    DB_BIND_INT (res, "@pid", pid);
    if (sqlite3_step (res) == SQLITE_ROW) {
        DnfSwdbPkg *pkg = dnf_swdb_pkg_new ((gchar *)sqlite3_column_text (res, 1), // name
                                            sqlite3_column_int (res, 2),           // epoch
                                            (gchar *)sqlite3_column_text (res, 3), // version
                                            (gchar *)sqlite3_column_text (res, 4), // release
                                            (gchar *)sqlite3_column_text (res, 5), // arch
                                            (gchar *)sqlite3_column_text (res, 6), // checksum_data
                                            (gchar *)sqlite3_column_text (res, 7), // checksum_type
                                            sqlite3_column_int (res, 8));          // type
        pkg->pid = pid;
        sqlite3_finalize (res);
        return pkg;
    }
    sqlite3_finalize (res);
    return NULL;
}

/**
 * dnf_swdb_get_packages_by_tid:
 * @self: SWDB object
 * @tid: transation ID
 *
 * Get list of package object featured in transaction with ID @tid
 *
 * Returns: (element-type DnfSwdbPkg)(array)(transfer container): list of #DnfSwdbPkg
 */
GPtrArray *
dnf_swdb_get_packages_by_tid (DnfSwdb *self, gint tid)
{
    if (dnf_swdb_open (self))
        return NULL;

    GPtrArray *node = g_ptr_array_new ();
    GArray *pids = _pids_by_tid (self->db, tid);
    gint pid;
    DnfSwdbPkg *pkg;
    for (guint i = 0; i < pids->len; i++) {
        pid = g_array_index (pids, gint, i);
        pkg = _get_package_by_pid (self->db, pid);
        if (pkg) {
            pkg->swdb = self;
            _resolve_package_state (self, pkg, tid);
            g_ptr_array_add (node, (gpointer)pkg);
        }
    }
    g_array_free (pids, TRUE);
    return node;
}

/**
 * dnf_swdb_pid_by_nevra:
 * @self: SWDB object
 * @nevra: string in format name-[epoch:]version-release.arch
 *
 * Returns: ID of package matched by @nevra or 0
 **/
gint
dnf_swdb_pid_by_nevra (DnfSwdb *self, const gchar *nevra)
{
    if (dnf_swdb_open (self))
        return 0;
    gint pid = _pid_by_nevra (self->db, nevra);
    return pid;
}

/**
 * dnf_swdb_package:
 * @self: SWDB object
 * @nevra: string in format name-[epoch:]version-release.arch
 *
 * Returns: (transfer full): #DnfSwdbPkg matched by @nevra
 **/
DnfSwdbPkg *
dnf_swdb_package (DnfSwdb *self, const gchar *nevra)
{
    if (dnf_swdb_open (self))
        return NULL;
    gint pid = _pid_by_nevra (self->db, nevra);
    if (!pid) {
        return NULL;
    }
    DnfSwdbPkg *pkg = _get_package_by_pid (self->db, pid);
    pkg->swdb = self;
    return pkg;
}

/**
 * dnf_swdb_package_data:
 * @self: SWDB object
 * @nevra: string in format name-[epoch:]version-release.arch
 *
 * Returns: (transfer full): #DnfSwdbPkgData for package matched by @nevra
 **/
DnfSwdbPkgData *
dnf_swdb_package_data (DnfSwdb *self, const gchar *nevra)
{
    if (dnf_swdb_open (self))
        return NULL;
    gint pid = _pid_by_nevra (self->db, nevra);
    if (!pid) {
        return NULL;
    }
    DnfSwdbPkgData *pkgdata = _get_package_data_by_pid (self->db, pid);
    return pkgdata;
}

/**
 * _mark_pkg_as:
 * @db: sqlite database handle
 * @pdid: package data ID
 * @mark: reason ID
 *
 * Set reason in package data with ID @pdid to @mark
 **/
static void
_mark_pkg_as (sqlite3 *db, gint pdid, DnfSwdbReason mark)
{
    sqlite3_stmt *res;
    const gchar *sql = U_REASON_BY_PDID;
    DB_PREP (db, sql, res);
    DB_BIND_INT (res, "@reason", mark);
    DB_BIND_INT (res, "@pdid", pdid);
    DB_STEP (res);
}

/**
 * dnf_swdb_set_reason:
 * @self: SWDB object
 * @nevra: string in format name-[epoch:]version-release.arch
 * @reason: reason id
 *
 * Set reason of package matched by @nevra to @reason
 * Returns: 0 if successfull, else 1
 **/
gint
dnf_swdb_set_reason (DnfSwdb *self, const gchar *nevra, DnfSwdbReason reason)
{
    if (dnf_swdb_open (self))
        return 1;
    gint pid = _pid_by_nevra (self->db, nevra);
    if (!pid) {
        return 1;
    }
    gint pdid = _pdid_from_pid (self->db, pid);
    _mark_pkg_as (self->db, pdid, reason);
    return 0;
}

/**************************** RPM DATA PERSISTOR *****************************/

/**
 * dnf_swdb_add_rpm_data:
 * @self: SWDB object
 * @rpm_data: RPM data object
 *
 * Add @rpm_data to swdb
 *
 * Returns: 0 if successfull
 **/
gint
dnf_swdb_add_rpm_data (DnfSwdb *self, DnfSwdbRpmData *rpm_data)
{
    if (dnf_swdb_open (self))
        return 1;
    sqlite3_stmt *res;
    const gchar *sql = INSERT_RPM_DATA;
    DB_PREP (self->db, sql, res);
    DB_BIND_INT (res, "@pid", rpm_data->pid);
    DB_BIND_INT (res, "@buildtime", rpm_data->buildtime);
    DB_BIND (res, "@buildhost", rpm_data->buildhost);
    DB_BIND (res, "@license", rpm_data->license);
    DB_BIND (res, "@packager", rpm_data->packager);
    DB_BIND (res, "@size", rpm_data->size);
    DB_BIND (res, "@sourcerpm", rpm_data->sourcerpm);
    DB_BIND (res, "@url", rpm_data->url);
    DB_BIND (res, "@vendor", rpm_data->vendor);
    DB_BIND (res, "@committer", rpm_data->committer);
    DB_BIND_INT (res, "@committime", rpm_data->committime);
    DB_STEP (res);
    return 0;
}

/*************************** TRANS DATA PERSISTOR ****************************/

/**
 * _resolve_group_origin:
 * @db: sqlite database handler
 * @tid: transaction ID
 * @pid: package ID
 *
 * Get trans group data of package installed with group
 *
 * Returns: trans group data ID for package with ID @pid
 **/
static gint
_resolve_group_origin (sqlite3 *db, gint tid, gint pid)
{
    sqlite3_stmt *res;
    const gchar *sql = RESOLVE_GROUP_TRANS;
    DB_PREP (db, sql, res);
    DB_BIND_INT (res, "@pid", pid);
    DB_BIND_INT (res, "@tid", tid);
    return DB_FIND (res);
}

/**
 * dnf_swdb_trans_data_beg:
 * @self: SWDB object
 * @tid: transaction ID
 * @pid: package ID
 * @reason: reason ID
 * @state: state string
 *
 * Initialize transaction data for package with ID @pid in transaction @tid
 *
 * If package was installed with group, create binding between transaction data
 *   and transaction group data
 *
 * Returns: 0 if successfull
 **/
gint
dnf_swdb_trans_data_beg (DnfSwdb *self,
                         gint tid,
                         gint pid,
                         DnfSwdbReason reason,
                         const gchar *state,
                         gint obsoleting)
{
    if (dnf_swdb_open (self))
        return 1;

    // generate new empty package data entry
    gint pdid = _new_pdid_for_pid (self->db, pid);

    // resolve group orogin
    gint tgid = 0;
    if (reason == DNF_SWDB_REASON_GROUP) {
        tgid = _resolve_group_origin (self->db, tid, pid);
    }

    // resolve state
    gint state_code = dnf_swdb_get_state_type (self, state);

    // insert new entry
    sqlite3_stmt *res;
    const gchar *sql = INSERT_TRANS_DATA_BEG;
    DB_PREP (self->db, sql, res);
    DB_BIND_INT (res, "@tid", tid);
    DB_BIND_INT (res, "@pdid", pdid);
    DB_BIND_INT (res, "@tgid", tgid);
    DB_BIND_INT (res, "@obsoleting", obsoleting);
    DB_BIND_INT (res, "@reason", reason);
    DB_BIND_INT (res, "@state", state_code);
    DB_STEP (res);

    return 0;
}

/**
 * dnf_swdb_trans_data_pid_end:
 * @self: SWDB object
 * @pid: package ID
 * @tid: transaction ID
 * @state: state string
 *
 * Finalize transaction for package with ID @pid
 *
 * Returns: 0 if successfull
 **/
gint
dnf_swdb_trans_data_pid_end (DnfSwdb *self, gint pid, gint tid, const gchar *state)
{
    if (dnf_swdb_open (self))
        return 1;

    gint pdid = _pdid_from_pid (self->db, pid);
    gint state_code = dnf_swdb_get_state_type (self, state);

    sqlite3_stmt *res;
    const gchar *sql = UPDATE_TRANS_DATA_PID_END;
    DB_PREP (self->db, sql, res);
    DB_BIND_INT (res, "@done", 1);
    DB_BIND_INT (res, "@tid", tid);
    DB_BIND_INT (res, "@pdid", pdid);
    DB_BIND_INT (res, "@state", state_code);
    DB_STEP (res);
    return 0;
}

/****************************** TRANS PERSISTOR ******************************/

/**
 * dnf_swdb_trans_beg:
 * @self: SWDB object
 * @timestamp: transaction timestamp
 * @rpmdb_version: rpmdb_version before transaction
 * @cmdline: command which initiated transaction
 * @loginuid: ID of user which initiated transaction
 * @releasever: release version of system
 *
 * Initialize transaction
 *
 * Returns: transaction ID or -1 if error occurred
 **/
gint
dnf_swdb_trans_beg (DnfSwdb *self,
                    gint64 timestamp,
                    const gchar *rpmdb_version,
                    const gchar *cmdline,
                    const gchar *loginuid,
                    const gchar *releasever)
{
    if (dnf_swdb_open (self))
        return -1;

    sqlite3_stmt *res;
    const gchar *sql = INSERT_TRANS_BEG;
    DB_PREP (self->db, sql, res);
    DB_BIND_INT (res, "@beg", timestamp);
    DB_BIND (res, "@rpmdbv", rpmdb_version);
    DB_BIND (res, "@cmdline", cmdline);
    DB_BIND (res, "@loginuid", loginuid);
    DB_BIND (res, "@releasever", releasever);
    DB_STEP (res);
    gint rc = sqlite3_last_insert_rowid (self->db);
    return rc;
}

/**
 * dnf_swdb_trans_end:
 * @self: SWDB object
 * @tid: transaction ID
 * @end_timestamp: end timestamp
 * @end_rpmdb_version: rpmdb version after transaction
 * @return_code: return code of transaction
 *
 * Finalize transaction
 *
 * Returns: 0 if successfull
 **/
gint
dnf_swdb_trans_end (DnfSwdb *self,
                    gint tid,
                    gint64 end_timestamp,
                    const gchar *end_rpmdb_version,
                    gint return_code)
{
    if (dnf_swdb_open (self))
        return 1;
    sqlite3_stmt *res;
    const gchar *sql = INSERT_TRANS_END;
    DB_PREP (self->db, sql, res);
    DB_BIND_INT (res, "@tid", tid);
    DB_BIND_INT (res, "@end", end_timestamp);
    DB_BIND (res, "@rpmdbv", end_rpmdb_version);
    DB_BIND_INT (res, "@rc", return_code);
    DB_STEP (res);
    return 0;
}

/**
 * dnf_swdb_trans_with:
 * @self: SWDB object
 * @tid: transaction ID
 * @pid: package ID
 *
 * Log transaction perforemed with package
 **/
void
dnf_swdb_trans_with (DnfSwdb *self, int tid, int pid)
{
    if (dnf_swdb_open (self)) {
        return;
    }
    sqlite3_stmt *res;
    const gchar *sql = I_TRANS_WITH;
    DB_PREP (self->db, sql, res);
    DB_BIND_INT (res, "@tid", tid);
    DB_BIND_INT (res, "@pid", pid);
    DB_STEP (res);
}

/**
 * dnf_swdb_trans_with_libdnf:
 * @self: SWDB object
 * @tid: transaction ID
 *
 * Log transaction perforemed with libdnf
 **/
void
dnf_swdb_trans_with_libdnf (DnfSwdb *self, int tid)
{
    if (dnf_swdb_open (self)) {
        return;
    }
    sqlite3_stmt *res;
    const gchar *sql = S_LATEST_PACKAGE;
    DB_PREP (self->db, sql, res);
    DB_BIND (res, "@name", "libdnf");
    gint pid = DB_FIND (res);
    if (pid) {
        dnf_swdb_trans_with (self, tid, pid);
    }
    DB_PREP (self->db, sql, res);
    DB_BIND (res, "@name", "rpm");
    pid = DB_FIND (res);
    if (pid) {
        dnf_swdb_trans_with (self, tid, pid);
    }
}

/**
 * dnf_swdb_trans_cmdline:
 * @self: SWDB object
 * @tid: transaction ID
 *
 * Returns: command line which initiated transaction @tid
 **/
gchar *
dnf_swdb_trans_cmdline (DnfSwdb *self, gint tid)
{
    if (dnf_swdb_open (self))
        return NULL;
    sqlite3_stmt *res;
    const gchar *sql = GET_TRANS_CMDLINE;
    DB_PREP (self->db, sql, res);
    DB_BIND_INT (res, "@tid", tid);
    gchar *cmdline = DB_FIND_STR (res);
    return cmdline;
}

/**
 * _resolve_altered:
 * @trans: list of #DnfSwdbTrans
 *
 * Resolve if rpmdb altered between transactions
 **/
static void
_resolve_altered (GPtrArray *trans)
{
    for (guint i = 0; i < (trans->len - 1); i++) {
        DnfSwdbTrans *las = (DnfSwdbTrans *)g_ptr_array_index (trans, i);
        DnfSwdbTrans *obj = (DnfSwdbTrans *)g_ptr_array_index (trans, i + 1);

        if (!las->end_rpmdb_version || !obj->beg_rpmdb_version)
            continue;

        if (g_strcmp0 (obj->end_rpmdb_version, las->beg_rpmdb_version)) {
            obj->altered_lt_rpmdb = 1;
            las->altered_gt_rpmdb = 1;
        } else {
            las->altered_gt_rpmdb = 0;
            obj->altered_lt_rpmdb = 0;
        }
    }
}

/**
 * _test_output:
 * @self: SWDB object
 * @tid: transaction ID
 * @o_type: output type string
 *
 * Returns: %TRUE if transaction has output of type @o_type
 **/
static gboolean
_test_output (DnfSwdb *self, gint tid, const gchar *o_type)
{
    gint type = dnf_swdb_get_output_type (self, o_type);
    sqlite3_stmt *res;
    const gchar *sql = T_OUTPUT;
    DB_PREP (self->db, sql, res);
    DB_BIND_INT (res, "@tid", tid);
    DB_BIND_INT (res, "@type", type);
    if (sqlite3_step (res) == SQLITE_ROW) {
        sqlite3_finalize (res);<commit_msg>[swdb]: fix altered rpmdb flag in transaction<commit_after>/* dnf-swdb.c
 *
 * Copyright (C) 2017 Red Hat, Inc.
 * Author: <NAME> <<EMAIL>>
 *
 * Licensed under the GNU Lesser General Public License Version 2.1
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "dnf-swdb.h"
#include "dnf-swdb-sql.h"
#include "dnf-swdb-trans.h"

G_DEFINE_TYPE (DnfSwdb, dnf_swdb, G_TYPE_OBJECT)
G_DEFINE_TYPE (DnfSwdbTransData, dnf_swdb_transdata, G_TYPE_OBJECT) // trans data
G_DEFINE_TYPE (DnfSwdbPkgData, dnf_swdb_pkgdata, G_TYPE_OBJECT)
G_DEFINE_TYPE (DnfSwdbRpmData, dnf_swdb_rpmdata, G_TYPE_OBJECT)

// SWDB object Destructor
static void
dnf_swdb_finalize (GObject *object)
{
    DnfSwdb *swdb = (DnfSwdb *)object;
    dnf_swdb_close (swdb);
    g_free (swdb->path);
    g_free (swdb->releasever);
    G_OBJECT_CLASS (dnf_swdb_parent_class)->finalize (object);
}

// SWDB Class initialiser
static void
dnf_swdb_class_init (DnfSwdbClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
    object_class->finalize = dnf_swdb_finalize;
}

// SWDB Object initialiser
static void
dnf_swdb_init (DnfSwdb *self)
{
    self->path = NULL;
    self->releasever = NULL;
}

/**
 * dnf_swdb_new:
 *
 * Creates a new #DnfSwdb.
 *
 * Returns: a #DnfSwdb
 **/
DnfSwdb *
dnf_swdb_new (const gchar *db_path, const gchar *releasever)
{
    DnfSwdb *swdb = g_object_new (DNF_TYPE_SWDB, NULL);
    if (releasever) {
        swdb->releasever = g_strdup (releasever);
    }
    if (db_path) {
        g_free (swdb->path);
        swdb->path = g_strdup (db_path);
    }
    return swdb;
}

// SWDB Package Data Destructor
static void
dnf_swdb_pkgdata_finalize (GObject *object)
{
    DnfSwdbPkgData *pkgdata = (DnfSwdbPkgData *)object;
    g_free (pkgdata->from_repo);
    g_free (pkgdata->from_repo_revision);
    g_free (pkgdata->installed_by);
    g_free (pkgdata->changed_by);
    g_free (pkgdata->installonly);
    G_OBJECT_CLASS (dnf_swdb_pkgdata_parent_class)->finalize (object);
}

// SWDB Package Data Class initialiser
static void
dnf_swdb_pkgdata_class_init (DnfSwdbPkgDataClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
    object_class->finalize = dnf_swdb_pkgdata_finalize;
}

// SWDB Package Data Object initialiser
static void
dnf_swdb_pkgdata_init (DnfSwdbPkgData *self)
{
    self->from_repo = NULL;
    self->from_repo_revision = NULL;
    self->from_repo_timestamp = 0;
    self->installed_by = NULL;
    self->changed_by = NULL;
    self->installonly = NULL;
}

/**
 * dnf_swdb_pkgdata_new:
 *
 * Creates a new #DnfSwdbPkgData.
 *
 * Returns: a #DnfSwdbPkgData
 **/
DnfSwdbPkgData *
dnf_swdb_pkgdata_new (const gchar *from_repo_revision,
                      gint64 from_repo_timestamp,
                      const gchar *installed_by,
                      const gchar *changed_by,
                      const gchar *installonly,
                      const gchar *from_repo)
{
    DnfSwdbPkgData *pkgdata = g_object_new (DNF_TYPE_SWDB_PKGDATA, NULL);
    pkgdata->from_repo_revision = g_strdup (from_repo_revision);
    pkgdata->from_repo_timestamp = from_repo_timestamp;
    pkgdata->installed_by = g_strdup (installed_by);
    pkgdata->changed_by = g_strdup (changed_by);
    pkgdata->installonly = g_strdup (installonly);
    pkgdata->from_repo = g_strdup (from_repo);
    return pkgdata;
}

// SWDB Transaction Data Destructor
static void
dnf_swdb_transdata_finalize (GObject *object)
{
    DnfSwdbTransData *tdata = (DnfSwdbTransData *)object;
    g_free (tdata->state);
    G_OBJECT_CLASS (dnf_swdb_transdata_parent_class)->finalize (object);
}

// SWDB Transaction Data Class initialiser
static void
dnf_swdb_transdata_class_init (DnfSwdbTransDataClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
    object_class->finalize = dnf_swdb_transdata_finalize;
}

// TRANS DATA Object initialiser
static void
dnf_swdb_transdata_init (DnfSwdbTransData *self)
{
}

/**
 * dnf_swdb_transdata_new:
 *
 * Creates a new #DnfSwdbTransData.
 *
 * Returns: a #DnfSwdbTransData
 **/
DnfSwdbTransData *
dnf_swdb_transdata_new (gint tdid,
                        gint tid,
                        gint pdid,
                        gint tgid,
                        gint done,
                        gint obsoleting,
                        DnfSwdbReason reason,
                        gchar *state)
{
    DnfSwdbTransData *data = g_object_new (DNF_TYPE_SWDB_TRANSDATA, NULL);
    data->tdid = tdid;
    data->tid = tid;
    data->pdid = pdid;
    data->tgid = tgid;
    data->done = done;
    data->obsoleting = obsoleting;
    data->reason = reason;
    data->state = g_strdup (state);
    return data;
}

// SWDB RPM data destructor
static void
dnf_swdb_rpmdata_finalize (GObject *object)
{
    DnfSwdbRpmData *rpmdata = (DnfSwdbRpmData *)object;
    g_free (rpmdata->buildhost);
    g_free (rpmdata->license);
    g_free (rpmdata->packager);
    g_free (rpmdata->size);
    g_free (rpmdata->sourcerpm);
    g_free (rpmdata->url);
    g_free (rpmdata->vendor);
    g_free (rpmdata->committer);
    G_OBJECT_CLASS (dnf_swdb_rpmdata_parent_class)->finalize (object);
}

// SWDB RPM data Class initialiser
static void
dnf_swdb_rpmdata_class_init (DnfSwdbRpmDataClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
    object_class->finalize = dnf_swdb_rpmdata_finalize;
}

// SWDB RPM data Object initialiser
static void
dnf_swdb_rpmdata_init (DnfSwdbRpmData *self)
{
    self->pid = 0;
    self->buildtime = 0;
    self->buildhost = NULL;
    self->license = NULL;
    self->packager = NULL;
    self->size = NULL;
    self->sourcerpm = NULL;
    self->url = NULL;
    self->vendor = NULL;
    self->committer = NULL;
    self->committime = 0;
}

/**
 * dnf_swdb_rpmdata_new:
 *
 * Creates a new #DnfSwdbRpmData.
 *
 * Returns: a #DnfSwdbRpmData
 **/
DnfSwdbRpmData *
dnf_swdb_rpmdata_new (gint pid,
                      gint64 buildtime,
                      const gchar *buildhost,
                      const gchar *license,
                      const gchar *packager,
                      const gchar *size,
                      const gchar *sourcerpm,
                      const gchar *url,
                      const gchar *vendor,
                      const gchar *committer,
                      gint64 committime)
{
    DnfSwdbRpmData *rpmdata = g_object_new (DNF_TYPE_SWDB_RPMDATA, NULL);
    rpmdata->pid = pid;
    rpmdata->buildtime = buildtime;
    rpmdata->buildhost = g_strdup (buildhost);
    rpmdata->license = g_strdup (license);
    rpmdata->packager = g_strdup (packager);
    rpmdata->size = g_strdup (size);
    rpmdata->sourcerpm = g_strdup (sourcerpm);
    rpmdata->url = g_strdup (url);
    rpmdata->vendor = g_strdup (vendor);
    rpmdata->committer = g_strdup (committer);
    rpmdata->committime = committime;
    return rpmdata;
}

/**
 * dnf_swdb_search:
 * @patterns: (element-type utf8) (transfer container): list of patterns
 *
 * Find transactions containing packages matched by list of patterns
 * Accepted pattern formats:
 *   name
 *   name.arch
 *   name-version-release.arch
 *   name-version
 *   epoch:name-version-release.arch
 *   name-epoch-version-release.arch
 *
 * Returns: (element-type gint32) (transfer container): list of constants
 */
GArray *
dnf_swdb_search (DnfSwdb *self, GPtrArray *patterns)
{
    if (dnf_swdb_open (self))
        return NULL;

    GArray *tids = g_array_new (0, 0, sizeof (gint));
    for (guint i = 0; i < patterns->len; ++i) {
        gchar *pattern = g_ptr_array_index (patterns, i);

        // try simple search based on package name
        GArray *simple = _simple_search (self->db, pattern);
        if (simple->len) {
            tids = g_array_append_vals (tids, simple->data, simple->len);
            g_array_free (simple, TRUE);
            continue;
        }
        g_array_free (simple, TRUE);
        // need for extended search - slower
        GArray *extended = _extended_search (self->db, pattern);
        if (extended) {
            tids = g_array_append_vals (tids, extended->data, extended->len);
        }
        g_array_free (extended, TRUE);
    }
    return tids;
}

/**
 * _pid_by_nevra:
 * @db: sqlite database handle
 * @nevra: string in format name-[epoch:]version-release.arch
 *
 * Get Package ID from SWDB by package nevra
 * Indexed nevra provides fast binding between other package objects
 *
 * Returns: id of package matched by @nevra
 **/
static gint
_pid_by_nevra (sqlite3 *db, const gchar *nevra)
{
    const gchar *sql = g_strrstr (nevra, ":") ? S_PID_BY_NEVRA : S_PID_BY_NVRA;
    sqlite3_stmt *res;
    DB_PREP (db, sql, res);
    DB_BIND (res, "@nevra", nevra);
    return DB_FIND (res);
}

/**
 * dnf_swdb_checksums:
 * @nevras: (element-type utf8) (transfer container): list of patterns
 *
 * Patch packages by name-[epoch:]version-release.arch and return list of their
 * checksums.
 * Output is in format:
 *   [nevra-1, checksum_type-1, checksum_data-1, nevra-2, checksum_type-2, checksum_data-2, ...]
 *
 * Used for performace optimization of rpmdb_version calculation in DNF
 *
 * Returns: (element-type utf8) (transfer container): list of checksums
 **/
GPtrArray *
dnf_swdb_checksums (DnfSwdb *self, GPtrArray *nevras)
{
    if (dnf_swdb_open (self))
        return NULL;
    gchar *buff1;
    gchar *buff2;
    gchar *nevra;

    GPtrArray *checksums = g_ptr_array_new ();
    const gchar *sql = S_CHECKSUM_BY_NEVRA;
    const gchar *sql2 = S_CHECKSUM_BY_NVRA;
    for (guint i = 0; i < nevras->len; i++) {
        // get nevra
        nevra = (gchar *)g_ptr_array_index (nevras, i);

        // save nevra to output list
        g_ptr_array_add (checksums, (gpointer) g_strdup (nevra)); // nevra

        sqlite3_stmt *res;
        if (g_strrstr (nevra, ":")) {
            DB_PREP (self->db, sql, res);
        } else {
            DB_PREP (self->db, sql2, res);
        }

        gboolean inserted = FALSE;

        DB_BIND (res, "@nevra", nevra);
        if (sqlite3_step (res) == SQLITE_ROW) {
            buff1 = (gchar *)sqlite3_column_text (res, 0);
            buff2 = (gchar *)sqlite3_column_text (res, 1);
            if (buff1 && buff2) {
                g_ptr_array_add (checksums, (gpointer) g_strdup (buff2)); // type
                g_ptr_array_add (checksums, (gpointer) g_strdup (buff1)); // data
                inserted = TRUE;
            }
        }
        sqlite3_finalize (res);

        if (!inserted) {
            // insert plain checksum data to keep the order correct
            g_ptr_array_add (checksums, NULL);
            g_ptr_array_add (checksums, NULL);
        }
    }
    return checksums;
}

/**
 * dnf_swdb_select_user_installed:
 * @nevras: (element-type utf8)(transfer container): list of patterns
 *
 * Match user installed packages by their name-[epoch:]version-release.arch
 * Return id's of user installed pacakges
 *
 * Example:
 *   @nevras = ["abc", "def", "ghi"]
 *   # "abc" and "ghi" are user installed packages
 *   Output:[0,2]
 *
 * Returns: (element-type gint32)(transfer container): list of matched indexes
 **/
GArray *
dnf_swdb_select_user_installed (DnfSwdb *self, GPtrArray *nevras)
{
    if (dnf_swdb_open (self))
        return NULL;

    GArray *usr_ids = g_array_new (0, 0, sizeof (gint));

    gint pid = 0;
    gint pdid = 0;
    DnfSwdbReason reason_id = DNF_SWDB_REASON_UNKNOWN;
    const gchar *sql = S_REASON_ID_BY_PDID;

    for (guint i = 0; i < nevras->len; ++i) {
        pid = _pid_by_nevra (self->db, (gchar *)g_ptr_array_index (nevras, i));
        if (!pid) {
            // better not uninstall package when not sure
            g_array_append_val (usr_ids, i);
            continue;
        }
        pdid = _pdid_from_pid (self->db, pid);
        if (!pdid) {
            g_array_append_val (usr_ids, i);
            continue;
        }
        sqlite3_stmt *res;
        DB_PREP (self->db, sql, res);
        DB_BIND_INT (res, "@pdid", pdid);
        gboolean push = TRUE;
        while (sqlite3_step (res) == SQLITE_ROW) {
            reason_id = sqlite3_column_int (res, 0);
            if (reason_id == DNF_SWDB_REASON_DEP || reason_id == DNF_SWDB_REASON_WEAK) {
                push = FALSE;
                continue;
            }
            push = TRUE;
            break;
        }
        // push even in case when no record is found
        if (push) {
            g_array_append_val (usr_ids, i);
        }
        sqlite3_finalize (res);
    }
    return usr_ids;
}

/**
 * dnf_swdb_user_installed:
 * @self: SWDB object
 * @nevra: string in format name-[epoch:]version-release.arch
 * Returns: %TRUE if package is user installed
 **/
gboolean
dnf_swdb_user_installed (DnfSwdb *self, const gchar *nevra)
{
    if (dnf_swdb_open (self))
        return TRUE;

    gint pid = _pid_by_nevra (self->db, nevra);
    if (!pid) {
        return FALSE;
    }
    gboolean rc = TRUE;
    gchar *repo = _repo_by_pid (self->db, pid);
    DnfSwdbReason reason = _reason_by_pid (self->db, pid);
    if (reason != DNF_SWDB_REASON_USER || !g_strcmp0 ("anakonda", repo)) {
        rc = FALSE;
    }
    g_free (repo);
    return rc;
}

/***************************** REPO PERSISTOR ********************************/

/**
 * _bind_repo_by_name:
 * @db: sqlite database handle
 * @name: repository name
 * Returns: repository ID
 **/
static gint
_bind_repo_by_name (sqlite3 *db, const gchar *name)
{
    sqlite3_stmt *res;
    const gchar *sql = FIND_REPO_BY_NAME;
    DB_PREP (db, sql, res);
    DB_BIND (res, "@name", name);
    gint rc = DB_FIND (res);
    if (rc) {
        return rc;
    }
    sql = INSERT_REPO;
    DB_PREP (db, sql, res);
    DB_BIND (res, "@name", name);
    DB_STEP (res);
    return sqlite3_last_insert_rowid (db);
}

/**
 * _repo_by_rid:
 * @db: sqlite database handle
 * @rid: repository ID
 * Returns: repository name
 **/
gchar *
_repo_by_rid (sqlite3 *db, gint rid)
{
    sqlite3_stmt *res;
    const gchar *sql = S_REPO_BY_RID;
    DB_PREP (db, sql, res);
    DB_BIND_INT (res, "@rid", rid);
    return DB_FIND_STR (res);
}

/**
 * _repo_by_pid:
 * @db: sqlite database handle
 * @pid: package id
 * Returns: repository name
 **/
gchar *
_repo_by_pid (sqlite3 *db, gint pid)
{
    sqlite3_stmt *res;
    const gchar *sql = S_REPO_FROM_PID2;
    DB_PREP (db, sql, res);
    DB_BIND_INT (res, "@pid", pid);
    return DB_FIND_STR (res);
}

/**
 * dnf_swdb_repo:
 * @self: SWDB object
 * @nevra: string in format name-[epoch:]version-release.arch
 * Returns: repository name
 **/
gchar *
dnf_swdb_repo (DnfSwdb *self, const gchar *nevra)
{
    if (dnf_swdb_open (self))
        return NULL;
    gint pid = _pid_by_nevra (self->db, nevra);
    if (!pid) {
        return g_strdup ("unknown");
    }
    return _repo_by_pid (self->db, pid);
}

/**
 * dnf_swdb_set_repo:
 * @self: SWDB object
 * @nevra: string in format name-[epoch:]version-release.arch
 * @repo: repository name
 *
 * Set source repository for package matched by @nevra
 *
 * Returns: 0 if source repository was set successfully
 **/
gint
dnf_swdb_set_repo (DnfSwdb *self, const gchar *nevra, const gchar *repo)
{
    if (dnf_swdb_open (self))
        return 1;
    gint pid = _pid_by_nevra (self->db, nevra);
    if (!pid) {
        return 1;
    }
    gint rid = _bind_repo_by_name (self->db, repo);
    sqlite3_stmt *res;
    const gchar *sql = U_REPO_BY_PID;
    DB_PREP (self->db, sql, res);
    DB_BIND_INT (res, "@rid", rid);
    DB_BIND_INT (res, "@pid", pid);
    DB_STEP (res);
    return 0;
}

/**************************** PACKAGE PERSISTOR ******************************/

/**
 * _package_insert:
 * @self: SWDB object
 * @package: package object
 *
 * Insert package object into swdb
 *
 * Returns: inserted package ID
 **/
static gint
_package_insert (DnfSwdb *self, DnfSwdbPkg *package)
{
    sqlite3_stmt *res;
    const gchar *sql = INSERT_PKG;
    DB_PREP (self->db, sql, res);
    DB_BIND (res, "@name", package->name);
    DB_BIND_INT (res, "@epoch", package->epoch);
    DB_BIND (res, "@version", package->version);
    DB_BIND (res, "@release", package->release);
    DB_BIND (res, "@arch", package->arch);
    DB_BIND (res, "@cdata", package->checksum_data);
    DB_BIND (res, "@ctype", package->checksum_type);
    DB_BIND_INT (res, "@type", package->type);
    DB_STEP (res);
    package->pid = sqlite3_last_insert_rowid (self->db);
    return package->pid;
}

/**
 * dnf_swdb_add_package:
 * @self: SWDB object
 * @pkg: package object
 *
 * Insert package into database
 *
 * Returns: inserted package ID or -1 if database error occurred
 **/
gint
dnf_swdb_add_package (DnfSwdb *self, DnfSwdbPkg *pkg)
{
    if (dnf_swdb_open (self))
        return -1;
    gint rc = _package_insert (self, pkg);
    return rc;
}

/**
 * dnf_swdb_update_package_data:
 * @self: SWDB object
 * @pid: package ID
 * @tid: transation ID
 * @pkgdata: package data object
 *
 * Insert or update pkgdata for package with ID @pid in transation @tid.
 * This method MUST be called after transaction data initialization by `dnf_swdb_trans_data_beg`
 *
 * Returns: 0 if successful
 **/
gint
dnf_swdb_update_package_data (DnfSwdb *self, gint pid, gint tid, DnfSwdbPkgData *pkgdata)
{
    if (dnf_swdb_open (self))
        return 1;

    gint rid = _bind_repo_by_name (self->db, pkgdata->from_repo);

    // look for old pid's package data - all values of autogenerated rows are null
    sqlite3_stmt *res;
    const gchar *sql = S_PREV_PKG_DATA;
    DB_PREP (self->db, sql, res);
    DB_BIND_INT (res, "@pid", pid);
    DB_BIND_INT (res, "@tid", tid);
    gint pdid = DB_FIND (res);

    if (!pdid) {
        // field is not available
        g_warning("Package %d is not available in transation %d", pid, tid);
        return 1;
    }

    sql = UPDATE_PKG_DATA;
    DB_PREP (self->db, sql, res);
    DB_BIND_INT (res, "@pid", pid);
    DB_BIND_INT (res, "@rid", rid);
    DB_BIND (res, "@repo_r", pkgdata->from_repo_revision);
    DB_BIND_INT (res, "@repo_t", pkgdata->from_repo_timestamp);
    DB_BIND (res, "@installed_by", pkgdata->installed_by);
    DB_BIND (res, "@changed_by", pkgdata->changed_by);
    DB_BIND (res, "@installonly", pkgdata->installonly);
    DB_STEP (res);
    return 0;
}

/**
 * _new_pdid_for_pid:
 * @db: sqlite database handle
 * @pid: package ID
 *
 * Create new record in PACKAGE_DATA table for @pid
 *
 * Returns: new pdid
 **/
static gint
_new_pdid_for_pid (sqlite3 *db, gint pid)
{
    sqlite3_stmt *res;
    const gchar *sql = INSERT_PDID;
    DB_PREP (db, sql, res);
    DB_BIND_INT (res, "@pid", pid);
    DB_STEP (res);
    return sqlite3_last_insert_rowid (db);
}

/**
 * _pdid_from_pid:
 * @db: sqlite database handle
 * @pid: package ID
 *
 * Get package data ID for package with ID @pid.
 * Create new package data record if unavailable.
 *
 * Returns: package data ID
 **/
gint
_pdid_from_pid (sqlite3 *db, gint pid)
{
    sqlite3_stmt *res;
    const gchar *sql = FIND_PDID_FROM_PID;
    DB_PREP (db, sql, res);
    DB_BIND_INT (res, "@pid", pid);
    gint rc = DB_FIND (res);
    if (rc)
        return rc;
    return _new_pdid_for_pid (db, pid);
}

/**
 * _pids_by_tid:
 * @db: sqlite database handle
 * @tid: transaction ID
 *
 * Get all package IDs connected with transation with ID @tid
 *
 * Returns: list od package IDs
 **/
static GArray *
_pids_by_tid (sqlite3 *db, gint tid)
{
    GArray *pids = g_array_new (0, 0, sizeof (gint));
    sqlite3_stmt *res;
    const gchar *sql = PID_BY_TID;
    DB_PREP (db, sql, res);
    DB_BIND_INT (res, "@tid", tid);
    gint pid;
    while ((pid = DB_FIND_MULTI (res))) {
        g_array_append_val (pids, pid);
    }
    return pids;
}

/**
 * _reason_by_pid:
 * @db: sqlite database handle
 * @pid: package ID
 *
 * Returns: reason id for package with @pid
 **/
DnfSwdbReason
_reason_by_pid (sqlite3 *db, gint pid)
{
    sqlite3_stmt *res;
    const gchar *sql = S_REASON_BY_PID;
    DB_PREP (db, sql, res);
    DB_BIND_INT (res, "@pid", pid);
    return DB_FIND (res);
}

/**
 * dnf_swdb_reason:
 * @self: SWDB object
 * @nevra: string in format name-[epoch:]version-release.arch
 *
 * Returns: reason ID for package matched by @nevra
 **/
DnfSwdbReason
dnf_swdb_reason (DnfSwdb *self, const gchar *nevra)
{
    DnfSwdbReason reason = DNF_SWDB_REASON_UNKNOWN;
    if (dnf_swdb_open (self))
        return reason;
    gint pid = _pid_by_nevra (self->db, nevra);
    if (pid) {
        reason = _reason_by_pid (self->db, pid);
    }
    return reason;
}

/**
 * _resolve_package_state:
 * @self: SWDB object
 * @pkg: package object
 * @tid: transation ID
 *
 * Set package state to match transaction given by @tid
 **/
static void
_resolve_package_state (DnfSwdb *self, DnfSwdbPkg *pkg, gint tid)
{
    sqlite3_stmt *res;
    const gchar *sql = S_PACKAGE_STATE;
    DB_PREP (self->db, sql, res);
    DB_BIND_INT (res, "@pid", pkg->pid);
    DB_BIND_INT (res, "@tid", tid);
    if (sqlite3_step (res) == SQLITE_ROW) {
        pkg->done = sqlite3_column_int (res, 0);
        gint state_code = sqlite3_column_int (res, 1);
        pkg->obsoleting = sqlite3_column_int (res, 2);
        sqlite3_finalize (res);
        pkg->state = _get_description (self->db, state_code, S_STATE_TYPE_BY_ID);
    } else {
        sqlite3_finalize (res);
    }
}

/**
 * _get_package_data_by_pid:
 * @db: sqlite database handle
 * @pid: package ID
 *
 * Returns: latest package data object for package with ID @pid
 **/
static DnfSwdbPkgData *
_get_package_data_by_pid (sqlite3 *db, gint pid)
{
    sqlite3_stmt *res;
    const gchar *sql = S_PACKAGE_DATA_BY_PID;
    DB_PREP (db, sql, res);
    DB_BIND_INT (res, "@pid", pid);
    if (sqlite3_step (res) == SQLITE_ROW) {
        DnfSwdbPkgData *pkgdata =
          dnf_swdb_pkgdata_new ((gchar *)sqlite3_column_text (res, 3), // from_repo_revision
                                sqlite3_column_int (res, 4), // from_repo_timestamp
                                (gchar *)sqlite3_column_text (res, 5), // installed_by
                                (gchar *)sqlite3_column_text (res, 6), // changed_by
                                (gchar *)sqlite3_column_text (res, 7), // installonly
                                NULL);                                 // from_repo
        pkgdata->pdid = sqlite3_column_int (res, 0); // pdid
        pkgdata->pid = pid;
        gint rid = sqlite3_column_int (res, 2);
        sqlite3_finalize (res);
        pkgdata->from_repo = _repo_by_rid (db, rid); // from repo
        return pkgdata;
    }
    sqlite3_finalize (res);
    return NULL;
}

/**
 * _get_package_by_pid:
 * @db: sqlite database handle
 * @pid: package ID
 *
 * Returns: package object for @pid
 **/
DnfSwdbPkg *
_get_package_by_pid (sqlite3 *db, gint pid)
{
    sqlite3_stmt *res;
    const gchar *sql = S_PACKAGE_BY_PID;
    DB_PREP (db, sql, res);
    DB_BIND_INT (res, "@pid", pid);
    if (sqlite3_step (res) == SQLITE_ROW) {
        DnfSwdbPkg *pkg = dnf_swdb_pkg_new ((gchar *)sqlite3_column_text (res, 1), // name
                                            sqlite3_column_int (res, 2),           // epoch
                                            (gchar *)sqlite3_column_text (res, 3), // version
                                            (gchar *)sqlite3_column_text (res, 4), // release
                                            (gchar *)sqlite3_column_text (res, 5), // arch
                                            (gchar *)sqlite3_column_text (res, 6), // checksum_data
                                            (gchar *)sqlite3_column_text (res, 7), // checksum_type
                                            sqlite3_column_int (res, 8));          // type
        pkg->pid = pid;
        sqlite3_finalize (res);
        return pkg;
    }
    sqlite3_finalize (res);
    return NULL;
}

/**
 * dnf_swdb_get_packages_by_tid:
 * @self: SWDB object
 * @tid: transation ID
 *
 * Get list of package object featured in transaction with ID @tid
 *
 * Returns: (element-type DnfSwdbPkg)(array)(transfer container): list of #DnfSwdbPkg
 */
GPtrArray *
dnf_swdb_get_packages_by_tid (DnfSwdb *self, gint tid)
{
    if (dnf_swdb_open (self))
        return NULL;

    GPtrArray *node = g_ptr_array_new ();
    GArray *pids = _pids_by_tid (self->db, tid);
    gint pid;
    DnfSwdbPkg *pkg;
    for (guint i = 0; i < pids->len; i++) {
        pid = g_array_index (pids, gint, i);
        pkg = _get_package_by_pid (self->db, pid);
        if (pkg) {
            pkg->swdb = self;
            _resolve_package_state (self, pkg, tid);
            g_ptr_array_add (node, (gpointer)pkg);
        }
    }
    g_array_free (pids, TRUE);
    return node;
}

/**
 * dnf_swdb_pid_by_nevra:
 * @self: SWDB object
 * @nevra: string in format name-[epoch:]version-release.arch
 *
 * Returns: ID of package matched by @nevra or 0
 **/
gint
dnf_swdb_pid_by_nevra (DnfSwdb *self, const gchar *nevra)
{
    if (dnf_swdb_open (self))
        return 0;
    gint pid = _pid_by_nevra (self->db, nevra);
    return pid;
}

/**
 * dnf_swdb_package:
 * @self: SWDB object
 * @nevra: string in format name-[epoch:]version-release.arch
 *
 * Returns: (transfer full): #DnfSwdbPkg matched by @nevra
 **/
DnfSwdbPkg *
dnf_swdb_package (DnfSwdb *self, const gchar *nevra)
{
    if (dnf_swdb_open (self))
        return NULL;
    gint pid = _pid_by_nevra (self->db, nevra);
    if (!pid) {
        return NULL;
    }
    DnfSwdbPkg *pkg = _get_package_by_pid (self->db, pid);
    pkg->swdb = self;
    return pkg;
}

/**
 * dnf_swdb_package_data:
 * @self: SWDB object
 * @nevra: string in format name-[epoch:]version-release.arch
 *
 * Returns: (transfer full): #DnfSwdbPkgData for package matched by @nevra
 **/
DnfSwdbPkgData *
dnf_swdb_package_data (DnfSwdb *self, const gchar *nevra)
{
    if (dnf_swdb_open (self))
        return NULL;
    gint pid = _pid_by_nevra (self->db, nevra);
    if (!pid) {
        return NULL;
    }
    DnfSwdbPkgData *pkgdata = _get_package_data_by_pid (self->db, pid);
    return pkgdata;
}

/**
 * _mark_pkg_as:
 * @db: sqlite database handle
 * @pdid: package data ID
 * @mark: reason ID
 *
 * Set reason in package data with ID @pdid to @mark
 **/
static void
_mark_pkg_as (sqlite3 *db, gint pdid, DnfSwdbReason mark)
{
    sqlite3_stmt *res;
    const gchar *sql = U_REASON_BY_PDID;
    DB_PREP (db, sql, res);
    DB_BIND_INT (res, "@reason", mark);
    DB_BIND_INT (res, "@pdid", pdid);
    DB_STEP (res);
}

/**
 * dnf_swdb_set_reason:
 * @self: SWDB object
 * @nevra: string in format name-[epoch:]version-release.arch
 * @reason: reason id
 *
 * Set reason of package matched by @nevra to @reason
 * Returns: 0 if successfull, else 1
 **/
gint
dnf_swdb_set_reason (DnfSwdb *self, const gchar *nevra, DnfSwdbReason reason)
{
    if (dnf_swdb_open (self))
        return 1;
    gint pid = _pid_by_nevra (self->db, nevra);
    if (!pid) {
        return 1;
    }
    gint pdid = _pdid_from_pid (self->db, pid);
    _mark_pkg_as (self->db, pdid, reason);
    return 0;
}

/**************************** RPM DATA PERSISTOR *****************************/

/**
 * dnf_swdb_add_rpm_data:
 * @self: SWDB object
 * @rpm_data: RPM data object
 *
 * Add @rpm_data to swdb
 *
 * Returns: 0 if successfull
 **/
gint
dnf_swdb_add_rpm_data (DnfSwdb *self, DnfSwdbRpmData *rpm_data)
{
    if (dnf_swdb_open (self))
        return 1;
    sqlite3_stmt *res;
    const gchar *sql = INSERT_RPM_DATA;
    DB_PREP (self->db, sql, res);
    DB_BIND_INT (res, "@pid", rpm_data->pid);
    DB_BIND_INT (res, "@buildtime", rpm_data->buildtime);
    DB_BIND (res, "@buildhost", rpm_data->buildhost);
    DB_BIND (res, "@license", rpm_data->license);
    DB_BIND (res, "@packager", rpm_data->packager);
    DB_BIND (res, "@size", rpm_data->size);
    DB_BIND (res, "@sourcerpm", rpm_data->sourcerpm);
    DB_BIND (res, "@url", rpm_data->url);
    DB_BIND (res, "@vendor", rpm_data->vendor);
    DB_BIND (res, "@committer", rpm_data->committer);
    DB_BIND_INT (res, "@committime", rpm_data->committime);
    DB_STEP (res);
    return 0;
}

/*************************** TRANS DATA PERSISTOR ****************************/

/**
 * _resolve_group_origin:
 * @db: sqlite database handler
 * @tid: transaction ID
 * @pid: package ID
 *
 * Get trans group data of package installed with group
 *
 * Returns: trans group data ID for package with ID @pid
 **/
static gint
_resolve_group_origin (sqlite3 *db, gint tid, gint pid)
{
    sqlite3_stmt *res;
    const gchar *sql = RESOLVE_GROUP_TRANS;
    DB_PREP (db, sql, res);
    DB_BIND_INT (res, "@pid", pid);
    DB_BIND_INT (res, "@tid", tid);
    return DB_FIND (res);
}

/**
 * dnf_swdb_trans_data_beg:
 * @self: SWDB object
 * @tid: transaction ID
 * @pid: package ID
 * @reason: reason ID
 * @state: state string
 *
 * Initialize transaction data for package with ID @pid in transaction @tid
 *
 * If package was installed with group, create binding between transaction data
 *   and transaction group data
 *
 * Returns: 0 if successfull
 **/
gint
dnf_swdb_trans_data_beg (DnfSwdb *self,
                         gint tid,
                         gint pid,
                         DnfSwdbReason reason,
                         const gchar *state,
                         gint obsoleting)
{
    if (dnf_swdb_open (self))
        return 1;

    // generate new empty package data entry
    gint pdid = _new_pdid_for_pid (self->db, pid);

    // resolve group orogin
    gint tgid = 0;
    if (reason == DNF_SWDB_REASON_GROUP) {
        tgid = _resolve_group_origin (self->db, tid, pid);
    }

    // resolve state
    gint state_code = dnf_swdb_get_state_type (self, state);

    // insert new entry
    sqlite3_stmt *res;
    const gchar *sql = INSERT_TRANS_DATA_BEG;
    DB_PREP (self->db, sql, res);
    DB_BIND_INT (res, "@tid", tid);
    DB_BIND_INT (res, "@pdid", pdid);
    DB_BIND_INT (res, "@tgid", tgid);
    DB_BIND_INT (res, "@obsoleting", obsoleting);
    DB_BIND_INT (res, "@reason", reason);
    DB_BIND_INT (res, "@state", state_code);
    DB_STEP (res);

    return 0;
}

/**
 * dnf_swdb_trans_data_pid_end:
 * @self: SWDB object
 * @pid: package ID
 * @tid: transaction ID
 * @state: state string
 *
 * Finalize transaction for package with ID @pid
 *
 * Returns: 0 if successfull
 **/
gint
dnf_swdb_trans_data_pid_end (DnfSwdb *self, gint pid, gint tid, const gchar *state)
{
    if (dnf_swdb_open (self))
        return 1;

    gint pdid = _pdid_from_pid (self->db, pid);
    gint state_code = dnf_swdb_get_state_type (self, state);

    sqlite3_stmt *res;
    const gchar *sql = UPDATE_TRANS_DATA_PID_END;
    DB_PREP (self->db, sql, res);
    DB_BIND_INT (res, "@done", 1);
    DB_BIND_INT (res, "@tid", tid);
    DB_BIND_INT (res, "@pdid", pdid);
    DB_BIND_INT (res, "@state", state_code);
    DB_STEP (res);
    return 0;
}

/****************************** TRANS PERSISTOR ******************************/

/**
 * dnf_swdb_trans_beg:
 * @self: SWDB object
 * @timestamp: transaction timestamp
 * @rpmdb_version: rpmdb_version before transaction
 * @cmdline: command which initiated transaction
 * @loginuid: ID of user which initiated transaction
 * @releasever: release version of system
 *
 * Initialize transaction
 *
 * Returns: transaction ID or -1 if error occurred
 **/
gint
dnf_swdb_trans_beg (DnfSwdb *self,
                    gint64 timestamp,
                    const gchar *rpmdb_version,
                    const gchar *cmdline,
                    const gchar *loginuid,
                    const gchar *releasever)
{
    if (dnf_swdb_open (self))
        return -1;

    sqlite3_stmt *res;
    const gchar *sql = INSERT_TRANS_BEG;
    DB_PREP (self->db, sql, res);
    DB_BIND_INT (res, "@beg", timestamp);
    DB_BIND (res, "@rpmdbv", rpmdb_version);
    DB_BIND (res, "@cmdline", cmdline);
    DB_BIND (res, "@loginuid", loginuid);
    DB_BIND (res, "@releasever", releasever);
    DB_STEP (res);
    gint rc = sqlite3_last_insert_rowid (self->db);
    return rc;
}

/**
 * dnf_swdb_trans_end:
 * @self: SWDB object
 * @tid: transaction ID
 * @end_timestamp: end timestamp
 * @end_rpmdb_version: rpmdb version after transaction
 * @return_code: return code of transaction
 *
 * Finalize transaction
 *
 * Returns: 0 if successfull
 **/
gint
dnf_swdb_trans_end (DnfSwdb *self,
                    gint tid,
                    gint64 end_timestamp,
                    const gchar *end_rpmdb_version,
                    gint return_code)
{
    if (dnf_swdb_open (self))
        return 1;
    sqlite3_stmt *res;
    const gchar *sql = INSERT_TRANS_END;
    DB_PREP (self->db, sql, res);
    DB_BIND_INT (res, "@tid", tid);
    DB_BIND_INT (res, "@end", end_timestamp);
    DB_BIND (res, "@rpmdbv", end_rpmdb_version);
    DB_BIND_INT (res, "@rc", return_code);
    DB_STEP (res);
    return 0;
}

/**
 * dnf_swdb_trans_with:
 * @self: SWDB object
 * @tid: transaction ID
 * @pid: package ID
 *
 * Log transaction perforemed with package
 **/
void
dnf_swdb_trans_with (DnfSwdb *self, int tid, int pid)
{
    if (dnf_swdb_open (self)) {
        return;
    }
    sqlite3_stmt *res;
    const gchar *sql = I_TRANS_WITH;
    DB_PREP (self->db, sql, res);
    DB_BIND_INT (res, "@tid", tid);
    DB_BIND_INT (res, "@pid", pid);
    DB_STEP (res);
}

/**
 * dnf_swdb_trans_with_libdnf:
 * @self: SWDB object
 * @tid: transaction ID
 *
 * Log transaction perforemed with libdnf
 **/
void
dnf_swdb_trans_with_libdnf (DnfSwdb *self, int tid)
{
    if (dnf_swdb_open (self)) {
        return;
    }
    sqlite3_stmt *res;
    const gchar *sql = S_LATEST_PACKAGE;
    DB_PREP (self->db, sql, res);
    DB_BIND (res, "@name", "libdnf");
    gint pid = DB_FIND (res);
    if (pid) {
        dnf_swdb_trans_with (self, tid, pid);
    }
    DB_PREP (self->db, sql, res);
    DB_BIND (res, "@name", "rpm");
    pid = DB_FIND (res);
    if (pid) {
        dnf_swdb_trans_with (self, tid, pid);
    }
}

/**
 * dnf_swdb_trans_cmdline:
 * @self: SWDB object
 * @tid: transaction ID
 *
 * Returns: command line which initiated transaction @tid
 **/
gchar *
dnf_swdb_trans_cmdline (DnfSwdb *self, gint tid)
{
    if (dnf_swdb_open (self))
        return NULL;
    sqlite3_stmt *res;
    const gchar *sql = GET_TRANS_CMDLINE;
    DB_PREP (self->db, sql, res);
    DB_BIND_INT (res, "@tid", tid);
    gchar *cmdline = DB_FIND_STR (res);
    return cmdline;
}

/**
 * _resolve_altered:
 * @trans: list of #DnfSwdbTrans
 *
 * Resolve if rpmdb altered between transactions
 **/
static void
_resolve_altered (GPtrArray *trans)
{
    for (guint i = 0; i < (trans->len - 1); i++) {
        DnfSwdbTrans *las = (DnfSwdbTrans *)g_ptr_array_index (trans, i);
        DnfSwdbTrans *obj = (DnfSwdbTrans *)g_ptr_array_index (trans, i + 1);

        if (!las->end_rpmdb_version || !obj->beg_rpmdb_version)
            continue;

        if (g_strcmp0 (obj->end_rpmdb_version, las->beg_rpmdb_version)) {
            obj->altered_gt_rpmdb = 1;
            las->altered_lt_rpmdb = 1;
        } else {
            las->altered_lt_rpmdb = 0;
            obj->altered_gt_rpmdb = 0;
        }
    }
}

/**
 * _test_output:
 * @self: SWDB object
 * @tid: transaction ID
 * @o_type: output type string
 *
 * Returns: %TRUE if transaction has output of type @o_type
 **/
static gboolean
_test_output (DnfSwdb *self, gint tid, const gchar *o_type)
{
    gint type = dnf_swdb_get_output_type (self, o_type);
    sqlite3_stmt *res;
    const gchar *sql = T_OUTPUT;
    DB_PREP (self->db, sql, res);
    DB_BIND_INT (res, "@tid", tid);
    DB_BIND_INT (res, "@type", type);
    if (sqlite3_step (res) == SQLITE_ROW) {
        sqlite3_finalize (res);
{'fraction_non_alphanumeric': 0.1257388278185936, 'fraction_numerical': 0.005910622548748826, 'mean_word_length': 2.8246976179158083, 'pattern_counts': {'":': 4, '<': 19, '<?xml version=': 0, '>': 357, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 30, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1630479', 'n_tokens_mistral': 27025, 'n_tokens_neox': 26285, 'n_words': 6661}