<commit_before><?php

/**
 * @author <NAME> <<EMAIL>>
 * @copyright Copyright (c) 2013, <NAME>
 */

namespace Fxrm\Store;

abstract class PDOBackend extends Backend {
    private $pdo;
    private $idGeneratorMap;

    function __construct($dsn, $user = null, $password = null, $options = array()) {
        $this->idGeneratorMap = array_key_exists('idGeneratorMap', $options)
            ? (array)$options['idGeneratorMap']
            : array();

        $this->pdo = new \PDO($dsn, $user, $password);

        $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
        $this->initPDO($this->pdo);
    }

    final function find($method, $entity, $valueTypeMap, $valueMap, $returnType, $multiple) {
        $stmt = $this->pdo->prepare($this->getCustomQuery($method) ?: $this->generateFindQuery($this->getEntityName($entity), array_keys($valueMap), $multiple));

        foreach ($valueMap as $field => $value) {
            $stmt->bindValue(':' . $field, $this->fromValue($valueTypeMap[$field], $value));
        }

        $stmt->execute();

        // if specific entity requested, only return the first column, assumed to contain the ID
        $rows = is_array($returnType) ? $stmt->fetchAll(\PDO::FETCH_OBJ) : $stmt->fetchAll(\PDO::FETCH_COLUMN, 0);

        $stmt->closeCursor();

        // process all rows in-place
        if (is_array($returnType)) {
            foreach ($rows as &$row) {
                $row = $this->toRow($returnType, $row);
            }
        } else {
            foreach ($rows as &$row) {
                $row = $this->toValue($returnType, $row);
            }
        }

        // model may expect object to not exist: null value is the "pure" way to communicate that
        return $multiple ? $rows : (count($rows) === 0 ? null : $rows[0]);
    }

    final function get($method, $entity, $id, $fieldType, $field) {
        // @todo use the original model parameter name as query param
        $sql = $this->getCustomQuery($method) ?: $this->generateGetQuery($this->getEntityName($entity), $field);

        $stmt = $this->pdo->prepare($sql);
        $stmt->bindValue(':id', $id);
        $stmt->execute();

        // expecting only a single value per row
        $rows = $stmt->fetchAll(\PDO::FETCH_COLUMN, 0);

        $stmt->closeCursor();

        // object is expected to exist; this exception is not caught by model logic
        if (count($rows) !== 1) {
            throw new \Exception('object not found');
        }

        return $this->toValue($fieldType, $rows[0]);
    }

    final function set($method, $entity, $id, $valueTypeMap, $valueMap) {
        // @todo use the original model parameter name as query param
        $stmt = $this->pdo->prepare($this->getCustomQuery($method) ?: $this->generateSetQuery($this->getEntityName($entity), array_keys($valueMap)));
        $stmt->bindValue(':id', $id);

        $valueCount = 0;
        foreach ($valueMap as $field => $value) {
            $stmt->bindValue(':' . $field, $this->fromValue($valueTypeMap[$field], $value));
            $valueCount += 1;
        }

        $stmt->execute();

        // not checking updated row count in case the custom query updates multiple rows
        $stmt->closeCursor();
    }

    final function create($entity) {
        $predefinedId = null;

        if (array_key_exists($entity, $this->idGeneratorMap)) {
            $generator = $this->idGeneratorMap[$entity];
            $predefinedId = $generator($this->pdo);
        }

        $stmt = $this->pdo->prepare($this->generateCreateQuery($this->getEntityName($entity), ($predefinedId !== null)));

        if ($predefinedId !== null) {
            $stmt->bindValue(':id', $predefinedId);
        }

        $stmt->execute();
        $id = $predefinedId === null ? $this->pdo->lastInsertId() : $predefinedId;

        $stmt->closeCursor();

        return $id;
    }

