<commit_before><?php
/*"******************************************************************************************************
*   (c) 2004-2006 by MulchProductions, www.mulchprod.de                                                 *
*   (c) 2007-2016 by Kajona, www.kajona.de                                                              *
*       Published under the GNU LGPL v2.1, see /system/licence_lgpl.txt                                 *
********************************************************************************************************/

namespace Kajona\System\System\Db;

use Kajona\System\System\Database;
use Kajona\System\System\DbDatatypes;
use Kajona\System\System\Exception;
use Kajona\System\System\Filesystem;
use SQLite3;
use SQLite3Stmt;


/**
 * db-driver for sqlite3 using the php-sqlite3-interface.
 * Based on the sqlite2 driver by phwolfer
 *
 * @since 3.3.0.1
 * @author <EMAIL>
 * @package module_system
 */
class DbSqlite3 extends DbBase
{

    /**
     * @var SQLite3
     */
    private $linkDB;
    private $strDbFile;

    /**
     * This method makes sure to connect to the database properly
     *
     * @param string $strHost
     * @param string $strUsername
     * @param string $strPass
     * @param string $strDbName
     * @param int $intPort
     *
     * @throws Exception
     * @return bool
     */
    public function dbconnect($strHost, $strUsername, $strPass, $strDbName, $intPort)
    {

        if ($strDbName == "") {
            return false;
        }

        $this->strDbFile = _projectpath_.'/dbdumps/'.$strDbName.'.db3';

        try {


            $strPath = _realpath_.$this->strDbFile;
            /*if(defined("_autotesting_")) {
                $strPath = ":memory:";
            }*/
            $this->linkDB = new SQLite3($strPath);
            $this->_pQuery('PRAGMA encoding = "UTF-8"', array());
            //TODO deprecated in sqlite, so may be removed
            $this->_pQuery('PRAGMA short_column_names = ON', array());
            $this->_pQuery("PRAGMA journal_mode = TRUNCATE", array());
            if (method_exists($this->linkDB, "busyTimeout")) {
                $this->linkDB->busyTimeout(5000);
            }

            return true;
        }
        catch (Exception $e) {
            throw new Exception("Error connecting to database: ".$e, Exception::$level_FATALERROR);
        }

    }

    /**
     * Closes the connection to the database
     *
     * @return void
     */
    public function dbclose()
    {
        $this->linkDB->close();
    }


    private function buildAndCopyTempTables($strTargetTableName, $arrSourceTableInfo, $arrTargetTableInfo)
    {
        $bitReturn = true;

        $arrSourceColumns = array();
        array_walk($arrSourceTableInfo, function ($arrValue) use (&$arrSourceColumns) {
            $arrSourceColumns[] = $arrValue["columnName"];
        });

        $arrTargetColumns = array();
        array_walk($arrTargetTableInfo, function ($arrValue) use (&$arrTargetColumns) {
            $arrTargetColumns[] = $arrValue["columnName"];
        });


        //build the a temp table
        $strQuery = "CREATE TABLE ".$strTargetTableName."_temp ( \n";
        //loop the fields
        foreach ($arrTargetTableInfo as $intI => $arrOneColumn) {
            $strQuery .= " ".$arrOneColumn["columnName"]." ".$arrOneColumn["columnType"];
            //nullable?
            if ($intI == 0) {
                $strQuery .= " NOT NULL, \n";
            }
            else {
                $strQuery .= ", \n";
            }

        }

        //primary keys
        $strQuery .= " PRIMARY KEY (".$arrTargetTableInfo[0]["columnName"].") \n";
        $strQuery .= ") ";

        $bitReturn = $bitReturn && $this->_pQuery($strQuery, array());

        //copy all values
        $strQuery = "INSERT INTO ".$strTargetTableName."_temp (".implode(",", $arrTargetColumns).") SELECT ".implode(",", $arrSourceColumns)." FROM ".$strTargetTableName;
        $bitReturn = $bitReturn && $this->_pQuery($strQuery, array());

        $strQuery = "DROP TABLE ".$strTargetTableName;
        $bitReturn = $bitReturn && $this->_pQuery($strQuery, array());

        return $bitReturn && $this->renameTable($strTargetTableName."_temp", $strTargetTableName);
    }


