Program Listing for File cql.hpp

Return to documentation for file (cif++/cql.hpp)

/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2025  NKI/AVL, Netherlands Cancer Institute
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#pragma once


#include "cif++/category.hpp"
#include "cif++/item.hpp"
#include "cif++/iterator.hpp"
#include "cif++/row.hpp"

#include <cstddef>
#include <cstdint>
#include <iterator>
#include <memory>
#include <ostream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>

// --------------------------------------------------------------------

namespace cif::cql
{

class connection;
struct result_impl;

// --------------------------------------------------------------------

class field_ref final
{
  public:
    [[nodiscard]] std::string_view name() const &
    {
        return m_row.get_category().get_item_name(m_index);
    }

    [[nodiscard]] constexpr size_t num() const noexcept
    {
        return m_index;
    }

    template <typename T = std::string>
    [[nodiscard]] auto get() const -> T
    {
        return m_row[m_index].get<T>();
    }

    [[nodiscard]] bool is_null() const
    {
        return m_row[m_index].is_null();
    }

    template <typename T>
    auto value_or(const T &dv) const
    {
        return m_row[m_index].value_or(dv);
    }

    field_ref(const_row_handle rh, uint16_t col, std::shared_ptr<result_impl> result_impl)
        : m_row(std::move(rh))
        , m_index(col)
        , m_result_impl(std::move(result_impl))
    {
    }

    field_ref(const field_ref &) = default;
    field_ref(field_ref &&) = default;

    field_ref &operator=(const field_ref &) = default;
    field_ref &operator=(field_ref &&) = default;

  private:
    const_row_handle m_row;
    uint16_t m_index;

    std::shared_ptr<result_impl> m_result_impl;
};

// --------------------------------------------------------------------

class row_ref final
{
  public:
    class const_field_iterator
    {
      public:
        friend class result;

        using iterator_category = std::forward_iterator_tag;
        using value_type = const field_ref;
        using difference_type = std::ptrdiff_t;
        using pointer = value_type *;
        using reference = value_type &;

        const_field_iterator(const const_field_iterator &) = default;
        const_field_iterator(const_field_iterator &&) = default;

        const_field_iterator &operator=(const const_field_iterator &) = default;
        const_field_iterator &operator=(const_field_iterator &&) = default;

        reference operator*()
        {
            return m_current;
        }

        pointer operator->()
        {
            return &m_current;
        }

        const_field_iterator &operator++()
        {
            if (m_row)
            {
                ++m_col;
                m_current = field_ref(m_row, m_col, m_result_impl);
            }

            return *this;
        }

        const_field_iterator operator++(int)
        {
            const_field_iterator result(*this);
            this->operator++();
            return result;
        }

        bool operator==(const const_field_iterator &rhs) const
        {
            return m_row == rhs.m_row and m_col == rhs.m_col;
        }

        bool operator!=(const const_field_iterator &rhs) const
        {
            return m_row != rhs.m_row or m_col != rhs.m_col;
        }

      private:
        friend class row_ref;

        const_field_iterator(const_row_handle row, uint16_t column, std::shared_ptr<result_impl> result_impl)
            : m_row(std::move(row))
            , m_col(column)
            , m_current(m_row, m_col, result_impl)
            , m_result_impl(result_impl)
        {
        }

        const_row_handle m_row;
        uint16_t m_col;
        field_ref m_current;

        std::shared_ptr<result_impl> m_result_impl;

    };

    // --------------------------------------------------------------------

    row_ref() = default;

    row_ref(const_row_handle rh, std::shared_ptr<result_impl> result_impl)
        : m_row(std::move(rh))
        , m_result_impl(std::move(result_impl))
    {
    }

    row_ref(const row_ref &) = default;
    row_ref &operator=(const row_ref &) = default;

    // --------------------------------------------------------------------

    [[nodiscard]] const_field_iterator begin() const noexcept { return { m_row, 0, m_result_impl }; }
    [[nodiscard]] const_field_iterator cbegin() const noexcept { return { m_row, 0, m_result_impl }; }
    [[nodiscard]] const_field_iterator end() const noexcept { return { m_row, static_cast<uint16_t>(size()), m_result_impl }; }
    [[nodiscard]] const_field_iterator cend() const noexcept { return { m_row, static_cast<uint16_t>(size()), m_result_impl }; }

    [[nodiscard]] field_ref front() const noexcept { return { m_row, 0, m_result_impl }; }
    [[nodiscard]] field_ref back() const noexcept { return { m_row, static_cast<uint16_t>(size() - 1), m_result_impl }; }

    [[nodiscard]] size_t size() const noexcept;
    [[nodiscard]] bool empty() const noexcept { return size() == 0; }

    [[nodiscard]] field_ref operator[](uint16_t index) const noexcept { return { m_row, index, m_result_impl }; }
    [[nodiscard]] field_ref operator[](std::string_view name) const;

    // --------------------------------------------------------------------

