/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
 
#include <com/sun/star/embed/XStorage.hpp>
#include <com/sun/star/embed/ElementModes.hpp>
#include <com/sun/star/embed/XTransactedObject.hpp>
#include <com/sun/star/uno/Exception.hpp>
#include <com/sun/star/io/XInputStream.hpp>
#include <com/sun/star/io/WrongFormatException.hpp>
#include <com/sun/star/util/Date.hpp>
 
#include <com/sun/star/sdbc/XConnection.hpp>
#include <com/sun/star/sdbc/XParameters.hpp>
#include <com/sun/star/sdbc/DataType.hpp>
#include <com/sun/star/sdbc/SQLException.hpp>
 
#include <rtl/ustrbuf.hxx>
#include <sal/log.hxx>
#include <connectivity/dbtools.hxx>
#include <connectivity/dbexception.hxx>
#include <comphelper/processfactory.hxx>
 
#include "hsqlimport.hxx"
#include "parseschema.hxx"
#include "rowinputbinary.hxx"
 
namespace
{
using namespace css::io;
using namespace css::uno;
using namespace css::sdbc;
 
using ColumnTypeVector = std::vector<dbahsql::ColumnDefinition>;
using RowVector = std::vector<Any>;
using IndexVector = std::vector<sal_Int32>;
 
void lcl_setParams(const RowVector& row, Reference<XParameters> const& xParam,
                   const ColumnTypeVector& rColTypes)
{
    assert(row.size() == rColTypes.size());
    size_t nColIndex = 0;
    for (size_t i = 0; i < rColTypes.size(); ++i)
    {
        if (!row.at(i).hasValue())
            xParam->setNull(i + 1, rColTypes.at(i).getDataType());
 
        switch (rColTypes.at(i).getDataType())
        {
            case DataType::CHAR:
            case DataType::VARCHAR:
            case DataType::LONGVARCHAR:
            {
                OUString sVal;
                if (row.at(i) >>= sVal)
                {
                    xParam->setString(nColIndex + 1, sVal);
                }
            }
            break;
            case DataType::TINYINT:
            case DataType::SMALLINT:
            {
                sal_Int16 nVal;
                if (row.at(i) >>= nVal)
                {
                    xParam->setShort(nColIndex + 1, nVal);
                }
            }
            break;
            case DataType::INTEGER:
            {
                sal_Int32 nVal;
                if (row.at(i) >>= nVal)
                {
                    xParam->setInt(nColIndex + 1, nVal);
                }
            }
            break;
            case DataType::BIGINT:
            {
                sal_Int64 nVal;
                if (row.at(i) >>= nVal)
                {
                    xParam->setLong(nColIndex + 1, nVal);
                }
            }
            break;
            case DataType::REAL:
            case DataType::FLOAT:
            case DataType::DOUBLE:
            {
                double nVal;
                if (row.at(i) >>= nVal)
                {
                    xParam->setDouble(nColIndex + 1, nVal);
                }
            }
            break;
            case DataType::NUMERIC:
            case DataType::DECIMAL:
            {
                Sequence<Any> aNumeric;
                if (row.at(i) >>= aNumeric)
                {
                    sal_Int32 nScale = 0;
                    if (aNumeric[1] >>= nScale)
                        xParam->setObjectWithInfo(nColIndex + 1, aNumeric[0],
                                                  rColTypes.at(i).getDataType(), nScale);
                }
            }
            break;
            case DataType::DATE:
            {
                css::util::Date date;
                if (row.at(i) >>= date)
                {
                    xParam->setDate(nColIndex + 1, date);
                }
            }
            break;
            case DataType::TIME:
            {
                css::util::Time time;
                if (row.at(i) >>= time)
                {
                    xParam->setTime(nColIndex + 1, time);
                }
            }
            break;
            case DataType::TIMESTAMP:
            {
                css::util::DateTime dateTime;
                if (row.at(i) >>= dateTime)
                {
                    xParam->setTimestamp(nColIndex + 1, dateTime);
                }
            }
            break;
            case DataType::BOOLEAN:
            {
                bool bVal = false;
                if (row.at(i) >>= bVal)
                    xParam->setBoolean(nColIndex + 1, bVal);
            }
            break;
            case DataType::OTHER:
                // TODO
                break;
            case DataType::BINARY:
            case DataType::VARBINARY:
            case DataType::LONGVARBINARY:
            {
                Sequence<sal_Int8> nVal;
                if (row.at(i) >>= nVal)
                {
                    xParam->setBytes(nColIndex + 1, nVal);
                }
                break;
            }
            default:
                throw WrongFormatException();
        }
        ++nColIndex;
    }
}
 
OUString lcl_createInsertStatement(const OUString& sTableName, const ColumnTypeVector& rColTypes)
{
    assert(rColTypes.size() > 0);
    OUStringBuffer sql("INSERT INTO ");
    sql.append(sTableName);
    sql.append(" (");
 
    // column names
    for (size_t i = 0; i < rColTypes.size(); ++i)
    {
        sql.append(rColTypes.at(i).getName());
        if (i < rColTypes.size() - 1)
            sql.append(", ");
    }
    sql.append(")");
 
    sql.append(" VALUES (");
    for (size_t i = 0; i < rColTypes.size(); ++i)
    {
        sql.append("?");
        if (i < rColTypes.size() - 1)
            sql.append(", ");
    }
    sql.append(")");
    return sql.makeStringAndClear();
}
 
} // unnamed namespace
 