    /**
     * Renames a single column of the table
     *
     * @param $strTable
     * @param $strOldColumnName
     * @param $strNewColumnName
     * @param $strNewDatatype
     *
     * @return bool
     * @since 4.6
     */
    public function changeColumn($strTable, $strOldColumnName, $strNewColumnName, $strNewDatatype)
    {

        $arrTableInfo = $this->getColumnsOfTable($strTable);
        $arrTargetTableInfo = array();
        foreach ($arrTableInfo as $arrOneColumn) {
            if ($arrOneColumn["columnName"] == $strOldColumnName) {
                $arrNewRow = array(
                    "columnName" => $strNewColumnName,
                    "columnType" => $this->getDatatype($strNewDatatype)
                );

                $arrTargetTableInfo[] = $arrNewRow;
            }
            else {
                $arrTargetTableInfo[] = $arrOneColumn;
            }

        }

        return $this->buildAndCopyTempTables($strTable, $arrTableInfo, $arrTargetTableInfo);
    }


    /**
     * removes a single column from the table
     *
     * @param $strTable
     * @param $strColumn
     *
     * @return bool
     * @since 4.6
     */
    public function removeColumn($strTable, $strColumn)
    {

        $arrTableInfo = $this->getColumnsOfTable($strTable);
        $arrTargetTableInfo = array();
        foreach ($arrTableInfo as $arrOneColumn) {
            if ($arrOneColumn["columnName"] != $strColumn) {
                $arrTargetTableInfo[] = $arrOneColumn;
            }

        }

        return $this->buildAndCopyTempTables($strTable, $arrTargetTableInfo, $arrTargetTableInfo);
    }


    /**
     * Creates a single query in order to insert multiple rows at one time.
     * For most databases, this will create s.th. like
     * INSERT INTO $strTable ($arrColumns) VALUES (?, ?), (?, ?)...
     * Please note that this method is used to create the query itself, based on the Kajona-internal syntax.
     * The query is fired to the database by Database
     *
     * @param string $strTable
     * @param string[] $arrColumns
     * @param array $arrValueSets
     * @param Database $objDb
     *
     * @return bool
     */
    public function triggerMultiInsert($strTable, $arrColumns, $arrValueSets, Database $objDb)
    {

        $arrVersion = SQLite3::version();
        if (version_compare("3.7.11", $arrVersion["versionString"], "<=")) {
            return parent::triggerMultiInsert($strTable, $arrColumns, $arrValueSets, $objDb);
        }
        //legacy code
        else {

            $arrSafeColumns = array();
            $arrPlaceholder = array();
            foreach ($arrColumns as $strOneColumn) {
                $arrSafeColumns[] = $this->encloseColumnName($strOneColumn);
                $arrPlaceholder[] = "?";
            }

            $arrParams = array();

            $strQuery = "INSERT INTO ".$this->encloseTableName($strTable)."  (".implode(",", $arrSafeColumns).") ";
            for ($intI = 0; $intI < count($arrValueSets); $intI++) {

                $arrTemp = array();
                for ($intK = 0; $intK < count($arrColumns); $intK++) {
                    $arrTemp[] = " ? AS ".$this->encloseColumnName($arrColumns[$intK]);
                }

                if ($intI == 0) {
                    $strQuery .= " SELECT ".implode(", ", $arrTemp);
                }
                else {
                    $strQuery .= " UNION SELECT ".implode(", ", $arrTemp);
                }

                $arrParams = array_merge($arrParams, $arrValueSets[$intI]);
            }

            return $objDb->_pQuery($strQuery, $arrParams);
        }
    }

    /**
     * Sends a prepared statement to the database. All params must be represented by the ? char.
     * The params themselves are stored using the second params using the matching order.
     *
     * @param string $strQuery
     * @param array $arrParams
     *
     * @return bool
     * @since 3.4
     */
    public function _pQuery($strQuery, $arrParams)
    {
        $strQuery = $this->fixQuoting($strQuery);
        $strQuery = $this->processQuery($strQuery);

        $objStmt = $this->getPreparedStatement($strQuery);
        if ($objStmt === false) {
            return false;
        }
        $intCount = 1;
        foreach ($arrParams as $strOneParam) {
            if ($strOneParam === null) {
                $objStmt->bindValue(':param'.$intCount++, $strOneParam, SQLITE3_NULL);
            }
            //else if(is_double($strOneParam))
            //    $objStmt->bindValue(':param'.$intCount++ , $strOneParam, SQLITE3_FLOAT);
            //else if(is_numeric($strOneParam))
            //    $objStmt->bindValue(':param'.$intCount++ , $strOneParam, SQLITE3_INTEGER);
            else {
                $objStmt->bindValue(':param'.$intCount++, $strOneParam, SQLITE3_TEXT);
            }
        }

        if ($objStmt->execute() === false) {
            return false;
        }

        return true;
    }

