<?php

/**
 * This file is part of Gitonomy.
 *
 * (c) Alexandre Salomé <alexandre.salome@gmail.com>
 * (c) Julien DIDIER <genzo.wm@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */
namespace Gitonomy\Git;

use Gitonomy\Git\Blame\Line;
use Gitonomy\Git\Exception\InvalidArgumentException;
use Gitonomy\Git\Parser\BlameParser;

/**
 * @author Alexandre Salomé <alexandre.salome@gmail.com>
 */
class Blame implements \Countable
{
    /**
     * @var Repository
     */
    protected $repository;

    /**
     * @var Revision
     */
    protected $revision;

    /**
     * @var string
     */
    protected $file;

    /**
     * @var string|null
     */
    protected $lineRange;

    /**
     * @var array|null
     */
    protected $lines;

    /**
     * @param string $lineRange Argument to pass to git blame (-L).
     *                          Can be a line range (40,60 or 40,+21)
     *                          or a regexp ('/^function$/')
     */
    public function __construct(Repository $repository, Revision $revision, $file, $lineRange = null)
    {
        $this->repository = $repository;
        $this->revision = $revision;
        $this->lineRange = $lineRange;
        $this->file = $file;
    }

    /**
     * @return Line
     */
    public function getLine($number)
    {
        if ($number < 1) {
            throw new InvalidArgumentException('Line number should be at least 1');
        }

        $lines = $this->getLines();

        if (!isset($lines[$number])) {
            throw new InvalidArgumentException('Line does not exist');
        }

        return $lines[$number];
    }

    /**
     * Returns lines grouped by commit.
     *
     * @return array a list of two-elements array (commit, lines)
     */
    public function getGroupedLines()
    {
        $result = array();
        $commit = null;
        $current = array();

        foreach ($this->getLines() as $lineNumber => $line) {
            if ($commit !== $line->getCommit()) {
                if (count($current)) {
                    $result[] = array($commit, $current);
                }
                $commit = $line->getCommit();
                $current = array();
            }

            $current[$lineNumber] = $line;
        }

        if (count($current)) {
            $result[] = array($commit, $current);
        }

        return $result;
    }

    /**
     * Returns all lines of the blame.
     *
     * @return array
     */
    public function getLines()
    {
        if (null !== $this->lines) {
            return $this->lines;
        }

        $args = array('-p');

        if (null !== $this->lineRange) {
            $args[] = '-L';
            $args[] = $this->lineRange;
        }

        $args[] = $this->revision->getRevision();
        $args[] = '--';
        $args[] = $this->file;

        $parser = new BlameParser($this->repository);
        $parser->parse($this->repository->run('blame', $args));
        $this->lines = $parser->lines;

        return $this->lines;
    }

    /**
     * @return int
     */
    public function count()
    {
        return count($this->getLines());
    }
}


<?php

/**
 * This file is part of Gitonomy.
 *
 * (c) Alexandre Salomé <alexandre.salome@gmail.com>
 * (c) Julien DIDIER <genzo.wm@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */
namespace Gitonomy\Git;

use Gitonomy\Git\Exception\ReferenceNotFoundException;
use Gitonomy\Git\Exception\RuntimeException;
use Gitonomy\Git\Reference\Branch;
use Gitonomy\Git\Reference\Stash;
use Gitonomy\Git\Reference\Tag;

/**
 * Reference set associated to a repository.
 *
 * @author Alexandre Salomé <alexandre.salome@gmail.com>
 * @author Julien DIDIER <genzo.wm@gmail.com>
 */
class ReferenceBag implements \Countable, \IteratorAggregate
{
    /**
     * Repository object.
     *
     * @var Gitonomy\Git\Repository
     */
    protected $repository;

    /**
     * Associative array of fullname references.
     *
     * @var array
     */
    protected $references;

    /**
     * List with all tags.
     *
     * @var array
     */
    protected $tags;

    /**
     * List with all branches.
     *
     * @var array
     */
    protected $branches;

    /**
     * A boolean indicating if the bag is already initialized.
     *
     * @var bool
     */
    protected $initialized = false;

    /**
     * Constructor.
     *
     * @param Gitonomy\Git\Repository $repository The repository
     */
    public function __construct($repository)
    {
        $this->repository = $repository;
        $this->references = array();
        $this->tags = array();
        $this->branches = array();
    }

    /**
     * Returns a reference, by name.
     *
     * @param string $fullname Fullname of the reference (refs/heads/master, for example).
     *
     * @return Reference A reference object.
     */
    public function get($fullname)
    {
        $this->initialize();

        if (!isset($this->references[$fullname])) {
            throw new ReferenceNotFoundException($fullname);
        }

        return $this->references[$fullname];
    }

    public function has($fullname)
    {
        $this->initialize();

        return isset($this->references[$fullname]);
    }

    public function update(Reference $reference)
    {
        $fullname = $reference->getFullname();

        $this->initialize();
        $this->repository->run('update-ref', array($fullname, $reference->getCommitHash()));

        $this->references[$fullname] = $reference;

        return $reference;
    }

    public function createBranch($name, $commitHash)
    {
        $branch = new Branch($this->repository, 'refs/heads/'.$name, $commitHash);

        return $this->update($branch);
    }

    public function createTag($name, $commitHash)
    {
        $tag = new Tag($this->repository, 'refs/tags/'.$name, $commitHash);

        return $this->update($tag);
    }

    public function delete($fullname)
    {
        $this->repository->run('update-ref', array('-d', $fullname));

        unset($this->references[$fullname]);
    }

    public function hasBranches()
    {
        $this->initialize();

        return count($this->branches) > 0;
    }

    public function hasBranch($name)
    {
        return $this->has('refs/heads/'.$name);
    }

    public function hasRemoteBranch($name)
    {
        return $this->has('refs/remotes/'.$name);
    }

    public function hasTag($name)
    {
        return $this->has('refs/tags/'.$name);
    }

    public function getFirstBranch()
    {
        $this->initialize();
        reset($this->branches);

        return current($this->references);
    }

    /**
     * @return array An array of Tag objects
     */
    public function resolveTags($hash)
    {
        $this->initialize();

        if ($hash instanceof Commit) {
            $hash = $hash->getHash();
        }

        $tags = array();
        foreach ($this->references as $reference) {
            if ($reference instanceof Reference\Tag && $reference->getCommitHash() === $hash) {
                $tags[] = $reference;
            }
        }

        return $tags;
    }

    /**
     * @return array An array of Branch objects
     */
    public function resolveBranches($hash)
    {
        $this->initialize();

        if ($hash instanceof Commit) {
            $hash = $hash->getHash();
        }

        $branches = array();
        foreach ($this->references as $reference) {
            if ($reference instanceof Reference\Branch && $reference->getCommitHash() === $hash) {
                $branches[] = $reference;
            }
        }

        return $branches;
    }

    /**
     * @return array An array of references
     */
    public function resolve($hash)
    {
        $this->initialize();

        if ($hash instanceof Commit) {
            $hash = $hash->getHash();
        }

        $result = array();
        foreach ($this->references as $k => $reference) {
            if ($reference->getCommitHash() === $hash) {
                $result[] = $reference;
            }
        }

        return $result;
    }

    /**
     * Returns all tags.
     *
     * @return array
     */
    public function getTags()
    {
        $this->initialize();

        return $this->tags;
    }

    /**
     * Returns all branches.
     *
     * @return array
     */
    public function getBranches()
    {
        $this->initialize();

        $result = array();
        foreach ($this->references as $reference) {
            if ($reference instanceof Reference\Branch) {
                $result[] = $reference;
            }
        }

        return $result;
    }

    /**
     * Returns all locales branches.
     *
     * @return array
     */
    public function getLocalBranches()
    {
        $result = array();
        foreach ($this->getBranches() as $branch) {
            if ($branch->isLocal()) {
                $result[] = $branch;
            }
        }

        return $result;
    }

    /**
     * Returns all remote branches.
     *
     * @return array
     */
    public function getRemoteBranches()
    {
        $result = array();
        foreach ($this->getBranches() as $branch) {
            if ($branch->isRemote()) {
                $result[] = $branch;
            }
        }

        return $result;
    }

    /**
     * @return array An associative array with fullname as key (refs/heads/master, refs/tags/0.1)
     */
    public function getAll()
    {
        $this->initialize();

        return $this->references;
    }

    /**
     * @return Tag
     */
    public function getTag($name)
    {
        $this->initialize();

        return $this->get('refs/tags/'.$name);
    }

    /**
     * @return Branch
     */
    public function getBranch($name)
    {
        $this->initialize();

        return $this->get('refs/heads/'.$name);
    }

    /**
     * @return Branch
     */
    public function getRemoteBranch($name)
    {
        $this->initialize();

        return $this->get('refs/remotes/'.$name);
    }

    protected function initialize()
    {
        if (true === $this->initialized) {
            return;
        }
        $this->initialized = true;

        try {
            $parser = new Parser\ReferenceParser();
            $output = $this->repository->run('show-ref');
        } catch (RuntimeException $e) {
            $output = $e->getOutput();
            $error = $e->getErrorOutput();
            if ($error) {
                throw new RuntimeException('Error while getting list of references: '.$error);
            }
        }
        $parser->parse($output);

        foreach ($parser->references as $row) {
            list($commitHash, $fullname) = $row;

            if (preg_match('#^refs/(heads|remotes)/(.*)$#', $fullname)) {
                if (preg_match('#.*HEAD$#', $fullname)) {
                    continue;
                }
                $reference = new Branch($this->repository, $fullname, $commitHash);
                $this->references[$fullname] = $reference;
                $this->branches[] = $reference;
            } elseif (preg_match('#^refs/tags/(.*)$#', $fullname)) {
                $reference = new Tag($this->repository, $fullname, $commitHash);
                $this->references[$fullname] = $reference;
                $this->tags[] = $reference;
            } elseif ($fullname === 'refs/stash') {
                $reference = new Stash($this->repository, $fullname, $commitHash);
                $this->references[$fullname] = $reference;
            } elseif (preg_match('#^refs/pull/(.*)$#', $fullname)) {
                // Do nothing here
            } elseif ($fullname === 'refs/notes/gtm-data') {
                // Do nothing here
            } else {
                throw new RuntimeException(sprintf('Unable to parse "%s"', $fullname));
            }
        }
    }

    /**
     * @return int
     *
     * @see Countable
     */
    public function count()
    {
        $this->initialize();

        return count($this->references);
    }

    /**
     * @see IteratorAggregate
     */
    public function getIterator()
    {
        $this->initialize();

        return new \ArrayIterator($this->references);
    }
}


<?php

/**
 * This file is part of Gitonomy.
 *
 * (c) Alexandre Salomé <alexandre.salome@gmail.com>
 * (c) Julien DIDIER <genzo.wm@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */
namespace Gitonomy\Git;

/**
 * Representation of a Blob commit.
 *
 * @author Alexandre Salomé <alexandre.salome@gmail.com>
 */
class Blob
{
    /**
     * @var Repository
     */
    protected $repository;

    /**
     * @var string
     */
    protected $hash;

    /**
     * @var string
     */
    protected $content;

    /**
     * @var string
     */
    protected $mimetype;

    /**
     * @param Repository $repository Repository where the blob is located
     * @param string     $hash       Hash of the blob
     */
    public function __construct(Repository $repository, $hash)
    {
        $this->repository = $repository;
        $this->hash = $hash;
    }