    final function retrieve($querySpecMap, $valueTypeMap, $paramMap, $returnTypeMap) {
        $ns = $this->getImplementationNamespace();

        if (!array_key_exists($ns, $querySpecMap)) {
            throw new \Exception('no query spec for namespace "' . $ns . '"');
        }

        $spec = $querySpecMap[$ns];

        $stmt = $this->pdo->prepare($spec);
        foreach ($paramMap as $field => $value) {
            $stmt->bindValue(is_int($field) ? $field + 1 : ':' . $field, $this->fromValue($valueTypeMap[$field], $value));
        }
        $stmt->execute();
        $rows = $stmt->fetchAll(\PDO::FETCH_OBJ);
        $stmt->closeCursor();

        // process all rows in-place
        foreach ($rows as &$row) {
            $row = $this->toRow($returnTypeMap, $row);
        }

        return $rows;
    }

    private function toRow($fieldTypeMap, $data) {
        $result = (object)null;

        // permissively copying all fields, including those with unknown types
        foreach ($data as $k => $v) {
            $result->$k = isset($fieldTypeMap[$k]) ? $this->toValue($fieldTypeMap[$k], $v) : $v;
        }

        return $result;
    }

    private function toValue($type, $data) {
        if ($data === null) {
            return null;
        }

        if (is_array($type)) {
            return $this->toJSONValue($type, json_decode($data, false)); // prefer anonymous objects instead of associative arrays
        } else {
            return $this->toSimpleValue($type, $data);
        }

        return $data;
    }

    private function toSimpleValue($type, $data) {
        if ($type === Backend::DATE_TIME_TYPE) {
            return $this->internDateTime($data);
        }

        return $data;
    }

    private function toJSONValue($fieldType, $data) {
        if (!is_array($fieldType)) {
            return $this->toSimpleValue($fieldType, $data);
        } elseif (array_key_exists(0, $fieldType)) {
            return $this->toJSONArray($fieldType, $data);
        } else {
            return $this->toJSONMap($fieldType, $data);
        }
    }