    /**
     * This method is used to retrieve an array of resultsets from the database using
     * a prepared statement
     *
     * @param string $strQuery
     * @param array $arrParams
     *
     * @since 3.4
     * @return array
     */
    public function getPArray($strQuery, $arrParams)
    {
        $strQuery = $this->fixQuoting($strQuery);
        $strQuery = $this->processQuery($strQuery);

        $objStmt = $this->getPreparedStatement($strQuery);
        if ($objStmt === false) {
            return false;
        }

        $intCount = 1;
        foreach ($arrParams as $strOneParam) {
            if ($strOneParam === null) {
                $objStmt->bindValue(':param'.$intCount++, $strOneParam, SQLITE3_NULL);
            }
            //else if(is_double($strOneParam))
            //    $objStmt->bindValue(':param'.$intCount++ , $strOneParam, SQLITE3_FLOAT);
            //else if(is_numeric($strOneParam))
            //    $objStmt->bindValue(':param'.$intCount++ , $strOneParam, SQLITE3_INTEGER);
            else {
                $objStmt->bindValue(':param'.$intCount++, $strOneParam, SQLITE3_TEXT);
            }
        }

        $arrResult = array();
        $objResult = $objStmt->execute();

        if ($objResult === false) {
            return false;
        }

        while ($arrTemp = $objResult->fetchArray()) {
            $arrResult[] = $arrTemp;
        }

        return $arrResult;
    }


    /**
     * Returns the last error reported by the database.
     * Is being called after unsuccessful queries
     *
     * @return string
     */
    public function getError()
    {
        return $this->linkDB->lastErrorMsg();
    }

    /**
     * Returns ALL tables in the database currently connected to.
     * The method should return an array using the following keys:
     * name => Table name
     *
     * @return array
     */
    public function getTables()
    {
        $arrReturn = array();
        $resultSet = $this->linkDB->query("SELECT name FROM sqlite_master WHERE type='table'");
        while ($arrRow = $resultSet->fetchArray(SQLITE3_ASSOC)) {
            $arrReturn[] = array("name" => $arrRow["name"]);
        }
        return $arrReturn;
    }

    /**
     * Looks up the columns of the given table.
     * Should return an array for each row consisting of:
     * array ("columnName", "columnType")
     *
     * @param string $strTableName
     *
     * @return array
     */
    public function getColumnsOfTable($strTableName)
    {
        $arrColumns = array();
        $arrTableInfo = $this->getPArray("SELECT sql FROM sqlite_master WHERE type='table' and name=?", array($strTableName));
        if (!empty($arrTableInfo)) {
            $strTableDef = $arrTableInfo[0]["sql"];
            $strTableDef = uniStrReplace("\"", "", $strTableDef);

            // Extract the column definitions from the create statement
            $arrMatch = array();
            preg_match("/CREATE TABLE\s+[a-z_0-9]+\s+\((.+)\)/ism", trim($strTableDef), $arrMatch);

            // Get all column names and types
            $strColumnDef = $arrMatch[1];
            $intPrimaryKeyPos = strripos($strColumnDef, "PRIMARY KEY");
            if ($intPrimaryKeyPos !== false) {
                $strColumnDef = substr($strColumnDef, 0, $intPrimaryKeyPos);
            }
            preg_match_all("/\s*([a-z_0-9]+)\s+([a-z]+)[^,]+/ism", trim($strColumnDef), $arrMatch, PREG_SET_ORDER);

            foreach ($arrMatch as $arrColumnInfo) {
                $arrColumns[] = array(
                    "columnName" => $arrColumnInfo[1],
                    "columnType" => $arrColumnInfo[2]
                );
            }
        }
        return $arrColumns;
    }

    /**
     * Used to send a create table statement to the database
     * By passing the query through this method, the driver can
     * add db-specific commands.
     * The array of fields should have the following structure
     * $array[string columnName] = array(string datatype, boolean isNull [, default (only if not null)])
     * whereas datatype is one of the following:
     *         int
     *      long<commit_msg>BUG: system | DbSqlite3 -> fixed copying tables<commit_after><?php
/*"******************************************************************************************************
*   (c) 2004-2006 by MulchProductions, www.mulchprod.de                                                 *
*   (c) 2007-2016 by Kajona, www.kajona.de                                                              *
*       Published under the GNU LGPL v2.1, see /system/licence_lgpl.txt                                 *
********************************************************************************************************/

namespace Kajona\System\System\Db;

use Kajona\System\System\Database;
use Kajona\System\System\DbDatatypes;
use Kajona\System\System\Exception;
use Kajona\System\System\Filesystem;
use SQLite3;
use SQLite3Stmt;


/**
 * db-driver for sqlite3 using the php-sqlite3-interface.
 * Based on the sqlite2 driver by phwolfer
 *
 * @since 3.3.0.1
 * @author <EMAIL>
 * @package module_system
 */
class DbSqlite3 extends DbBase
{