    /**
     * @return string
     */
    public function getHash()
    {
        return $this->hash;
    }

    /**
     * Returns content of the blob.
     *
     * @throws ProcessException Error occurred while getting content of blob
     */
    public function getContent()
    {
        if (null === $this->content) {
            $this->content = $this->repository->run('cat-file', array('-p', $this->hash));
        }

        return $this->content;
    }

    /**
     * Determine the mimetype of the blob.
     *
     * @return string A mimetype
     */
    public function getMimetype()
    {
        if (null === $this->mimetype) {
            $finfo = new \finfo(FILEINFO_MIME);
            $this->mimetype = $finfo->buffer($this->getContent());
        }

        return $this->mimetype;
    }

    /**
     * Determines if file is binary.
     *
     * @return bool
     */
    public function isBinary()
    {
        return !$this->isText();
    }

    /**
     * Determines if file is text.
     *
     * @return bool
     */
    public function isText()
    {
        return (bool) preg_match('#^text/|^application/xml#', $this->getMimetype());
    }
}


<?php

/**
 * This file is part of Gitonomy.
 *
 * (c) Alexandre Salomé <alexandre.salome@gmail.com>
 * (c) Julien DIDIER <genzo.wm@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */
namespace Gitonomy\Git\Diff;

use Gitonomy\Git\Parser\DiffParser;
use Gitonomy\Git\Repository;

/**
 * Representation of a diff.
 *
 * @author Alexandre Salomé <alexandre.salome@gmail.com>
 */
class Diff
{
    /**
     * @var array
     */
    protected $files;

    /**
     * @var string
     */
    protected $rawDiff;

    /**
     * Constructs a new diff for a given revision.
     *
     * @param array  $files   The files
     * @param string $rawDiff The raw diff
     */
    public function __construct(array $files, $rawDiff)
    {
        $this->files = $files;
        $this->rawDiff = $rawDiff;
    }

    /**
     * @return Diff
     */
    public static function parse($rawDiff)
    {
        $parser = new DiffParser();
        $parser->parse($rawDiff);

        return new self($parser->files, $rawDiff);
    }

    public function setRepository(Repository $repository)
    {
        foreach ($this->files as $file) {
            $file->setRepository($repository);
        }
    }

    /**
     * @return array
     */
    public function getRevisions()
    {
        return $this->revisions;
    }

    /**
     * Get list of files modified in the diff's revision.
     *
     * @return array An array of Diff\File objects
     */
    public function getFiles()
    {
        return $this->files;
    }

    /**
     * Returns the raw diff.
     *
     * @return string The raw diff
     */
    public function getRawDiff()
    {
        return $this->rawDiff;
    }

    /**
     * Export a diff as array.
     *
     * @return array The array
     */
    public function toArray()
    {
        return array(
            'rawDiff' => $this->rawDiff,
            'files' => array_map(
                function (File $file) {
                    return $file->toArray();
                },
                $this->files
            ),
        );
    }

    /**
     * Create a new instance of Diff from an array.
     *
     * @param array $array The array
     *
     * @return Diff The new instance
     */
    public static function fromArray(array $array)
    {
        return new static(
            array_map(
                function ($array) {
                    return File::fromArray($array);
                },
                $array['files']
            ),
            $array['rawDiff']
        );
    }
}


<?php

/**
 * This file is part of the EmailReplyParser package.
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @license    MIT License
 */

namespace EmailReplyParser\Parser;

use EmailReplyParser\Email;
use EmailReplyParser\Fragment;

/**
 * @author William Durand <william.durand1@gmail.com>
 */
class EmailParser
{
    const QUOTE_REGEX = '/^>+/s';

    /**
     * Regex to match signatures
     *
     * @var string
     */
    private $signatureRegex = '/(?:^\s*--|^\s*__|^-\w|^-- $)|(?:^Sent from my (?:\s*\w+){1,4}$)|(?:^={30,}$)$/s';

    /**
     * @var string[]
     */
    private $quoteHeadersRegex = array(
        '/^\s*(On(?:(?!^>*\s*On\b|\bwrote:).){0,1000}wrote:)$/ms', // On DATE, NAME <EMAIL> wrote:
        '/^\s*(Le(?:(?!^>*\s*Le\b|\bécrit:).){0,1000}écrit :)$/ms', // Le DATE, NAME <EMAIL> a écrit :
        '/^\s*(El(?:(?!^>*\s*El\b|\bescribió:).){0,1000}escribió:)$/ms', // El DATE, NAME <EMAIL> escribió:
        '/^\s*(Il(?:(?!^>*\s*Il\b|\bscritto:).){0,1000}scritto:)$/ms', // Il DATE, NAME <EMAIL> ha scritto:
        '/^[\S\s]+ (написа(л|ла|в)+)+:$/msu', // Everything before написал: not ending on wrote:
        '/^\s*(Op\s.+?(schreef|geschreven).+:)$/ms', // Op DATE schreef NAME <EMAIL>:, Op DATE heeft NAME <EMAIL> het volgende geschreven:
        '/^\s*((W\sdniu|Dnia)\s.+?(pisze|napisał(\(a\))?):)$/msu', // W dniu DATE, NAME <EMAIL> pisze|napisał:
        '/^\s*(Den\s.+\sskrev\s.+:)$/m', // Den DATE skrev NAME <EMAIL>:
        '/^\s*(Am\s.+\sum\s.+\sschrieb\s.+:)$/m', // Am DATE um TIME schrieb NAME:
        '/^(在.+写道：)$/ms', // > 在 DATE, TIME, NAME 写道：
        '/^(20[0-9]{2}\..+\s작성:)$/m', // DATE TIME NAME 작성:
        '/^(20[0-9]{2}\/.+のメッセージ:)$/m', // DATE TIME、NAME のメッセージ:
        '/^(.+\s<.+>\sschrieb:)$/m', // NAME <EMAIL> schrieb:
        '/^\s*(From\s?:.+\s?(\[|<).+(\]|>))/mu', // "From: NAME <EMAIL>" OR "From : NAME <EMAIL>" OR "From : NAME<EMAIL>"(With support whitespace before start and before <)
        '/^\s*(发件人\s?:.+\s?(\[|<).+(\]|>))/mu', // "发件人: NAME <EMAIL>" OR "发件人 : NAME <EMAIL>" OR "发件人 : NAME<EMAIL>"(With support whitespace before start and before <)
        '/^\s*(De\s?:.+\s?(\[|<).+(\]|>))/mu', // "De: NAME <EMAIL>" OR "De : NAME <EMAIL>" OR "De : NAME<EMAIL>"  (With support whitespace before start and before <)
        '/^\s*(Van\s?:.+\s?(\[|<).+(\]|>))/mu', // "Van: NAME <EMAIL>" OR "Van : NAME <EMAIL>" OR "Van : NAME<EMAIL>"  (With support whitespace before start and before <)
        '/^\s*(Da\s?:.+\s?(\[|<).+(\]|>))/mu', // "Da: NAME <EMAIL>" OR "Da : NAME <EMAIL>" OR "Da : NAME<EMAIL>"  (With support whitespace before start and before <)
        '/^(20[0-9]{2}\-(?:0?[1-9]|1[012])\-(?:0?[0-9]|[1-2][0-9]|3[01]|[1-9])\s[0-2]?[0-9]:\d{2}\s.+?:)$/ms', // 20YY-MM-DD HH:II GMT+01:00 NAME <EMAIL>:
        '/^\s*([a-z]{3,4}\.\s.+\sskrev\s.+:)$/ms', // DATE skrev NAME <EMAIL>:
    );

    /**
     * @var FragmentDTO[]
     */
    private $fragments = array();

    /**
     * Parse a text which represents an email and splits it into fragments.
     *
     * @param string $text A text.
     *
     * @return Email
     */
    public function parse($text)
    {
        $text = str_replace(array("\r\n", "\r"), "\n", $text);

        foreach ($this->quoteHeadersRegex as $regex) {
            if (preg_match($regex, $text, $matches)) {
                $text = str_replace($matches[1], str_replace("\n", ' ', $matches[1]), $text);
            }
        }

        $fragment = null;
        $text_array = explode("\n", $text);
        while (($line = array_pop($text_array)) !== NULL) {
            $line = ltrim($line, "\n");

            if (!$this->isSignature($line)) {
                $line = rtrim($line);
            }

            if ($fragment) {
                $first = reset($fragment->lines);

                if ($this->isSignature($first)) {
                    $fragment->isSignature = true;
                    $this->addFragment($fragment);

                    $fragment = null;
                } elseif (empty($line) && $this->isQuoteHeader($first)) {
                    $fragment->isQuoted = true;
                    $this->addFragment($fragment);

                    $fragment = null;
                }
            }

            $isQuoted = $this->isQuote($line);

            if (null === $fragment || !$this->isFragmentLine($fragment, $line, $isQuoted)) {
                if ($fragment) {
                    $this->addFragment($fragment);
                }

                $fragment = new FragmentDTO();
                $fragment->isQuoted = $isQuoted;
            }

            array_unshift($fragment->lines, $line);
        }

        if ($fragment) {
            $this->addFragment($fragment);
        }

        $email = $this->createEmail($this->fragments);

        $this->fragments = array();

        return $email;
    }

    /**
     * @return string[]
     */
    public function getQuoteHeadersRegex()
    {
        return $this->quoteHeadersRegex;
    }

    /**
     * @param string[] $quoteHeadersRegex
     *
     * @return EmailParser
     */
    public function setQuoteHeadersRegex(array $quoteHeadersRegex)
    {
        $this->quoteHeadersRegex = $quoteHeadersRegex;

        return $this;
    }

    /**
     * @return string
     * @since 2.7.0
     */
    public function getSignatureRegex()
    {
        return $this->signatureRegex;
    }

    /**
     * @param string $signatureRegex
     *
     * @return EmailParser
     * @since 2.7.0
     */
    public function setSignatureRegex($signatureRegex)
    {
        $this->signatureRegex = $signatureRegex;

        return $this;
    }

    /**
     * @param FragmentDTO[] $fragmentDTOs
     *
     * @return Email
     */
    protected function createEmail(array $fragmentDTOs)
    {
        $fragments = array();
        foreach ($fragmentDTOs as $fragment) {
            $fragments[] = new Fragment(
                preg_replace("/^\n/", '', implode("\n", $fragment->lines)),
                $fragment->isHidden,
                $fragment->isSignature,
                $fragment->isQuoted
            );
        }

        return new Email($fragments);
    }

    private function isQuoteHeader($line)
    {
        foreach ($this->quoteHeadersRegex as $regex) {
            if (preg_match($regex, $line)) {
                return true;
            }
        }

        return false;
    }

    private function isSignature($line)
    {
        return preg_match($this->signatureRegex, $line) ? true : false;
    }

    /**
     * @param string $line
     * @return bool
     */
    private function isQuote($line)
    {
        return preg_match(static::QUOTE_REGEX, $line) ? true : false;
    }

    private function isEmpty(FragmentDTO $fragment)
    {
        return '' === implode('', $fragment->lines);
    }

    /**
     * @param FragmentDTO $fragment
     * @param string  $line
     * @param boolean $isQuoted
     * @return bool
     */
    private function isFragmentLine(FragmentDTO $fragment, $line, $isQuoted)
    {
        return $fragment->isQuoted === $isQuoted ||
            ($fragment->isQuoted && ($this->isQuoteHeader($line) || empty($line)));
    }