    bool operator==(const row_ref &rhs) const { return m_row == rhs.m_row; }
    bool operator!=(const row_ref &rhs) const { return m_row != rhs.m_row; }

  private:
    const_row_handle m_row;
    std::shared_ptr<result_impl> m_result_impl;

};

// --------------------------------------------------------------------

class result
{
  public:
    // --------------------------------------------------------------------

    class iterator
    {
      public:
        friend class view;


        using iterator_category = std::forward_iterator_tag;
        using value_type = const row_ref;
        using difference_type = std::ptrdiff_t;
        using pointer = value_type *;
        using reference = value_type &;

        // const_row_iterator() = default;

        iterator(std::shared_ptr<result_impl> result_impl, category::const_iterator cat_iter)
            : m_iter(std::move(cat_iter))
            , m_current(*m_iter, result_impl)
            , m_result_impl(result_impl)
        {
        }

        iterator(const iterator &) = default;
        iterator(iterator &&) = default;

        // const_row_iterator &operator=(const const_row_iterator &) = default;
        // const_row_iterator &operator=(const_row_iterator &&) = default;

        reference operator*()
        {
            return m_current;
        }

        pointer operator->()
        {
            return &m_current;
        }

        iterator &operator++()
        {
            ++m_iter;
            m_current = { *m_iter, m_result_impl };
            return *this;
        }

        iterator operator++(int)
        {
            iterator result(*this);
            this->operator++();
            return result;
        }

        bool operator==(const iterator &rhs) const
        {
            return m_result_impl == rhs.m_result_impl and m_iter == rhs.m_iter;
        }

        bool operator!=(const iterator &rhs) const
        {
            return m_result_impl != rhs.m_result_impl or m_iter != rhs.m_iter;
        }

      private:
        category::const_iterator m_iter;
        row_ref m_current;
        std::shared_ptr<result_impl> m_result_impl;

    };

    // --------------------------------------------------------------------

    result() = delete;
    result(result const &rhs) noexcept = default;
    result(result &&rhs) noexcept = default;
    result &operator=(result const &rhs) noexcept = default;
    result &operator=(result &&rhs) noexcept = default;

    result(category &&data, const std::string &query = "");

    ~result() = default;

    [[nodiscard]] row_ref one_row() const
    {
        if (size() != 1)
            throw std::runtime_error("Expected one row");
        return front();
    }

    [[nodiscard]] field_ref one_field() const
    {
        expect_columns(1);

        if (size() != 1)
            throw std::runtime_error("Expected one row");

        return one_row().front();
    }

    // --------------------------------------------------------------------

    [[nodiscard]] iterator begin() const noexcept;
    [[nodiscard]] iterator cbegin() const noexcept;

    [[nodiscard]] iterator end() const noexcept;
    [[nodiscard]] iterator cend() const noexcept;

    [[nodiscard]] row_ref front() const;
    [[nodiscard]] row_ref back() const;

    [[nodiscard]] size_t size() const noexcept;
    [[nodiscard]] bool empty() const noexcept { return size() == 0; }

    [[nodiscard]] size_t column_count() const;

    [[nodiscard]] category &get_category() const;

    void expect_columns(size_t cols) const
    {
        if (auto actual = column_count(); size() > 0 and cols != actual)
            throw std::runtime_error("Unexpected number of columns");
    }

    // --------------------------------------------------------------------

    friend std::ostream &operator<<(std::ostream &os, const result &r)
    {
        os << r.get_category();
        return os;
    }

  private:
    friend class transaction;
    friend class SelectStatement;

    std::shared_ptr<result_impl> m_impl;
};

// --------------------------------------------------------------------

template <typename... Ts>
class cql_iterator_proxy : public cif::iterator_proxy<Ts...>
{
  public:
    cql_iterator_proxy(result &&res)
        : cif::iterator_proxy<Ts...>(res.get_category())
        , m_result(std::forward<result>(res))
    {
        m_result.expect_columns(cif::iterator_proxy<Ts...>::N);
    }

  private:
    result m_result;
};

// --------------------------------------------------------------------

class transaction final
{
  public:
    transaction(connection &conn);

    ~transaction();

    transaction(const transaction &) = delete;
    transaction &operator=(const transaction &) = delete;

    result exec(std::string query);

    result exec(std::string query, std::string &tail);

    template <typename... Ts>
    cql_iterator_proxy<Ts...> stream(const std::string &sql)
    {
        return cql_iterator_proxy<Ts...>{ exec(sql) };
    }

    void commit();

    void rollback();

  private:
    connection &m_conn;
    bool m_transaction_active = false;
};

// --------------------------------------------------------------------

class connection final
{
  public:
    connection(datablock &db);

    ~connection();

    friend class transaction;

    [[nodiscard]] bool is_complete_statement(const std::string &sql) const;

    result exec(std::string query);

    result exec(std::string query, std::string &tail);

    [[nodiscard]] bool is_modified() const;

  private:
    struct connection_impl *m_impl;
};

} // namespace cif::cql