    /**
     * @var SQLite3
     */
    private $linkDB;
    private $strDbFile;

    /**
     * This method makes sure to connect to the database properly
     *
     * @param string $strHost
     * @param string $strUsername
     * @param string $strPass
     * @param string $strDbName
     * @param int $intPort
     *
     * @throws Exception
     * @return bool
     */
    public function dbconnect($strHost, $strUsername, $strPass, $strDbName, $intPort)
    {

        if ($strDbName == "") {
            return false;
        }

        $this->strDbFile = _projectpath_.'/dbdumps/'.$strDbName.'.db3';

        try {


            $strPath = _realpath_.$this->strDbFile;
            /*if(defined("_autotesting_")) {
                $strPath = ":memory:";
            }*/
            $this->linkDB = new SQLite3($strPath);
            $this->_pQuery('PRAGMA encoding = "UTF-8"', array());
            //TODO deprecated in sqlite, so may be removed
            $this->_pQuery('PRAGMA short_column_names = ON', array());
            $this->_pQuery("PRAGMA journal_mode = TRUNCATE", array());
            if (method_exists($this->linkDB, "busyTimeout")) {
                $this->linkDB->busyTimeout(5000);
            }

            return true;
        }
        catch (Exception $e) {
            throw new Exception("Error connecting to database: ".$e, Exception::$level_FATALERROR);
        }

    }

    /**
     * Closes the connection to the database
     *
     * @return void
     */
    public function dbclose()
    {
        $this->linkDB->close();
    }


    private function buildAndCopyTempTables($strTargetTableName, $arrSourceTableInfo, $arrTargetTableInfo)
    {
        $bitReturn = true;

        /* Get existing table info */
        $arrPragmaTableInfo = $this->getPArray("PRAGMA table_info('{$strTargetTableName}')", array());
        $arrColumnsPragma = array();
        foreach($arrPragmaTableInfo as $arrRow) {
            $arrColumnsPragma[$arrRow['name']] = $arrRow;
        }

        $arrSourceColumns = array();
        array_walk($arrSourceTableInfo, function ($arrValue) use (&$arrSourceColumns) {
            $arrSourceColumns[] = $arrValue["columnName"];
        });

        $arrTargetColumns = array();
        array_walk($arrTargetTableInfo, function ($arrValue) use (&$arrTargetColumns) {
            $arrTargetColumns[] = $arrValue["columnName"];
        });


        //build the a temp table
        $strQuery = "CREATE TABLE ".$strTargetTableName."_temp ( \n";

        //loop the fields
        $arrColumns = array();
        $arrPks = array();
        foreach ($arrTargetTableInfo as $arrOneColumn) {
            $arrRow = null;

            if(array_key_exists($arrOneColumn["columnName"], $arrColumnsPragma)) {
                $arrRow = $arrColumnsPragma[$arrOneColumn["columnName"]];
            }
            else {
                $arrRow["name"] = $arrOneColumn["columnName"];
                $arrRow["type"] = $arrOneColumn["columnType"];
            }

            //column settings
            $strColumn = " ".$arrRow["name"]." ".$arrRow["type"];

            if($arrRow["notnull"] === 1) {
                $strColumn .= " NOT NULL ";
            }
            elseif($arrRow["notnull"] === 0) {
                $strColumn .= " NULL ";
            }

            if($arrRow["dflt_value"] !== null) {
                $strColumn .= " DEFAULT {$arrRow["dflt_value"]} ";
            }
            $arrColumns[] = $strColumn;

            //primary key?
            if($arrRow["pk"] === 1) {
                $arrPks[] = $arrRow["name"];
            }
        }

        //columns
        $strQuery .= implode(",\n", $arrColumns);

        //primary keys
        if(count($arrPks) > 0) {
            $strQuery .= ",PRIMARY KEY (";
            $strQuery .= implode(",", $arrPks);
            $strQuery .= ")\n";
        }
        
        $strQuery .= ")\n";

        $bitReturn = $bitReturn && $this->_pQuery($strQuery, array());

        //copy all values
        $strQuery = "INSERT INTO ".$strTargetTableName."_temp (".implode(",", $arrTargetColumns).") SELECT ".implode(",", $arrSourceColumns)." FROM ".$strTargetTableName;
        $bitReturn = $bitReturn && $this->_pQuery($strQuery, array());

        $strQuery = "DROP TABLE ".$strTargetTableName;
        $bitReturn = $bitReturn && $this->_pQuery($strQuery, array());

        return $bitReturn && $this->renameTable($strTargetTableName."_temp", $strTargetTableName);
    }