    private function addFragment(FragmentDTO $fragment)
    {
        if ($fragment->isQuoted || $fragment->isSignature || $this->isEmpty($fragment)) {
            $fragment->isHidden = true;
        }

        array_unshift($this->fragments, $fragment);
    }
}


<?php
/**
 * Author: Nil Portugués Calderó <contact@nilportugues.com>
 * Date: 12/25/14
 * Time: 12:12 PM.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace NilPortugues\Sql\QueryBuilder\Manipulation;

use NilPortugues\Sql\QueryBuilder\Syntax\Column;
use NilPortugues\Sql\QueryBuilder\Syntax\OrderBy;
use NilPortugues\Sql\QueryBuilder\Syntax\SyntaxFactory;

/**
 * Class ColumnQuery.
 */
class ColumnQuery
{
    /**
     * @var array
     */
    protected $columns = [];

    /**
     * @var array
     */
    protected $columnSelects = [];

    /**
     * @var array
     */
    protected $columnValues = [];

    /**
     * @var array
     */
    protected $columnFuncs = [];

    /**
     * @var bool
     */
    protected $isCount = false;

    /**
     * @var Select
     */
    protected $select;

    /**
     * @var JoinQuery
     */
    protected $joinQuery;

    /**
     * @param Select    $select
     * @param JoinQuery $joinQuery
     * @param array     $columns
     */
    public function __construct(Select $select, JoinQuery $joinQuery, array $columns = null)
    {
        $this->select = $select;
        $this->joinQuery = $joinQuery;

        if (!isset($columns)) {
            $columns = array(Column::ALL);
        }

        if (\count($columns)) {
            $this->setColumns($columns);
        }
    }

    /**
     * @param     $start
     * @param int $count
     *
     * @return Select
     */
    public function limit($start, $count = 0)
    {
        return $this->select->limit($start, $count);
    }

    /**
     * @param string $whereOperator
     *
     * @return \NilPortugues\Sql\QueryBuilder\Syntax\Where
     */
    public function where($whereOperator = 'AND')
    {
        return $this->select->where($whereOperator);
    }

    /**
     * @param string $column
     * @param string $direction
     * @param null   $table
     *
     * @return Select
     */
    public function orderBy($column, $direction = OrderBy::ASC, $table = null)
    {
        return $this->select->orderBy($column, $direction, $table);
    }

    /**
     * @param string[] $columns
     *
     * @return Select
     */
    public function groupBy(array $columns)
    {
        return $this->select->groupBy($columns);
    }

    /**
     * Allows setting a Select query as a column value.
     *
     * @param array $column
     *
     * @return $this
     */
    public function setSelectAsColumn(array $column)
    {
        $this->columnSelects[] = $column;

        return $this;
    }

    /**
     * @return array
     */
    public function getColumnSelects()
    {
        return $this->columnSelects;
    }

    /**
     * Allows setting a value to the select statement.
     *
     * @param string $value
     * @param string $alias
     *
     * @return $this
     */
    public function setValueAsColumn($value, $alias)
    {
        $this->columnValues[$alias] = $value;

        return $this;
    }

    /**
     * @return array
     */
    public function getColumnValues()
    {
        return $this->columnValues;
    }

    /**
     * Allows calculation on columns using predefined SQL functions.
     *
     * @param string   $funcName
     * @param string[] $arguments
     * @param string   $alias
     *
     * @return $this
     */
    public function setFunctionAsColumn($funcName, array $arguments, $alias)
    {
        $this->columnFuncs[$alias] = ['func' => $funcName, 'args' => $arguments];

        return $this;
    }

    /**
     * @return array
     */
    public function getColumnFuncs()
    {
        return $this->columnFuncs;
    }

    /**
     * @param string $columnName
     * @param string $alias
     *
     * @return $this
     */
    public function count($columnName = '*', $alias = '')
    {
        $table = $this->select->getTable();

        $count = 'COUNT(';
        $count .= ($columnName !== '*') ? "$table.{$columnName}" : '*';
        $count .= ')';

        if (isset($alias) && \strlen($alias) > 0) {
            $count .= ' AS "'.$alias.'"';
        }

        $this->columns = array($count);
        $this->isCount = true;

        return $this;
    }

    /**
     * @return bool
     */
    public function isCount()
    {
        return $this->isCount;
    }

    /**
     * @return array
     */
    public function getAllColumns()
    {
        $columns = $this->getColumns();

        foreach ($this->joinQuery->getJoins() as $join) {
            $joinCols = $join->getAllColumns();
            $columns = \array_merge($columns, $joinCols);
        }

        return $columns;
    }

    /**
     * @return \NilPortugues\Sql\QueryBuilder\Syntax\Column
     *
     * @throws QueryException
     */
    public function getColumns()
    {
        if (\is_null($this->select->getTable())) {
            throw new QueryException('No table specified for the Select instance');
        }

        return SyntaxFactory::createColumns($this->columns, $this->select->getTable());
    }

    /**
     * Sets the column names used to write the SELECT statement.
     * If key is set, key is the column's alias. Value is always the column names.
     *
     * @param array $columns
     *
     * @return $this
     */
    public function setColumns(array $columns)
    {
        $this->columns = $columns;

        return $this;
    }
}


<?php
/**
 * Author: Nil Portugués Calderó <contact@nilportugues.com>
 * Date: 9/12/14
 * Time: 7:15 PM.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace NilPortugues\Sql\QueryBuilder\Builder\Syntax;

use NilPortugues\Sql\QueryBuilder\Builder\GenericBuilder;
use NilPortugues\Sql\QueryBuilder\Manipulation\Minus;

/**
 * Class MinusWriter.
 */
class MinusWriter
{
    /**
     * @var GenericBuilder
     */
    protected $writer;

    /**
     * @param GenericBuilder $writer
     */
    public function __construct(GenericBuilder $writer)
    {
        $this->writer = $writer;
    }

    /**
     * @param Minus $minus
     *
     * @return string
     */
    public function write(Minus $minus)
    {
        $first = $this->writer->write($minus->getFirst());
        $second = $this->writer->write($minus->getSecond());

        return $first."\n".Minus::MINUS."\n".$second;
    }
}


<?php
/**
 * Author: Nil Portugués Calderó <contact@nilportugues.com>
 * Date: 6/11/14
 * Time: 1:50 AM.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace NilPortugues\Sql\QueryBuilder\Builder\Syntax;

use NilPortugues\Sql\QueryBuilder\Builder\GenericBuilder;
use NilPortugues\Sql\QueryBuilder\Manipulation\Select;
use NilPortugues\Sql\QueryBuilder\Syntax\Column;
use NilPortugues\Sql\QueryBuilder\Syntax\OrderBy;
use NilPortugues\Sql\QueryBuilder\Syntax\SyntaxFactory;

/**
 * Class SelectWriter.
 */
class SelectWriter extends AbstractBaseWriter
{
    /**
     * @param        $alias
     * @param Select $select
     *
     * @return Column
     */
    public function selectToColumn($alias, Select $select)
    {
        $selectAsColumn = $this->write($select);

        if (!empty($selectAsColumn)) {
            $selectAsColumn = '('.$selectAsColumn.')';
        }

        $column = array($alias => $selectAsColumn);

        return SyntaxFactory::createColumn($column, null);
    }

    /**
     * @param Select $select
     *
     * @return string
     */
    public function write(Select $select)
    {
        if ($select->isJoinSelect()) {
            return $this->writer->writeJoin($select);
        }

        return $this->writeSelectQuery($select);
    }

    /**
     * @param Select $select
     *
     * @return string
     */
    protected function writeSelectQuery(Select $select)
    {
        $parts = ['SELECT'];

        if ($select->isDistinct()) {
            $parts[] = 'DISTINCT';
        }

        $this->writeSelectColumns($select, $parts);
        $this->writeSelectFrom($select, $parts);
        $this->writeSelectJoins($select, $parts);
        $this->writeSelectWhere($select, $parts);
        $this->writeSelectGroupBy($select, $parts);
        $this->writeSelectHaving($select, $parts);
        $this->writeSelectOrderBy($select, $parts);
        $this->writeSelectLimit($select, $parts);

        return AbstractBaseWriter::writeQueryComment($select).implode(' ', \array_filter($parts));
    }

    /**
     * @param Select   $select
     * @param string[] $parts
     *
     * @return $this
     */
    public function writeSelectColumns(Select $select, array &$parts)
    {
        if ($select->isCount() === false) {
            $columns = $this->writeColumnAlias(
                $select->getAllColumns(),
                $this->columnWriter->writeSelectsAsColumns($select),
                $this->columnWriter->writeValueAsColumns($select),
                $this->columnWriter->writeFuncAsColumns($select)
            );

            $parts = \array_merge($parts, [implode(', ', $columns)]);

            return $this;
        }

        $columns = $select->getColumns();
        $column = \array_pop($columns);
        $columnList = $column->getName();

        $parts = \array_merge($parts, [$columnList]);

        return $this;
    }

    /**
     * @param $tableColumns
     * @param $selectAsColumns
     * @param $valueAsColumns
     * @param $funcAsColumns
     *
     * @return array
     */
    protected function writeColumnAlias($tableColumns, $selectAsColumns, $valueAsColumns, $funcAsColumns)
    {
        $columns = \array_merge($tableColumns, $selectAsColumns, $valueAsColumns, $funcAsColumns);

        \array_walk(
            $columns,
            function (&$column) {
                $column = $this->columnWriter->writeColumnWithAlias($column);
            }
        );

        return $columns;
    }

    /**
     * @param Select   $select
     * @param string[] $parts
     *
     * @return $this
     */
    public function writeSelectFrom(Select $select, array &$parts)
    {
        $parts = \array_merge(
            $parts,
            ['FROM '.$this->writer->writeTableWithAlias($select->getTable())]
        );

        return $this;
    }

    /**
     * @param Select $select
     * @param array  $parts
     *
     * @return $this
     */
    public function writeSelectJoins(Select $select, array &$parts)
    {
        $parts = \array_merge(
            $parts,
            [$this->writeSelectAggrupation($select, $this->writer, 'getAllJoins', 'writeJoin', ' ')]
        );

        return $this;
    }

    /**
     * @param Select $select
     * @param        $writer
     * @param string $getMethod
     * @param string $writeMethod
     * @param string $glue
     * @param string $prepend
     *
     * @return string
     */
    protected function writeSelectAggrupation(Select $select, $writer, $getMethod, $writeMethod, $glue, $prepend = '')
    {
        $str = '';
        $joins = $select->$getMethod();

        if (!empty($joins)) {
            \array_walk(
                $joins,
                function (&$join) use ($writer, $writeMethod) {
                    $join = $writer->$writeMethod($join);
                }
            );

            $str = $prepend.implode($glue, $joins);
        }

        return $str;
    }

    /**
     * @param Select $select
     * @param array  $parts
     *
     * @return $this
     */
    public function writeSelectWhere(Select $select, array &$parts)
    {
        $str = '';
        $wheres = $this->writeSelectWheres($select->getAllWheres());
        $wheres = \array_filter($wheres);

        if (\count($wheres) > 0) {
            $str = 'WHERE ';
            $separator = ' '.$this->writer->writeConjunction($select->getWhereOperator()).' ';

            $str .= \implode($separator, $wheres);
        }

        $parts = \array_merge($parts, [$str]);

        return $this;
    }