    private function toJSONArray($fieldTypeMap, $data) {
        $type = $fieldTypeMap[0];
        $result = array();
<commit_msg>Fixed deserialization of null JSON fields<commit_after><?php

/**
 * @author <NAME> <<EMAIL>>
 * @copyright Copyright (c) 2013, <NAME>
 */

namespace Fxrm\Store;

abstract class PDOBackend extends Backend {
    private $pdo;
    private $idGeneratorMap;

    function __construct($dsn, $user = null, $password = null, $options = array()) {
        $this->idGeneratorMap = array_key_exists('idGeneratorMap', $options)
            ? (array)$options['idGeneratorMap']
            : array();

        $this->pdo = new \PDO($dsn, $user, $password);

        $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
        $this->initPDO($this->pdo);
    }

    final function find($method, $entity, $valueTypeMap, $valueMap, $returnType, $multiple) {
        $stmt = $this->pdo->prepare($this->getCustomQuery($method) ?: $this->generateFindQuery($this->getEntityName($entity), array_keys($valueMap), $multiple));

        foreach ($valueMap as $field => $value) {
            $stmt->bindValue(':' . $field, $this->fromValue($valueTypeMap[$field], $value));
        }

        $stmt->execute();

        // if specific entity requested, only return the first column, assumed to contain the ID
        $rows = is_array($returnType) ? $stmt->fetchAll(\PDO::FETCH_OBJ) : $stmt->fetchAll(\PDO::FETCH_COLUMN, 0);

        $stmt->closeCursor();

        // process all rows in-place
        if (is_array($returnType)) {
            foreach ($rows as &$row) {
                $row = $this->toRow($returnType, $row);
            }
        } else {
            foreach ($rows as &$row) {
                $row = $this->toValue($returnType, $row);
            }
        }

        // model may expect object to not exist: null value is the "pure" way to communicate that
        return $multiple ? $rows : (count($rows) === 0 ? null : $rows[0]);
    }

    final function get($method, $entity, $id, $fieldType, $field) {
        // @todo use the original model parameter name as query param
        $sql = $this->getCustomQuery($method) ?: $this->generateGetQuery($this->getEntityName($entity), $field);

        $stmt = $this->pdo->prepare($sql);
        $stmt->bindValue(':id', $id);
        $stmt->execute();

        // expecting only a single value per row
        $rows = $stmt->fetchAll(\PDO::FETCH_COLUMN, 0);

        $stmt->closeCursor();

        // object is expected to exist; this exception is not caught by model logic
        if (count($rows) !== 1) {
            throw new \Exception('object not found');
        }

        return $this->toValue($fieldType, $rows[0]);
    }

    final function set($method, $entity, $id, $valueTypeMap, $valueMap) {
        // @todo use the original model parameter name as query param
        $stmt = $this->pdo->prepare($this->getCustomQuery($method) ?: $this->generateSetQuery($this->getEntityName($entity), array_keys($valueMap)));
        $stmt->bindValue(':id', $id);

        $valueCount = 0;
        foreach ($valueMap as $field => $value) {
            $stmt->bindValue(':' . $field, $this->fromValue($valueTypeMap[$field], $value));
            $valueCount += 1;
        }

        $stmt->execute();

        // not checking updated row count in case the custom query updates multiple rows
        $stmt->closeCursor();
    }

    final function create($entity) {
        $predefinedId = null;

        if (array_key_exists($entity, $this->idGeneratorMap)) {
            $generator = $this->idGeneratorMap[$entity];
            $predefinedId = $generator($this->pdo);
        }

        $stmt = $this->pdo->prepare($this->generateCreateQuery($this->getEntityName($entity), ($predefinedId !== null)));

        if ($predefinedId !== null) {
            $stmt->bindValue(':id', $predefinedId);
        }

        $stmt->execute();
        $id = $predefinedId === null ? $this->pdo->lastInsertId() : $predefinedId;

        $stmt->closeCursor();

        return $id;
    }

    final function retrieve($querySpecMap, $valueTypeMap, $paramMap, $returnTypeMap) {
        $ns = $this->getImplementationNamespace();

        if (!array_key_exists($ns, $querySpecMap)) {
            throw new \Exception('no query spec for namespace "' . $ns . '"');
        }

        $spec = $querySpecMap[$ns];

        $stmt = $this->pdo->prepare($spec);
        foreach ($paramMap as $field => $value) {
            $stmt->bindValue(is_int($field) ? $field + 1 : ':' . $field, $this->fromValue($valueTypeMap[$field], $value));
        }
        $stmt->execute();
        $rows = $stmt->fetchAll(\PDO::FETCH_OBJ);
        $stmt->closeCursor();

        // process all rows in-place
        foreach ($rows as &$row) {
            $row = $this->toRow($returnTypeMap, $row);
        }

        return $rows;
    }

    private function toRow($fieldTypeMap, $data) {
        $result = (object)null;

        // permissively copying all fields, including those with unknown types
        foreach ($data as $k => $v) {
            $result->$k = isset($fieldTypeMap[$k]) ? $this->toValue($fieldTypeMap[$k], $v) : $v;
        }

        return $result;
    }

    private function toValue($type, $data) {
        if ($data === null) {
            return null;
        }

        if (is_array($type)) {
            return $this->toJSONValue($type, json_decode($data, false)); // prefer anonymous objects instead of associative arrays
        } else {
            return $this->toSimpleValue($type, $data);
        }

        return $data;
    }

    private function toSimpleValue($type, $data) {
        if ($type === Backend::DATE_TIME_TYPE) {
            return $this->internDateTime($data);
        }

        return $data;
    }

    private function toJSONValue($fieldType, $data) {
        if ($data === null) {
            return null;
        }

        if (!is_array($fieldType)) {
            return $this->toSimpleValue($fieldType, $data);
        } elseif (array_key_exists(0, $fieldType)) {
            return $this->toJSONArray($fieldType, $data);
        } else {
            return $this->toJSONMap($fieldType, $data);
        }
    }

    private function toJSONArray($fieldTypeMap, $data) {
        $type = $fieldTypeMap[0];
        $result = array();

{'fraction_non_alphanumeric': 0.16146595156834065, 'fraction_numerical': 0.0024378352023403218, 'mean_word_length': 2.4100858963701857, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 155, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6318748', 'n_tokens_mistral': 3708, 'n_tokens_neox': 3582, 'n_words': 838}