    /**
     * Renames a single column of the table
     *
     * @param $strTable
     * @param $strOldColumnName
     * @param $strNewColumnName
     * @param $strNewDatatype
     *
     * @return bool
     * @since 4.6
     */
    public function changeColumn($strTable, $strOldColumnName, $strNewColumnName, $strNewDatatype)
    {

        $arrTableInfo = $this->getColumnsOfTable($strTable);
        $arrTargetTableInfo = array();
        foreach ($arrTableInfo as $arrOneColumn) {
            if ($arrOneColumn["columnName"] == $strOldColumnName) {
                $arrNewRow = array(
                    "columnName" => $strNewColumnName,
                    "columnType" => $this->getDatatype($strNewDatatype)
                );

                $arrTargetTableInfo[] = $arrNewRow;
            }
            else {
                $arrTargetTableInfo[] = $arrOneColumn;
            }

        }

        return $this->buildAndCopyTempTables($strTable, $arrTableInfo, $arrTargetTableInfo);
    }


    /**
     * removes a single column from the table
     *
     * @param $strTable
     * @param $strColumn
     *
     * @return bool
     * @since 4.6
     */
    public function removeColumn($strTable, $strColumn)
    {

        $arrTableInfo = $this->getColumnsOfTable($strTable);
        $arrTargetTableInfo = array();
        foreach ($arrTableInfo as $arrOneColumn) {
            if ($arrOneColumn["columnName"] != $strColumn) {
                $arrTargetTableInfo[] = $arrOneColumn;
            }

        }

        return $this->buildAndCopyTempTables($strTable, $arrTargetTableInfo, $arrTargetTableInfo);
    }


    /**
     * Creates a single query in order to insert multiple rows at one time.
     * For most databases, this will create s.th. like
     * INSERT INTO $strTable ($arrColumns) VALUES (?, ?), (?, ?)...
     * Please note that this method is used to create the query itself, based on the Kajona-internal syntax.
     * The query is fired to the database by Database
     *
     * @param string $strTable
     * @param string[] $arrColumns
     * @param array $arrValueSets
     * @param Database $objDb
     *
     * @return bool
     */
    public function triggerMultiInsert($strTable, $arrColumns, $arrValueSets, Database $objDb)
    {

        $arrVersion = SQLite3::version();
        if (version_compare("3.7.11", $arrVersion["versionString"], "<=")) {
            return parent::triggerMultiInsert($strTable, $arrColumns, $arrValueSets, $objDb);
        }
        //legacy code
        else {

            $arrSafeColumns = array();
            $arrPlaceholder = array();
            foreach ($arrColumns as $strOneColumn) {
                $arrSafeColumns[] = $this->encloseColumnName($strOneColumn);
                $arrPlaceholder[] = "?";
            }

            $arrParams = array();

            $strQuery = "INSERT INTO ".$this->encloseTableName($strTable)."  (".implode(",", $arrSafeColumns).") ";
            for ($intI = 0; $intI < count($arrValueSets); $intI++) {

                $arrTemp = array();
                for ($intK = 0; $intK < count($arrColumns); $intK++) {
                    $arrTemp[] = " ? AS ".$this->encloseColumnName($arrColumns[$intK]);
                }

                if ($intI == 0) {
                    $strQuery .= " SELECT ".implode(", ", $arrTemp);
                }
                else {
                    $strQuery .= " UNION SELECT ".implode(", ", $arrTemp);
                }

                $arrParams = array_merge($arrParams, $arrValueSets[$intI]);
            }

            return $objDb->_pQuery($strQuery, $arrParams);
        }
    }