    /**
     * @param array $wheres
     *
     * @return array
     */
    protected function writeSelectWheres(array $wheres)
    {
        $whereWriter = WriterFactory::createWhereWriter($this->writer, $this->placeholderWriter);

        \array_walk(
            $wheres,
            function (&$where) use (&$whereWriter) {

                $where = $whereWriter->writeWhere($where);
            }
        );

        return $wheres;
    }

    /**
     * @param Select $select
     * @param array  $parts
     *
     * @return $this
     */
    public function writeSelectGroupBy(Select $select, array &$parts)
    {
        $groupBy = $this->writeSelectAggrupation(
            $select,
            $this->columnWriter,
            'getGroupBy',
            'writeColumn',
            ', ',
            'GROUP BY '
        );

        $parts = \array_merge($parts, [$groupBy]);

        return $this;
    }

    /**
     * @param Select $select
     * @param array  $parts
     *
     * @return $this
     */
    public function writeSelectHaving(Select $select, array &$parts)
    {
        $str = '';
        $havingArray = $select->getAllHavings();

        if (\count($havingArray) > 0) {
            $placeholder = $this->placeholderWriter;
            $writer = $this->writer;

            $str = 'HAVING ';
            $separator = ' '.$select->getHavingOperator().' ';
            $havingArray = $this->getHavingConditions($havingArray, $select, $writer, $placeholder);

            $str .= \implode($separator, $havingArray);
        }

        $parts = \array_merge($parts, [$str]);

        return $this;
    }

    /**
     * @param array             $havingArray
     * @param Select            $select
     * @param GenericBuilder    $writer
     * @param PlaceholderWriter $placeholder
     *
     * @return mixed
     */
    protected function getHavingConditions(
        array &$havingArray,
        Select $select,
        GenericBuilder $writer,
        PlaceholderWriter $placeholder
    ) {
        \array_walk(
            $havingArray,
            function (&$having) use ($select, $writer, $placeholder) {

                $whereWriter = WriterFactory::createWhereWriter($writer, $placeholder);
                $clauses = $whereWriter->writeWhereClauses($having);
                $having = \implode($this->writer->writeConjunction($select->getHavingOperator()), $clauses);
            }
        );

        return $havingArray;
    }

    /**
     * @param Select $select
     * @param array  $parts
     *
     * @return $this
     */
    protected function writeSelectOrderBy(Select $select, array &$parts)
    {
        $str = '';
        if (\count($select->getAllOrderBy())) {
            $orderByArray = $select->getAllOrderBy();
            \array_walk(
                $orderByArray,
                function (&$orderBy) {
                    $orderBy = $this->writeOrderBy($orderBy);
                }
            );

            $str = 'ORDER BY ';
            $str .= \implode(', ', $orderByArray);
        }

        $parts = \array_merge($parts, [$str]);

        return $this;
    }

    /**
     * @param OrderBy $orderBy
     *
     * @return string
     */
    public function writeOrderBy(OrderBy $orderBy)
    {
        $column = $this->columnWriter->writeColumn($orderBy->getColumn());

        return $column.' '.$orderBy->getDirection();
    }

    /**
     * @param Select $select
     * @param array  $parts
     *
     * @return $this
     */
    protected function writeSelectLimit(Select $select, array &$parts)
    {
        $mask = $this->getStartingLimit($select).$this->getLimitCount($select);

        $limit = '';

        if ($mask !== '00') {
            $start = $this->placeholderWriter->add($select->getLimitStart());
            $count = $this->placeholderWriter->add($select->getLimitCount());

            $limit = "LIMIT {$start}, {$count}";
        }

        $parts = \array_merge($parts, [$limit]);

        return $this;
    }

    /**
     * @param Select $select
     *
     * @return string
     */
    protected function getStartingLimit(Select $select)
    {
        return (null === $select->getLimitStart() || 0 == $select->getLimitStart()) ? '0' : '1';
    }

    /**
     * @param Select $select
     *
     * @return string
     */
    protected function getLimitCount(Select $select)
    {
        return (null === $select->getLimitCount()) ? '0' : '1';
    }
}


<?php
/**
 * Author: Nil Portugués Calderó <contact@nilportugues.com>
 * Date: 6/11/14
 * Time: 1:51 AM.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace NilPortugues\Sql\QueryBuilder\Builder\Syntax;

use NilPortugues\Sql\QueryBuilder\Builder\GenericBuilder;
use NilPortugues\Sql\QueryBuilder\Manipulation\Insert;
use NilPortugues\Sql\QueryBuilder\Manipulation\QueryException;

/**
 * Class InsertWriter.
 */
class InsertWriter
{
    /**
     * @var GenericBuilder
     */
    protected $writer;

    /**
     * @var ColumnWriter
     */
    protected $columnWriter;

    /**
     * @param GenericBuilder    $writer
     * @param PlaceholderWriter $placeholder
     */
    public function __construct(GenericBuilder $writer, PlaceholderWriter $placeholder)
    {
        $this->writer = $writer;
        $this->columnWriter = WriterFactory::createColumnWriter($this->writer, $placeholder);
    }

    /**
     * @param Insert $insert
     *
     * @throws QueryException
     *
     * @return string
     */
    public function write(Insert $insert)
    {
        $columns = $insert->getColumns();

        if (empty($columns)) {
            throw new QueryException('No columns were defined for the current schema.');
        }

        $columns = $this->writeQueryColumns($columns);
        $values = $this->writeQueryValues($insert->getValues());
        $table = $this->writer->writeTable($insert->getTable());
        $comment = AbstractBaseWriter::writeQueryComment($insert);

        return $comment."INSERT INTO {$table} ($columns) VALUES ($values)";
    }

    /**
     * @param $columns
     *
     * @return string
     */
    protected function writeQueryColumns($columns)
    {
        return $this->writeCommaSeparatedValues($columns, $this->columnWriter, 'writeColumn');
    }

    /**
     * @param $collection
     * @param $writer
     * @param string $method
     *
     * @return string
     */
    protected function writeCommaSeparatedValues($collection, $writer, $method)
    {
        \array_walk(
            $collection,
            function (&$data) use ($writer, $method) {
                $data = $writer->$method($data);
            }
        );

        return \implode(', ', $collection);
    }

    /**
     * @param $values
     *
     * @return string
     */
    protected function writeQueryValues($values)
    {
        return $this->writeCommaSeparatedValues($values, $this->writer, 'writePlaceholderValue');
    }
}


<?php
/**
 * Author: Nil Portugués Calderó <contact@nilportugues.com>
 * Date: 6/3/14
 * Time: 12:07 AM.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace NilPortugues\Sql\QueryBuilder\Builder;

use NilPortugues\Sql\QueryBuilder\Syntax\Column;
use NilPortugues\Sql\QueryBuilder\Syntax\Table;

/**
 * Class MySqlBuilder.
 */
class MySqlBuilder extends GenericBuilder
{
    /**
     * {@inheritdoc}
     *
     * @param Column $column
     *
     * @return string
     */
    public function writeColumnName(Column $column)
    {
        if ($column->isAll()) {
            return '*';
        }

        if (false !== strpos($column->getName(), '(')) {
            return parent::writeColumnName($column);
        }

        return $this->wrapper(parent::writeColumnName($column));
    }

    /**
     * {@inheritdoc}
     *
     * @param Table $table
     *
     * @return string
     */
    public function writeTableName(Table $table)
    {
        return $this->wrapper(parent::writeTableName($table));
    }

    /**
     * {@inheritdoc}
     *
     * @param $alias
     *
     * @return string
     */
    public function writeTableAlias($alias)
    {
        return $this->wrapper(parent::writeTableAlias($alias));
    }

    /**
     * {@inheritdoc}
     *
     * @param $alias
     *
     * @return string
     */
    public function writeColumnAlias($alias)
    {
        return $this->wrapper($alias);
    }

    /**
     * @param        $string
     * @param string $char
     *
     * @return string
     */
    protected function wrapper($string, $char = '`')
    {
        if (0 === strlen($string)) {
            return '';
        }

        return $char.$string.$char;
    }
}


<?php
/**
 * Author: Nil Portugués Calderó <contact@nilportugues.com>
 * Date: 6/3/14
 * Time: 12:07 AM.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace NilPortugues\Sql\QueryBuilder\Syntax;

use NilPortugues\Sql\QueryBuilder\Manipulation\QueryException;
use NilPortugues\Sql\QueryBuilder\Manipulation\QueryFactory;
use NilPortugues\Sql\QueryBuilder\Manipulation\QueryInterface;
use NilPortugues\Sql\QueryBuilder\Manipulation\Select;

/**
 * Class Where.
 */
class Where
{
    const OPERATOR_GREATER_THAN_OR_EQUAL = '>=';
    const OPERATOR_GREATER_THAN = '>';
    const OPERATOR_LESS_THAN_OR_EQUAL = '<=';
    const OPERATOR_LESS_THAN = '<';
    const OPERATOR_LIKE = 'LIKE';
    const OPERATOR_NOT_LIKE = 'NOT LIKE';
    const OPERATOR_EQUAL = '=';
    const OPERATOR_NOT_EQUAL = '<>';
    const CONJUNCTION_AND = 'AND';
    const CONJUNCTION_AND_NOT = 'AND NOT';
    const CONJUNCTION_OR = 'OR';
    const CONJUNCTION_OR_NOT = 'OR NOT';
    const CONJUNCTION_EXISTS = 'EXISTS';
    const CONJUNCTION_NOT_EXISTS = 'NOT EXISTS';

    /**
     * @var array
     */
    protected $comparisons = [];

    /**
     * @var array
     */
    protected $betweens = [];

    /**
     * @var array
     */
    protected $isNull = [];

    /**
     * @var array
     */
    protected $isNotNull = [];

    /**
     * @var array
     */
    protected $booleans = [];

    /**
     * @var array
     */
    protected $match = [];

    /**
     * @var array
     */
    protected $ins = [];

    /**
     * @var array
     */
    protected $notIns = [];

    /**
     * @var array
     */
    protected $subWheres = [];

    /**
     * @var string
     */
    protected $conjunction = self::CONJUNCTION_AND;

    /**
     * @var QueryInterface
     */
    protected $query;

    /**
     * @var Table
     */
    protected $table;

    /**
     * @var array
     */
    protected $exists = [];

    /**
     * @var array
     */
    protected $notExists = [];

    /**
     * @var array
     */
    protected $notBetweens = [];

    /**
     * @param QueryInterface $query
     */
    public function __construct(QueryInterface $query)
    {
        $this->query = $query;
    }

    /**
     * Deep copy for nested references.
     *
     * @return mixed
     */
    public function __clone()
    {
        return \unserialize(\serialize($this));
    }

    /**
     * @return bool
     */
    public function isEmpty()
    {
        $empty = \array_merge(
            $this->comparisons,
            $this->booleans,
            $this->betweens,
            $this->isNotNull,
            $this->isNull,
            $this->ins,
            $this->notIns,
            $this->subWheres,
            $this->exists
        );

        return 0 == \count($empty);
    }

    /**
     * @return string
     */
    public function getConjunction()
    {
        return $this->conjunction;
    }