namespace dbahsql
{
using namespace css::embed;
 
HsqlImporter::HsqlImporter(Reference<XConnection>& rConnection, const Reference<XStorage>& rStorage)
    : m_rConnection(rConnection)
{
    m_xStorage.set(rStorage);
}
 
void HsqlImporter::insertRow(const RowVector& xRows, const OUString& sTableName,
                             const ColumnTypeVector& rColTypes)
{
    OUString sStatement = lcl_createInsertStatement(sTableName, rColTypes);
    Reference<XPreparedStatement> xStatement = m_rConnection->prepareStatement(sStatement);
 
    Reference<XParameters> xParameter(xStatement, UNO_QUERY);
    assert(xParameter.is());
    xParameter->clearParameters();
 
    lcl_setParams(xRows, xParameter, rColTypes);
    xStatement->executeQuery();
}
 
void HsqlImporter::processTree(HsqlBinaryNode& rNode, HsqlRowInputStream& rStream,
                               const ColumnTypeVector& rColTypes, const OUString& sTableName,
                               sal_Int32 nIndexCount)
{
    rNode.readChildren(rStream);
    sal_Int32 nNext = rNode.getLeft();
    if (nNext > 0)
    {
        HsqlBinaryNode aLeft{ nNext };
        processTree(aLeft, rStream, rColTypes, sTableName, nIndexCount);
    }
    std::vector<Any> row = rNode.readRow(rStream, rColTypes, nIndexCount);
    insertRow(row, sTableName, rColTypes);
 
    nNext = rNode.getRight();
    if (nNext > 0)
    {
        HsqlBinaryNode aRight{ nNext };
        processTree(aRight, rStream, rColTypes, sTableName, nIndexCount);
    }
}
 
/**
 * Format from the indexed file position is the following:
 * <Node x20><Row>
 * Where Node is a 20 byte data, representing the rows in a binary tree:
 * <Size x4><Balance x4><Left x4> <Right x4><Parent x4>
 *
 * Size is the size of <Row>;
 * Balance: ?
 * Left/Right/Parent: File position of the Left/Right/Parent child
 */
void HsqlImporter::parseTableRows(const IndexVector& rIndexes,
                                  const std::vector<ColumnDefinition>& rColTypes,
                                  const OUString& sTableName)
{
    constexpr char BINARY_FILENAME[] = "data";
 
    if (!m_xStorage->hasByName(BINARY_FILENAME))
    {
        SAL_WARN("dbaccess", "data file does not exist in storage during hsqldb import");
        assert(false); // TODO throw error
    }
 
    Reference<css::io::XStream> xStream(
        m_xStorage->openStreamElement(BINARY_FILENAME, ElementModes::READ));
 
    HsqlRowInputStream rowInput;
    Reference<XInputStream> xInput = xStream->getInputStream();
    rowInput.setInputStream(xInput);
 
    if (rIndexes.size() > 0)
    {
        HsqlBinaryNode aPrimaryNode{ rIndexes.at(0) };
        processTree(aPrimaryNode, rowInput, rColTypes, sTableName, rIndexes.size());
    }
 
    xInput->closeInput();
}
 
void HsqlImporter::importHsqlDatabase()
{
    assert(m_xStorage);
 
    SchemaParser parser(m_xStorage);
    std::unique_ptr<SQLException> pException;
    try
    {
        parser.parseSchema();
    }
    catch (SQLException& ex)
    {
        // chain errors and keep going
        if (pException)
            ex.NextException <<= *pException;
        pException.reset(new SQLException{ std::move(ex) });
    }
 
    auto statements = parser.getCreateStatements();
 
    if (statements.size() < 1 && !pException)
    {
        SAL_WARN("dbaccess", "dbashql: there is nothing to import");
        return; // there is nothing to import
    }
 
    // schema
    for (auto& sSql : statements)
    {
        Reference<XStatement> statement = m_rConnection->createStatement();
        try
        {
            statement->executeQuery(sSql);
        }
        catch (SQLException& ex)
        {
            if (pException)
                ex.NextException <<= *pException;
            pException.reset(new SQLException{ std::move(ex) });
        }
    }
 
    // data
    for (const auto& tableIndex : parser.getTableIndexes())
    {
        std::vector<ColumnDefinition> aColTypes = parser.getTableColumnTypes(tableIndex.first);
        try
        {
            parseTableRows(tableIndex.second, aColTypes, tableIndex.first);
        }
        catch (SQLException& ex)
        {
            if (pException)
                ex.NextException <<= *pException;
            pException.reset(new SQLException{ std::move(ex) });
        }
    }
 
    // alter stmts
    for (const auto& sSql : parser.getAlterStatements())
    {
        Reference<XStatement> statement = m_rConnection->createStatement();
        try
        {
            statement->executeQuery(sSql);
        }
        catch (SQLException& ex)
        {
            if (pException)
                ex.NextException <<= *pException;
            pException.reset(new SQLException{ std::move(ex) });
        }
    }
 
    if (pException)
    {
        SAL_WARN("dbaccess", "Error during migration");
        dbtools::showError(dbtools::SQLExceptionInfo{ *pException }, nullptr,
                           ::comphelper::getProcessComponentContext());
    }
}
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V614 The 'pException' smart pointer is utilized immediately after being declared or reset. It is suspicious that no value was assigned to it.

V547 Expression 'pException' is always false.