    /**
     * Sends a prepared statement to the database. All params must be represented by the ? char.
     * The params themselves are stored using the second params using the matching order.
     *
     * @param string $strQuery
     * @param array $arrParams
     *
     * @return bool
     * @since 3.4
     */
    public function _pQuery($strQuery, $arrParams)
    {
        $strQuery = $this->fixQuoting($strQuery);
        $strQuery = $this->processQuery($strQuery);

        $objStmt = $this->getPreparedStatement($strQuery);
        if ($objStmt === false) {
            return false;
        }
        $intCount = 1;
        foreach ($arrParams as $strOneParam) {
            if ($strOneParam === null) {
                $objStmt->bindValue(':param'.$intCount++, $strOneParam, SQLITE3_NULL);
            }
            //else if(is_double($strOneParam))
            //    $objStmt->bindValue(':param'.$intCount++ , $strOneParam, SQLITE3_FLOAT);
            //else if(is_numeric($strOneParam))
            //    $objStmt->bindValue(':param'.$intCount++ , $strOneParam, SQLITE3_INTEGER);
            else {
                $objStmt->bindValue(':param'.$intCount++, $strOneParam, SQLITE3_TEXT);
            }
        }

        if ($objStmt->execute() === false) {
            return false;
        }

        return true;
    }

    /**
     * This method is used to retrieve an array of resultsets from the database using
     * a prepared statement
     *
     * @param string $strQuery
     * @param array $arrParams
     *
     * @since 3.4
     * @return array
     */
    public function getPArray($strQuery, $arrParams)
    {
        $strQuery = $this->fixQuoting($strQuery);
        $strQuery = $this->processQuery($strQuery);

        $objStmt = $this->getPreparedStatement($strQuery);
        if ($objStmt === false) {
            return false;
        }

        $intCount = 1;
        foreach ($arrParams as $strOneParam) {
            if ($strOneParam === null) {
                $objStmt->bindValue(':param'.$intCount++, $strOneParam, SQLITE3_NULL);
            }
            //else if(is_double($strOneParam))
            //    $objStmt->bindValue(':param'.$intCount++ , $strOneParam, SQLITE3_FLOAT);
            //else if(is_numeric($strOneParam))
            //    $objStmt->bindValue(':param'.$intCount++ , $strOneParam, SQLITE3_INTEGER);
            else {
                $objStmt->bindValue(':param'.$intCount++, $strOneParam, SQLITE3_TEXT);
            }
        }

        $arrResult = array();
        $objResult = $objStmt->execute();

        if ($objResult === false) {
            return false;
        }

        while ($arrTemp = $objResult->fetchArray()) {
            $arrResult[] = $arrTemp;
        }

        return $arrResult;
    }


    /**
     * Returns the last error reported by the database.
     * Is being called after unsuccessful queries
     *
     * @return string
     */
    public function getError()
    {
        return $this->linkDB->lastErrorMsg();
    }

    /**
     * Returns ALL tables in the database currently connected to.
     * The method should return an array using the following keys:
     * name => Table name
     *
     * @return array
     */
    public function getTables()
    {
        $arrReturn = array();
        $resultSet = $this->linkDB->query("SELECT name FROM sqlite_master WHERE type='table'");
        while ($arrRow = $resultSet->fetchArray(SQLITE3_ASSOC)) {
            $arrReturn[] = array("name" => $arrRow["name"]);
        }
        return $arrReturn;
    }

    /**
     * Looks up the columns of the given table.
     * Should return an array for each row consisting of:
     * array ("columnName", "columnType")
     *
     * @param string $strTableName
     *
     * @return array
     */
    public function getColumnsOfTable($strTableName)
    {
        $arrTableInfo = $this->getPArray("PRAGMA table_info('{$strTableName}')", array());

        $arrColumns = array();
        foreach($arrTableInfo as $arrRow) {
            $arrColumns[] = array(
                "columnName" => $arrRow['name'],
                "columnType" => $arrRow['type']
            );
        }

        return $arrColumns;
    }

    /**
     * Used to send a create table statement to the database
     * By passing the query through this method, the driver can
     * add db-specific commands.
     * The array of fields should have the following structure
     * $array[string columnName] = array(string datatype, boolean isNull [, default (only if not null)])
     * whereas datatype is one of the following:
     *         int
     *      long
{'fraction_non_alphanumeric': 0.14826393997351772, 'fraction_numerical': 0.005222892452552597, 'mean_word_length': 1.9996690203000882, 'pattern_counts': {'":': 2, '<': 13, '<?xml version=': 0, '>': 115, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2872433', 'n_tokens_mistral': 7890, 'n_tokens_neox': 7615, 'n_words': 1749}