    /**
     * @param string $operator
     *
     * @return $this
     *
     * @throws QueryException
     */
    public function conjunction($operator)
    {
        if (false === \in_array(
                $operator,
                [self::CONJUNCTION_AND, self::CONJUNCTION_OR, self::CONJUNCTION_OR_NOT, self::CONJUNCTION_AND_NOT]
            )
        ) {
            throw new QueryException(
                "Invalid conjunction specified, must be one of AND or OR, but '".$operator."' was found."
            );
        }
        $this->conjunction = $operator;

        return $this;
    }

    /**
     * @return array
     */
    public function getSubWheres()
    {
        return $this->subWheres;
    }

    /**
     * @param $operator
     *
     * @return Where
     */
    public function subWhere($operator = 'OR')
    {
        /** @var Where $filter */
        $filter = QueryFactory::createWhere($this->query);
        $filter->conjunction($operator);
        $filter->setTable($this->getTable());

        $this->subWheres[] = $filter;

        return $filter;
    }

    /**
     * @return Table
     */
    public function getTable()
    {
        return $this->query->getTable();
    }

    /**
     * Used for subWhere query building.
     *
     * @param Table $table string
     *
     * @return $this
     */
    public function setTable($table)
    {
        $this->table = $table;

        return $this;
    }

    /**
     * equals alias.
     *
     * @param     $column
     * @param int $value
     *
     * @return static
     */
    public function eq($column, $value)
    {
        return $this->equals($column, $value);
    }

    /**
     * @param $column
     * @param $value
     *
     * @return static
     */
    public function equals($column, $value)
    {
        return $this->compare($column, $value, self::OPERATOR_EQUAL);
    }

    /**
     * @param        $column
     * @param        $value
     * @param string $operator
     *
     * @return $this
     */
    protected function compare($column, $value, $operator)
    {
        $column = $this->prepareColumn($column);

        $this->comparisons[] = [
            'subject' => $column,
            'conjunction' => $operator,
            'target' => $value,
        ];

        return $this;
    }

    /**
     * @param $column
     *
     * @return Column|Select
     */
    protected function prepareColumn($column)
    {
        //This condition handles the "Select as a a column" special case.
        //or when compare column is customized.
        if ($column instanceof Select || $column instanceof Column) {
            return $column;
        }

        $newColumn = [$column];

        return SyntaxFactory::createColumn($newColumn, $this->getTable());
    }

    /**
     * @param string $column
     * @param int    $value
     *
     * @return static
     */
    public function notEquals($column, $value)
    {
        return $this->compare($column, $value, self::OPERATOR_NOT_EQUAL);
    }

    /**
     * @param string $column
     * @param int    $value
     *
     * @return static
     */
    public function greaterThan($column, $value)
    {
        return $this->compare($column, $value, self::OPERATOR_GREATER_THAN);
    }

    /**
     * @param string $column
     * @param int    $value
     *
     * @return static
     */
    public function greaterThanOrEqual($column, $value)
    {
        return $this->compare($column, $value, self::OPERATOR_GREATER_THAN_OR_EQUAL);
    }

    /**
     * @param string $column
     * @param int    $value
     *
     * @return static
     */
    public function lessThan($column, $value)
    {
        return $this->compare($column, $value, self::OPERATOR_LESS_THAN);
    }

    /**
     * @param string $column
     * @param int    $value
     *
     * @return static
     */
    public function lessThanOrEqual($column, $value)
    {
        return $this->compare($column, $value, self::OPERATOR_LESS_THAN_OR_EQUAL);
    }

    /**
     * @param string $column
     * @param        $value
     *
     * @return static
     */
    public function like($column, $value)
    {
        return $this->compare($column, $value, self::OPERATOR_LIKE);
    }

    /**
     * @param string $column
     * @param int    $value
     *
     * @return static
     */
    public function notLike($column, $value)
    {
        return $this->compare($column, $value, self::OPERATOR_NOT_LIKE);
    }

    /**
     * @param string[] $columns
     * @param mixed[]  $values
     *
     * @return static
     */
    public function match(array $columns, array $values)
    {
        return $this->genericMatch($columns, $values, 'natural');
    }

    /**
     * @param string[] $columns
     * @param mixed[]  $values
     * @param string   $mode
     *
     * @return $this
     */
    protected function genericMatch(array &$columns, array &$values, $mode)
    {
        $this->match[] = [
            'columns' => $columns,
            'values' => $values,
            'mode' => $mode,
        ];

        return $this;
    }

    /**
     * @param string $literal
     *
     * @return $this
     */
    public function asLiteral($literal)
    {
        $this->comparisons[] = $literal;

        return $this;
    }

    /**
     * @param string[] $columns
     * @param mixed[]  $values
     *
     * @return $this
     */
    public function matchBoolean(array $columns, array $values)
    {
        return $this->genericMatch($columns, $values, 'boolean');
    }

    /**
     * @param string[] $columns
     * @param mixed[]  $values
     *
     * @return $this
     */
    public function matchWithQueryExpansion(array $columns, array $values)
    {
        return $this->genericMatch($columns, $values, 'query_expansion');
    }

    /**
     * @param string $column
     * @param int[]  $values
     *
     * @return $this
     */
    public function in($column, array $values)
    {
        $this->ins[$column] = $values;

        return $this;
    }

    /**
     * @param string $column
     * @param int[]  $values
     *
     * @return $this
     */
    public function notIn($column, array $values)
    {
        $this->notIns[$column] = $values;

        return $this;
    }

    /**
     * @param string $column
     * @param int    $a
     * @param int    $b
     *
     * @return $this
     */
    public function between($column, $a, $b)
    {
        $column = $this->prepareColumn($column);
        $this->betweens[] = ['subject' => $column, 'a' => $a, 'b' => $b];

        return $this;
    }

    /**
     * @param string $column
     * @param int    $a
     * @param int    $b
     *
     * @return $this
     */
    public function notBetween($column, $a, $b)
    {
        $column = $this->prepareColumn($column);
        $this->notBetweens[] = ['subject' => $column, 'a' => $a, 'b' => $b];

        return $this;
    }

    /**
     * @param string $column
     *
     * @return static
     */
    public function isNull($column)
    {
        $column = $this->prepareColumn($column);
        $this->isNull[] = ['subject' => $column];

        return $this;
    }

    /**
     * @param string $column
     *
     * @return $this
     */
    public function isNotNull($column)
    {
        $column = $this->prepareColumn($column);
        $this->isNotNull[] = ['subject' => $column];

        return $this;
    }

    /**
     * @param string $column
     * @param int    $value
     *
     * @return $this
     */
    public function addBitClause($column, $value)
    {
        $column = $this->prepareColumn($column);
        $this->booleans[] = ['subject' => $column, 'value' => $value];

        return $this;
    }

    /**
     * @param Select $select
     *
     * @return $this
     */
    public function exists(Select $select)
    {
        $this->exists[] = $select;

        return $this;
    }

    /**
     * @return array
     */
    public function getExists()
    {
        return $this->exists;
    }

    /**
     * @param Select $select
     *
     * @return $this
     */
    public function notExists(Select $select)
    {
        $this->notExists[] = $select;

        return $this;
    }

    /**
     * @return array
     */
    public function getNotExists()
    {
        return $this->notExists;
    }

    /**
     * @return array
     */
    public function getMatches()
    {
        return $this->match;
    }

    /**
     * @return array
     */
    public function getIns()
    {
        return $this->ins;
    }

    /**
     * @return array
     */
    public function getNotIns()
    {
        return $this->notIns;
    }

    /**
     * @return array
     */
    public function getBetweens()
    {
        return $this->betweens;
    }

    /**
     * @return array
     */
    public function getNotBetweens()
    {
        return $this->notBetweens;
    }

    /**
     * @return array
     */
    public function getBooleans()
    {
        return $this->booleans;
    }

    /**
     * @return array
     */
    public function getComparisons()
    {
        return $this->comparisons;
    }

    /**
     * @return array
     */
    public function getNotNull()
    {
        return $this->isNotNull;
    }

    /**
     * @return array
     */
    public function getNull()
    {
        return $this->isNull;
    }

    /**
    * @return QueryInterface
    */
    public function end()
    {
       return $this->query;
    }
}


<?php
/**
 * Author: Nil Portugués Calderó <contact@nilportugues.com>
 * Date: 6/11/14
 * Time: 1:51 AM.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace NilPortugues\Sql\QueryBuilder\Builder\Syntax;

use NilPortugues\Sql\QueryBuilder\Manipulation\QueryException;
use NilPortugues\Sql\QueryBuilder\Manipulation\Update;
use NilPortugues\Sql\QueryBuilder\Syntax\SyntaxFactory;

/**
 * Class UpdateWriter.
 */
class UpdateWriter extends AbstractBaseWriter
{
    /**
     * @param Update $update
     *
     * @throws QueryException
     *
     * @return string
     */
    public function write(Update $update)
    {
        $values = $update->getValues();
        if (empty($values)) {
            throw new QueryException('No values to update in Update query.');
        }

        $parts = array(
            'UPDATE '.$this->writer->writeTable($update->getTable()).' SET ',
            $this->writeUpdateValues($update),
        );

        AbstractBaseWriter::writeWhereCondition($update, $this->writer, $this->placeholderWriter, $parts);
        AbstractBaseWriter::writeLimitCondition($update, $this->placeholderWriter, $parts);
        $comment = AbstractBaseWriter::writeQueryComment($update);

        return $comment.implode(' ', $parts);
    }

    /**
     * @param Update $update
     *
     * @return string
     */
    protected function writeUpdateValues(Update $update)
    {
        $assigns = [];
        foreach ($update->getValues() as $column => $value) {
            $newColumn = array($column);
            $column = $this->columnWriter->writeColumn(SyntaxFactory::createColumn($newColumn, $update->getTable()));

            $value = $this->writer->writePlaceholderValue($value);

            $assigns[] = "$column = $value";
        }

        return \implode(', ', $assigns);
    }
}


<?php

namespace NilPortugues\Sql\QueryBuilder\Builder\Syntax;

use NilPortugues\Sql\QueryBuilder\Manipulation\Select;
use NilPortugues\Sql\QueryBuilder\Syntax\Column;
use NilPortugues\Sql\QueryBuilder\Syntax\SyntaxFactory;
use NilPortugues\Sql\QueryBuilder\Syntax\Where;

/**
 * Class WhereWriter.
 */
class WhereWriter extends AbstractBaseWriter
{
    /**
     * @var array
     */
    protected $matchMode = [
        'natural' => '(MATCH({{columnNames}}) AGAINST({{columnValues}}))',
        'boolean' => '(MATCH({{columnNames}}) AGAINST({{columnValues}} IN BOOLEAN MODE))',
        'query_expansion' => '(MATCH({{columnNames}}) AGAINST({{columnValues}} WITH QUERY EXPANSION))',
    ];

    /**
     * @param Where $where
     *
     * @return string
     */
    public function writeWhere(Where $where)
    {
        $clauses = $this->writeWhereClauses($where);
        $clauses = \array_filter($clauses);

        if (empty($clauses)) {
            return '';
        }

        return \implode($this->writer->writeConjunction($where->getConjunction()), $clauses);
    }

    /**
     * @param Where $where
     *
     * @return array
     */
    public function writeWhereClauses(Where $where)
    {
        $whereArray = [];

        $this->writeWhereMatches($where, $whereArray);
        $this->writeWhereIns($where, $whereArray);
        $this->writeWhereNotIns($where, $whereArray);
        $this->writeWhereBetweens($where, $whereArray);
        $this->writeWhereNotBetweens($where, $whereArray);
        $this->writeWhereComparisons($where, $whereArray);
        $this->writeWhereIsNulls($where, $whereArray);
        $this->writeWhereIsNotNulls($where, $whereArray);
        $this->writeWhereBooleans($where, $whereArray);
        $this->writeExists($where, $whereArray);
        $this->writeNotExists($where, $whereArray);
        $this->writeSubWheres($where, $whereArray);

        return $whereArray;
    }

    /**
     * @param Where $where
     * @param array $whereArray
     *
     * @return array
     */
    protected function writeWhereMatches(Where $where, array &$whereArray)
    {
        $matches = [];

        foreach ($where->getMatches() as $values) {
            $columns = SyntaxFactory::createColumns($values['columns'], $where->getTable());
            $columnNames = $this->getColumnNames($columns);

            $columnValues = array(\implode(' ', $values['values']));
            $columnValues = \implode(', ', $this->writer->writeValues($columnValues));

            $matches[] = \str_replace(
                ['{{columnNames}}', '{{columnValues}}'],
                [$columnNames, $columnValues],
                $this->matchMode[$values['mode']]
            );
        }

        $whereArray = \array_merge($whereArray, $matches);
    }

    /**
     * @param $columns
     *
     * @return string
     */
    protected function getColumnNames($columns)
    {
        $columnNames = [];
        foreach ($columns as &$column) {
            $columnNames[] = $this->columnWriter->writeColumn($column);
        }

        return \implode(', ', $columnNames);
    }

    /**
     * @param Where $where
     * @param array $whereArray
     *
     * @return array
     */
    protected function writeWhereIns(Where $where, array &$whereArray)
    {
        $whereArray = \array_merge(
            $whereArray,
            $this->writeWhereIn($where, 'getIns', 'IN')
        );
    }

    /**
     * @param Where  $where
     * @param string $method
     * @param string $operation
     *
     * @return array
     */
    protected function writeWhereIn(Where $where, $method, $operation)
    {
        $collection = [];

        foreach ($where->$method() as $column => $values) {
            $newColumn = array($column);
            $column = SyntaxFactory::createColumn($newColumn, $where->getTable());
            $column = $this->columnWriter->writeColumn($column);

            $values = $this->writer->writeValues($values);
            $values = \implode(', ', $values);

            $collection[] = "({$column} $operation ({$values}))";
        }

        return $collection;
    }

    /**
     * @param Where $where
     * @param array $whereArray
     *
     * @return array
     */
    protected function writeWhereNotIns(Where $where, array &$whereArray)
    {
        $whereArray = \array_merge(
            $whereArray,
            $this->writeWhereIn($where, 'getNotIns', 'NOT IN')
        );
    }

    /**
     * @param Where $where
     * @param array $whereArray
     *
     * @return array
     */
    protected function writeWhereBetweens(Where $where, array &$whereArray)
    {
        $between = $where->getBetweens();
        \array_walk(
            $between,
            function (&$between) {

                $between = '('
                    .$this->columnWriter->writeColumn($between['subject'])
                    .' BETWEEN '
                    .$this->writer->writePlaceholderValue($between['a'])
                    .' AND '
                    .$this->writer->writePlaceholderValue($between['b'])
                    .')';
            }
        );

        $whereArray = \array_merge($whereArray, $between);
    }

    /**
     * @param Where $where
     * @param array $whereArray
     *
     * @return array
     */
    protected function writeWhereNotBetweens(Where $where, array &$whereArray)
    {
        $between = $where->getNotBetweens();
        \array_walk(
            $between,
            function (&$between) {

                $between = '('
                    .$this->columnWriter->writeColumn($between['subject'])
                    .' NOT BETWEEN '
                    .$this->writer->writePlaceholderValue($between['a'])
                    .' AND '
                    .$this->writer->writePlaceholderValue($between['b'])
                    .')';
            }
        );

        $whereArray = \array_merge($whereArray, $between);
    }

    /**
     * @param Where $where
     * @param array $whereArray
     *
     * @return array
     */
    protected function writeWhereComparisons(Where $where, array &$whereArray)
    {
        $comparisons = $where->getComparisons();
        \array_walk(
            $comparisons,
            function (&$comparison) {

                if (!is_array($comparison)) {
                    return;
                }

                $str = $this->writeWherePartialCondition($comparison['subject']);
                $str .= $this->writer->writeConjunction($comparison['conjunction']);
                $str .= $this->writeWherePartialCondition($comparison['target']);

                $comparison = "($str)";
            }
        );

        $whereArray = \array_merge($whereArray, $comparisons);
    }

    /**
     * @param $subject
     *
     * @return string
     */
    protected function writeWherePartialCondition(&$subject)
    {
        if ($subject instanceof Column) {
            $str = $this->columnWriter->writeColumn($subject);
        } elseif ($subject instanceof Select) {
            $selectWriter = WriterFactory::createSelectWriter($this->writer, $this->placeholderWriter);
            $str = '('.$selectWriter->write($subject).')';
        } else {
            $str = $this->writer->writePlaceholderValue($subject);
        }

        return $str;
    }

    /**
     * @param Where $where
     * @param array $whereArray
     *
     * @return array
     */
    protected function writeWhereIsNulls(Where $where, array &$whereArray)
    {
        $whereArray = \array_merge(
            $whereArray,
            $this->writeWhereIsNullable($where, 'getNull', 'writeIsNull')
        );
    }

    /**
     * @param Where  $where
     * @param string $getMethod
     * @param string $writeMethod
     *
     * @return array
     */
    protected function writeWhereIsNullable(Where $where, $getMethod, $writeMethod)
    {
        $collection = $where->$getMethod();

        \array_walk(
            $collection,
            function (&$collection) use ($writeMethod) {
                $collection =
                    '('.$this->columnWriter->writeColumn($collection['subject'])
                    .$this->writer->$writeMethod().')';
            }
        );

        return $collection;
    }

    /**
     * @param Where $where
     * @param array $whereArray
     *
     * @return array
     */
    protected function writeWhereIsNotNulls(Where $where, array &$whereArray)
    {
        $whereArray = \array_merge(
            $whereArray,
            $this->writeWhereIsNullable($where, 'getNotNull', 'writeIsNotNull')
        );
    }

    /**
     * @param Where $where
     * @param array $whereArray
     *
     * @return array
     */
    protected function writeWhereBooleans(Where $where, array &$whereArray)
    {
        $booleans = $where->getBooleans();
        $placeholderWriter = $this->placeholderWriter;

        \array_walk(
            $booleans,
            function (&$boolean) use (&$placeholderWriter) {
                $column = $this->columnWriter->writeColumn($boolean['subject']);
                $value = $this->placeholderWriter->add($boolean['value']);

                $boolean = '(ISNULL('.$column.', 0) = '.$value.')';
            }
        );

        $whereArray = \array_merge($whereArray, $booleans);
    }

    /**
     * @param Where $where
     * @param array $whereArray
     *
     * @return array
     */
    protected function writeExists(Where $where, array &$whereArray)
    {
        $whereArray = \array_merge(
            $whereArray,
            $this->writeExistence($where, 'getExists', 'EXISTS')
        );
    }

    /**
     * @param Where  $where
     * @param string $method
     * @param string $operation
     *
     * @return array
     */
    protected function writeExistence(Where $where, $method, $operation)
    {
        $exists = [];

        foreach ($where->$method() as $select) {
            $exists[] = "$operation (".$this->writer->write($select, false).')';
        }

        return $exists;
    }

    /**
     * @param Where $where
     * @param array $whereArray
     *
     * @return array
     */
    protected function writeNotExists(Where $where, array &$whereArray)
    {
        $whereArray = \array_merge(
            $whereArray,
            $this->writeExistence($where, 'getNotExists', 'NOT EXISTS')
        );
    }

    /**
     * @param Where $where
     * @param array $whereArray
     *
     * @return array
     */
    protected function writeSubWheres(Where $where, array &$whereArray)
    {
        $subWheres = $where->getSubWheres();

        \array_walk(
            $subWheres,
            function (&$subWhere) {
                $subWhere = "({$this->writeWhere($subWhere)})";
            }
        );

        $whereArray = \array_merge($whereArray, $subWheres);
    }
}


<?php
/**
 * Author: Nil Portugués Calderó <contact@nilportugues.com>
 * Date: 6/3/14
 * Time: 12:07 AM.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace NilPortugues\Sql\QueryBuilder\Manipulation;

use NilPortugues\Sql\QueryBuilder\Syntax\SyntaxFactory;
use NilPortugues\Sql\QueryBuilder\Syntax\Table;
use NilPortugues\Sql\QueryBuilder\Syntax\Where;
use NilPortugues\Sql\QueryBuilder\Syntax\OrderBy;

/**
 * Class Select.
 */
class Select extends AbstractBaseQuery
{
    /**
     * @var Table
     */
    protected $table;

    /**
     * @var array
     */
    protected $groupBy = [];

    /**
     * @var string
     */
    protected $camelCaseTableName = '';

    /**
     * @var Where
     */
    protected $having;

    /**
     * @var string
     */
    protected $havingOperator = 'AND';

    /**
     * @var bool
     */
    protected $isDistinct = false;

    /**
     * @var Where
     */
    protected $where;

    /**
     * @var JoinQuery
     */
    protected $joinQuery;

    /**
     * @var ColumnQuery
     */
    protected $columnQuery;

    /**
     * @var ParentQuery
     */
    protected $parentQuery;

    /**
     * @param string $table
     * @param array  $columns
     */
    public function __construct($table = null, array $columns = null)
    {
        if (isset($table)) {
            $this->setTable($table);
        }

        $this->joinQuery = new JoinQuery($this);
        $this->columnQuery = new ColumnQuery($this, $this->joinQuery, $columns);
    }

    /**
     * This __clone method will create an exact clone but without the object references due to the fact these
     * are lost in the process of serialization and un-serialization.
     *
     * @return Select
     */
    public function __clone()
    {
        return \unserialize(\serialize($this));
    }

    /**
     * @return string
     */
    public function partName()
    {
        return 'SELECT';
    }

    /**
     * @param string   $table
     * @param string   $selfColumn
     * @param string   $refColumn
     * @param string[] $columns
     *
     * @return Select
     */
    public function leftJoin($table, $selfColumn = null, $refColumn = null, $columns = [])
    {
        return $this->joinQuery->leftJoin($table, $selfColumn, $refColumn, $columns);
    }

    /**
     * @param string   $table
     * @param string   $selfColumn
     * @param string   $refColumn
     * @param string[] $columns
     * @param string   $joinType
     *
     * @return Select
     */
    public function join(
        $table,
        $selfColumn = null,
        $refColumn = null,
        $columns = [],
        $joinType = null
    ) {
        return $this->joinQuery->join($table, $selfColumn, $refColumn, $columns, $joinType);
    }

    /**
     * WHERE constrains used for the ON clause of a (LEFT/RIGHT/INNER/CROSS) JOIN.
     *
     * @return Where
     */
    public function joinCondition()
    {
        return $this->joinQuery->joinCondition();
    }

    /**
     * @param Select $select
     * @param string $selfColumn
     * @param string $refColumn
     *
     * @return Select
     */
    public function addJoin(Select $select, $selfColumn, $refColumn)
    {
        return $this->joinQuery->addJoin($select, $selfColumn, $refColumn);
    }

    /**
     * Transforms Select in a joint.
     *
     * @param bool $isJoin
     *
     * @return JoinQuery
     */
    public function isJoin($isJoin = true)
    {
        return $this->joinQuery->setJoin($isJoin);
    }

    /**
     * @param string   $table
     * @param string   $selfColumn
     * @param string   $refColumn
     * @param string[] $columns
     *
     * @internal param null $selectClass
     *
     * @return Select
     */
    public function rightJoin($table, $selfColumn = null, $refColumn = null, $columns = [])
    {
        return $this->joinQuery->rightJoin($table, $selfColumn, $refColumn, $columns);
    }

    /**
     * @param string   $table
     * @param string   $selfColumn
     * @param string   $refColumn
     * @param string[] $columns
     *
     * @return Select
     */
    public function crossJoin($table, $selfColumn = null, $refColumn = null, $columns = [])
    {
        return $this->joinQuery->crossJoin($table, $selfColumn, $refColumn, $columns);
    }

    /**
     * @param string   $table
     * @param string   $selfColumn
     * @param string   $refColumn
     * @param string[] $columns
     *
     * @return Select
     */
    public function innerJoin($table, $selfColumn = null, $refColumn = null, $columns = [])
    {
        return $this->joinQuery->innerJoin($table, $selfColumn, $refColumn, $columns);
    }

    /**
     * Alias to joinCondition.
     *
     * @return Where
     */
    public function on()
    {
        return $this->joinQuery->joinCondition();
    }

    /**
     * @return bool
     */
    public function isJoinSelect()
    {
        return $this->joinQuery->isJoin();
    }

    /**
     * @return array
     */
    public function getAllColumns()
    {
        return $this->columnQuery->getAllColumns();
    }

    /**
     * @return \NilPortugues\Sql\QueryBuilder\Syntax\Column
     *
     * @throws QueryException
     */
    public function getColumns()
    {
        return $this->columnQuery->getColumns();
    }

    /**
     * Sets the column names used to write the SELECT statement.
     * If key is set, key is the column's alias. Value is always the column names.
     *
     * @param string[] $columns
     *
     * @return ColumnQuery
     */
    public function setColumns(array $columns)
    {
        return $this->columnQuery->setColumns($columns);
    }

    /**
     * Allows setting a Select query as a column value.
     *
     * @param array $column
     *
     * @return ColumnQuery
     */
    public function setSelectAsColumn(array $column)
    {
        return $this->columnQuery->setSelectAsColumn($column);
    }

    /**
     * @return array
     */
    public function getColumnSelects()
    {
        return $this->columnQuery->getColumnSelects();
    }

    /**
     * Allows setting a value to the select statement.
     *
     * @param string $value
     * @param string $alias
     *
     * @return ColumnQuery
     */
    public function setValueAsColumn($value, $alias)
    {
        return $this->columnQuery->setValueAsColumn($value, $alias);
    }

    /**
     * @return array
     */
    public function getColumnValues()
    {
        return $this->columnQuery->getColumnValues();
    }

    /**
     * Allows calculation on columns using predefined SQL functions.
     *
     * @param string   $funcName
     * @param string[] $arguments
     * @param string   $alias
     *
     * @return ColumnQuery
     */
    public function setFunctionAsColumn($funcName, array $arguments, $alias)
    {
        return $this->columnQuery->setFunctionAsColumn($funcName, $arguments, $alias);
    }

    /**
     * @return array
     */
    public function getColumnFuncs()
    {
        return $this->columnQuery->getColumnFuncs();
    }

    /**
     * Returns all the Where conditions to the BuilderInterface class in order to write the SQL WHERE statement.
     *
     * @return array
     */
    public function getAllWheres()
    {
        return $this->getAllOperation($this->where, 'getAllWheres');
    }

    /**
     * @param null|Where $data
     * @param string     $operation
     *
     * @return array
     */
    protected function getAllOperation($data, $operation)
    {
        $collection = [];

        if (!is_null($data)) {
            $collection[] = $data;
        }

        foreach ($this->joinQuery->getJoins() as $join) {
            $collection = \array_merge($collection, $join->$operation());
        }

        return $collection;
    }

    /**
     * @return array
     */
    public function getAllHavings()
    {
        return $this->getAllOperation($this->having, 'getAllHavings');
    }

    /**
     * @param string $columnName
     * @param string $alias
     *
     * @return ColumnQuery
     */
    public function count($columnName = '*', $alias = '')
    {
        return $this->columnQuery->count($columnName, $alias);
    }

    /**
     * @return bool
     */
    public function isCount()
    {
        return $this->columnQuery->isCount();
    }

    /**
     * @param int $start
     * @param     $count
     *
     * @return $this
     */
    public function limit($start, $count = 0)
    {
        $this->limitStart = $start;
        $this->limitCount = $count;

        return $this;
    }

    /**
     * @return array
     */
    public function getAllJoins()
    {
        return $this->joinQuery->getAllJoins();
    }

    /**
     * @return array
     */
    public function getGroupBy()
    {
        return SyntaxFactory::createColumns($this->groupBy, $this->getTable());
    }

    /**
     * @param string[] $columns
     *
     * @return $this
     */
    public function groupBy(array $columns)
    {
        $this->groupBy = $columns;

        return $this;
    }

    /**
     * @return Where
     */
    public function getJoinCondition()
    {
        return $this->joinQuery->getJoinCondition();
    }

    /**
     * @return string
     */
    public function getJoinType()
    {
        return $this->joinQuery->getJoinType();
    }

    /**
     * @param string|null $joinType
     *
     * @return $this
     */
    public function setJoinType($joinType)
    {
        $this->joinQuery->setJoinType($joinType);

        return $this;
    }

    /**
     * @param $havingOperator
     *
     * @throws QueryException
     *
     * @return Where
     */
    public function having($havingOperator = 'AND')
    {
        if (!isset($this->having)) {
            $this->having = QueryFactory::createWhere($this);
        }

        if (!in_array($havingOperator, array(Where::CONJUNCTION_AND, Where::CONJUNCTION_OR))) {
            throw new QueryException(
                "Invalid conjunction specified, must be one of AND or OR, but '".$havingOperator."' was found."
            );
        }

        $this->havingOperator = $havingOperator;

        return $this->having;
    }

    /**
     * @return string
     */
    public function getHavingOperator()
    {
        return $this->havingOperator;
    }

    /**
     * @return $this
     */
    public function distinct()
    {
        $this->isDistinct = true;

        return $this;
    }

    /**
     * @return bool
     */
    public function isDistinct()
    {
        return $this->isDistinct;
    }

    /**
     * @return array
     */
    public function getAllOrderBy()
    {
        return $this->orderBy;
    }

    /**
     * @return ParentQuery
     */
    public function getParentQuery()
    {
        return $this->parentQuery;
    }

    /**
     * @param Select $parentQuery
     *
     * @return $this
     */
    public function setParentQuery(Select $parentQuery)
    {
        $this->parentQuery = $parentQuery;

        return $this;
    }

    /**
     * @param string $column
     * @param string $direction
     * @param null   $table
     *
     * @return $this
     */
    public function orderBy($column, $direction = OrderBy::ASC, $table = null)
    {
        $current = parent::orderBy($column, $direction, $table);
        if ($this->getParentQuery() != null) {
            $this->getParentQuery()->orderBy($column, $direction, \is_null($table) ? $this->getTable() : $table);
        }
        return $current;
    }
}


<?php
/**
 * Author: Nil Portugués Calderó <contact@nilportugues.com>
 * Date: 6/12/14
 * Time: 1:28 AM.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace NilPortugues\Sql\QueryBuilder\Builder\Syntax;

use NilPortugues\Sql\QueryBuilder\Builder\GenericBuilder;
use NilPortugues\Sql\QueryBuilder\Manipulation\Select;
use NilPortugues\Sql\QueryBuilder\Syntax\Column;
use NilPortugues\Sql\QueryBuilder\Syntax\SyntaxFactory;

/**
 * Class ColumnWriter.
 */
class ColumnWriter
{
    /**
     * @var \NilPortugues\Sql\QueryBuilder\Builder\GenericBuilder
     */
    protected $writer;

    /**
     * @var PlaceholderWriter
     */
    protected $placeholderWriter;

    /**
     * @param GenericBuilder    $writer
     * @param PlaceholderWriter $placeholderWriter
     */
    public function __construct(GenericBuilder $writer, PlaceholderWriter $placeholderWriter)
    {
        $this->writer = $writer;
        $this->placeholderWriter = $placeholderWriter;
    }

    /**
     * @param Select $select
     *
     * @return array
     */
    public function writeSelectsAsColumns(Select $select)
    {
        $selectAsColumns = $select->getColumnSelects();

        if (!empty($selectAsColumns)) {
            $selectWriter = WriterFactory::createSelectWriter($this->writer, $this->placeholderWriter);
            $selectAsColumns = $this->selectColumnToQuery($selectAsColumns, $selectWriter);
        }

        return $selectAsColumns;
    }

    /**
     * @param array        $selectAsColumns
     * @param SelectWriter $selectWriter
     *
     * @return mixed
     */
    protected function selectColumnToQuery(array &$selectAsColumns, SelectWriter $selectWriter)
    {
        \array_walk(
            $selectAsColumns,
            function (&$column) use (&$selectWriter) {
                $keys = \array_keys($column);
                $key = \array_pop($keys);

                $values = \array_values($column);
                $value = $values[0];

                if (\is_numeric($key)) {
                    /* @var Column $value */
                    $key = $this->writer->writeTableName($value->getTable());
                }
                $column = $selectWriter->selectToColumn($key, $value);
            }
        );

        return $selectAsColumns;
    }

    /**
     * @param Select $select
     *
     * @return array
     */
    public function writeValueAsColumns(Select $select)
    {
        $valueAsColumns = $select->getColumnValues();
        $newColumns = [];

        if (!empty($valueAsColumns)) {
            foreach ($valueAsColumns as $alias => $value) {
                $value = $this->writer->writePlaceholderValue($value);
                $newValueColumn = array($alias => $value);

                $newColumns[] = SyntaxFactory::createColumn($newValueColumn, null);
            }
        }

        return $newColumns;
    }

    /**
     * @param Select $select
     *
     * @return array
     */
    public function writeFuncAsColumns(Select $select)
    {
        $funcAsColumns = $select->getColumnFuncs();
        $newColumns = [];

        if (!empty($funcAsColumns)) {
            foreach ($funcAsColumns as $alias => $value) {
                $funcName = $value['func'];
                $funcArgs = (!empty($value['args'])) ? '('.implode(', ', $value['args']).')' : '';

                $newFuncColumn = array($alias => $funcName.$funcArgs);
                $newColumns[] = SyntaxFactory::createColumn($newFuncColumn, null);
            }
        }

        return $newColumns;
    }

    /**
     * @param Column $column
     *
     * @return string
     */
    public function writeColumnWithAlias(Column $column)
    {
        if (($alias = $column->getAlias()) && !$column->isAll()) {
            return $this->writeColumn($column).' AS '.$this->writer->writeColumnAlias($alias);
        }

        return $this->writeColumn($column);
    }

    /**
     * @param Column $column
     *
     * @return string
     */
    public function writeColumn(Column $column)
    {
        $alias = $column->getTable()->getAlias();
        $table = ($alias) ? $this->writer->writeTableAlias($alias) : $this->writer->writeTable($column->getTable());

        $columnString = (empty($table)) ? '' : "{$table}.";
        $columnString .= $this->writer->writeColumnName($column);

        return $columnString;
    }
}


<?php
/**
 * Author: Nil Portugués Calderó <contact@nilportugues.com>
 * Date: 6/11/14
 * Time: 1:50 AM.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace NilPortugues\Sql\QueryBuilder\Builder\Syntax;

use NilPortugues\Sql\QueryBuilder\Builder\GenericBuilder;
use NilPortugues\Sql\QueryBuilder\Manipulation\Delete;

/**
 * Class DeleteWriter.
 */
class DeleteWriter
{
    /**
     * @var GenericBuilder
     */
    protected $writer;

    /**
     * @var PlaceholderWriter
     */
    protected $placeholderWriter;

    /**
     * @param GenericBuilder    $writer
     * @param PlaceholderWriter $placeholder
     */
    public function __construct(GenericBuilder $writer, PlaceholderWriter $placeholder)
    {
        $this->writer = $writer;
        $this->placeholderWriter = $placeholder;
    }

    /**
     * @param Delete $delete
     *
     * @return string
     */
    public function write(Delete $delete)
    {
        $table = $this->writer->writeTable($delete->getTable());
        $parts = array("DELETE FROM {$table}");

        AbstractBaseWriter::writeWhereCondition($delete, $this->writer, $this->placeholderWriter, $parts);
        AbstractBaseWriter::writeLimitCondition($delete, $this->placeholderWriter, $parts);
        $comment = AbstractBaseWriter::writeQueryComment($delete);

        return $comment.implode(' ', $parts);
    }
}


<?php
/**
 * Author: Nil Portugués Calderó <contact@nilportugues.com>
 * Date: 6/3/14
 * Time: 12:07 AM.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace NilPortugues\Sql\QueryBuilder\Builder;

use NilPortugues\Sql\QueryBuilder\Builder\Syntax\WriterFactory;
use NilPortugues\Sql\QueryBuilder\Manipulation\AbstractBaseQuery;
use NilPortugues\Sql\QueryBuilder\Manipulation\QueryInterface;
use NilPortugues\Sql\QueryBuilder\Manipulation\QueryFactory;
use NilPortugues\Sql\QueryBuilder\Manipulation\Select;
use NilPortugues\Sql\QueryBuilder\Syntax\Column;
use NilPortugues\Sql\QueryBuilder\Syntax\Table;

/**
 * Class Generic.
 */
class GenericBuilder implements BuilderInterface
{
    /**
     * The placeholder parameter bag.
     *
     * @var \NilPortugues\Sql\QueryBuilder\Builder\Syntax\PlaceholderWriter
     */
    protected $placeholderWriter;

    /**
     * The Where writer.
     *
     * @var \NilPortugues\Sql\QueryBuilder\Builder\Syntax\WhereWriter
     */
    protected $whereWriter;

    /**
     * The SQL formatter.
     *
     * @var \NilPortugues\Sql\QueryFormatter\Formatter
     */
    protected $sqlFormatter;

    /**
     * Class namespace for the query pretty output formatter.
     * Required to create the instance only if required.
     *
     * @var string
     */
    protected $sqlFormatterClass = 'NilPortugues\Sql\QueryFormatter\Formatter';

    /**
     * Array holding the writers for each query part. Methods are called upon request and stored in
     * the $queryWriterInstances array.
     *
     * @var array
     */
    protected $queryWriterArray = [
        'SELECT' => '\NilPortugues\Sql\QueryBuilder\Builder\Syntax\WriterFactory::createSelectWriter',
        'INSERT' => '\NilPortugues\Sql\QueryBuilder\Builder\Syntax\WriterFactory::createInsertWriter',
        'UPDATE' => '\NilPortugues\Sql\QueryBuilder\Builder\Syntax\WriterFactory::createUpdateWriter',
        'DELETE' => '\NilPortugues\Sql\QueryBuilder\Builder\Syntax\WriterFactory::createDeleteWriter',
        'INTERSECT' => '\NilPortugues\Sql\QueryBuilder\Builder\Syntax\WriterFactory::createIntersectWriter',
        'MINUS' => '\NilPortugues\Sql\QueryBuilder\Builder\Syntax\WriterFactory::createMinusWriter',
        'UNION' => '\NilPortugues\Sql\QueryBuilder\Builder\Syntax\WriterFactory::createUnionWriter',
        'UNION ALL' => '\NilPortugues\Sql\QueryBuilder\Builder\Syntax\WriterFactory::createUnionAllWriter',
    ];

    /**
     * Array that stores instances of query writers.
     *
     * @var array
     */
    protected $queryWriterInstances = [
        'SELECT' => null,
        'INSERT' => null,
        'UPDATE' => null,
        'DELETE' => null,
        'INTERSECT' => null,
        'MINUS' => null,
        'UNION' => null,
        'UNION ALL' => null,
    ];

    /**
     * Creates writers.
     */
    public function __construct()
    {
        $this->placeholderWriter = WriterFactory::createPlaceholderWriter();
    }

    /**
     * @param string $table
     * @param array  $columns
     *
     * @return \NilPortugues\Sql\QueryBuilder\Manipulation\Select
     */
    public function select($table = null, array $columns = null)
    {
        return $this->injectBuilder(QueryFactory::createSelect($table, $columns));
    }

    /**
     * @param \NilPortugues\Sql\QueryBuilder\Manipulation\AbstractBaseQuery
     *
     * @return \NilPortugues\Sql\QueryBuilder\Manipulation\AbstractBaseQuery
     */
    protected function injectBuilder(AbstractBaseQuery $query)
    {
        return $query->setBuilder($this);
    }

    /**
     * @param string $table
     * @param array  $values
     *
     *@return AbstractBaseQuery
     */
    public function insert($table = null, array $values = null)
    {
        return $this->injectBuilder(QueryFactory::createInsert($table, $values));
    }

    /**
     * @param string $table
     * @param array  $values
     *
     *@return AbstractBaseQuery
     */
    public function update($table = null, array $values = null)
    {
        return $this->injectBuilder(QueryFactory::createUpdate($table, $values));
    }

    /**
     * @param string $table
     *
     * @return \NilPortugues\Sql\QueryBuilder\Manipulation\Delete
     */
    public function delete($table = null)
    {
        return $this->injectBuilder(QueryFactory::createDelete($table));
    }

    /**
     * @return \NilPortugues\Sql\QueryBuilder\Manipulation\Intersect
     */
    public function intersect()
    {
        return QueryFactory::createIntersect();
    }

    /**
     * @return \NilPortugues\Sql\QueryBuilder\Manipulation\Union
     */
    public function union()
    {
        return QueryFactory::createUnion();
    }

    /**
     * @return \NilPortugues\Sql\QueryBuilder\Manipulation\UnionAll
     */
    public function unionAll()
    {
        return QueryFactory::createUnionAll();
    }

    /**
     * @param \NilPortugues\Sql\QueryBuilder\Manipulation\Select $first
     * @param \NilPortugues\Sql\QueryBuilder\Manipulation\Select $second
     *
     * @return \NilPortugues\Sql\QueryBuilder\Manipulation\Minus
     */
    public function minus(Select $first, Select $second)
    {
        return QueryFactory::createMinus($first, $second);
    }

    /**
     * @return array
     */
    public function getValues()
    {
        return $this->placeholderWriter->get();
    }

    /**
     * Returns a SQL string in a readable human-friendly format.
     *
     * @param QueryInterface $query
     *
     * @return string
     */
    public function writeFormatted(QueryInterface $query)
    {
        if (null === $this->sqlFormatter) {
            $this->sqlFormatter = (new \ReflectionClass($this->sqlFormatterClass))->newInstance();
        }

        return $this->sqlFormatter->format($this->write($query));
    }

    /**
     * @param QueryInterface $query
     * @param bool           $resetPlaceholders
     *
     * @return string
     *
     * @throws \RuntimeException
     */
    public function write(QueryInterface $query, $resetPlaceholders = true)
    {
        if ($resetPlaceholders) {
            $this->placeholderWriter->reset();
        }

        $queryPart = $query->partName();

        if (false === empty($this->queryWriterArray[$queryPart])) {
            $this->createQueryObject($queryPart);

            return $this->queryWriterInstances[$queryPart]->write($query);
        }

        throw new \RuntimeException('Query builder part not defined.');
    }

    /**
     * @param Select $select
     *
     * @return string
     */
    public function writeJoin(Select $select)
    {
        if (null === $this->whereWriter) {
            $this->whereWriter = WriterFactory::createWhereWriter($this, $this->placeholderWriter);
        }

        $sql = ($select->getJoinType()) ? "{$select->getJoinType()} " : '';
        $sql .= 'JOIN ';
        $sql .= $this->writeTableWithAlias($select->getTable());
        $sql .= ' ON ';
        $sql .= $this->whereWriter->writeWhere($select->getJoinCondition());

        return $sql;
    }

    /**
     * @param Table $table
     *
     * @return string
     */
    public function writeTableWithAlias(Table $table)
    {
        $alias = ($table->getAlias()) ? " AS {$this->writeTableAlias($table->getAlias())}" : '';
        $schema = ($table->getSchema()) ? "{$table->getSchema()}." : '';

        return $schema.$this->writeTableName($table).$alias;
    }

    /**
     * @param $alias
     *
     * @return mixed
     */
    public function writeTableAlias($alias)
    {
        return $alias;
    }

    /**
     * Returns the table name.
     *
     * @param Table $table
     *
     * @return string
     */
    public function writeTableName(Table $table)
    {
        return $table->getName();
    }

    /**
     * @param string $alias
     *
     * @return string
     */
    public function writeColumnAlias($alias)
    {
        return sprintf('"%s"', $alias);
    }

    /**
     * @param Table $table
     *
     * @return string
     */
    public function writeTable(Table $table)
    {
        $schema = ($table->getSchema()) ? "{$table->getSchema()}." : '';

        return $schema.$this->writeTableName($table);
    }

    /**
     * @param array $values
     *
     * @return array
     */
    public function writeValues(array &$values)
    {
        \array_walk(
            $values,
            function (&$value) {
                $value = $this->writePlaceholderValue($value);
            }
        );

        return $values;
    }

    /**
     * @param $value
     *
     * @return string
     */
    public function writePlaceholderValue($value)
    {
        return $this->placeholderWriter->add($value);
    }

    /**
     * @param $operator
     *
     * @return string
     */
    public function writeConjunction($operator)
    {
        return ' '.$operator.' ';
    }

    /**
     * @return string
     */
    public function writeIsNull()
    {
        return ' IS NULL';
    }

    /**
     * @return string
     */
    public function writeIsNotNull()
    {
        return ' IS NOT NULL';
    }

    /**
     * Returns the column name.
     *
     * @param Column $column
     *
     * @return string
     */
    public function writeColumnName(Column $column)
    {
        $name = $column->getName();

        if ($name === Column::ALL) {
            return $this->writeColumnAll();
        }

        return $name;
    }

    /**
     * @return string
     */
    protected function writeColumnAll()
    {
        return '*';
    }

    /**
     * @param string $queryPart
     */
    protected function createQueryObject($queryPart)
    {
        if (null === $this->queryWriterInstances[$queryPart]) {
            $this->queryWriterInstances[$queryPart] = \call_user_func_array(
                \explode('::', $this->queryWriterArray[$queryPart]),
                [$this, $this->placeholderWriter]
            );
        }
    }
}