<?php
namespace ShSo\Lacassa\Query;
use Illuminate\Database\Query\Builder as BaseBuilder;
use Illuminate\Database\Query\Grammars\Grammar as BaseGrammar;
class Grammar extends BaseGrammar
{
protected $selectComponents = [
'columns',
'from',
'wheres',
'limit',
'allowFiltering',
];
/**
* Compile an insert statement into CQL.
*
* @param \ShSo\Lacassa\Query $query
* @param array $values
*
* @return string
*/
public function compileInsert(BaseBuilder $query, array $values)
{
// Essentially we will force every insert to be treated as a batch insert which
// simply makes creating the CQL easier for us since we can utilize the same
// basic routine regardless of an amount of records given to us to insert.
$table = $this->wrapTable($query->from);
$columns = $this->columnize(array_keys(reset($values)));
// We need to build a list of parameter place-holders of values that are bound
// to the query. Each insert should have the exact same amount of parameter
// bindings so we will loop through the record and parameterize them all.
$parameters = collect($values)->map(function ($record) {
return $this->parameterize($record);
})->implode(', ');
return "insert into {$table} ({$columns}) values ({$parameters})";
}
/**
* @param \Illuminate\Support\Collection $collection
*
* @return \Illuminate\Support\Collection
*/
public function buildInsertCollectionParam($collection)
{
return $collection->map(function ($collectionItem) {
return $this->compileCollectionValues($collectionItem['type'], $collectionItem['value']);
})->implode(', ');
}
/**
* Wrap a single string in keyword identifiers.
*
* @param string $value
*
* @return string
*/
protected function wrapValue($value)
{
if ($value !== '*') {
return str_replace('"', '""', $value);
}
return $value;
}
/**
* Compile a delete statement into CQL.
*
* @param \ShSo\Lacassa\Query $query
*
* @return string
*/
public function compileDelete(BaseBuilder $query)
{
$delColumns = '';
if (isset($query->delParams)) {
$delColumns = implode(', ', $query->delParams);
}
$wheres = is_array($query->wheres) ? $this->compileWheres($query) : '';
return trim('delete '.$delColumns." from {$this->wrapTable($query->from)} $wheres");
}
/**
* Compile an update statement into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $values
*
* @return string
*/
public function compileUpdate(BaseBuilder $query, $values)
{
$table = $this->wrapTable($query->from);
// Each one of the columns in the update statements needs to be wrapped in the
// keyword identifiers, also a place-holder needs to be created for each of
// the values in the list of bindings so we can make the sets statements.
$columns = collect($values)->map(
function ($value, $key) {
return $this->wrap($key).' = '.$this->parameter($value);
}
)->implode(', ');
// Of course, update queries may also be constrained by where clauses so we'll
// need to compile the where clauses and attach it to the query so only the
// intended records are updated by the SQL statements we generate to run.
$wheres = $this->compileWheres($query);
$upateCollections = $this->compileUpdateCollections($query);
if ($upateCollections) {
$upateCollections = $columns ? ', '.$upateCollections : $upateCollections;
}
return trim("update {$table} set $columns $upateCollections $wheres");
}
/**
* Compiles the udpate collection methods.
*
* @param BaseBuilder $query
*
* @return string
*/
public function compileUpdateCollections(BaseBuilder $query)
{
$updateCollections = collect($query->bindings['updateCollection']);
$updateCollectionCql = $updateCollections->map(function ($collection, $key) {
if ($collection['operation']) {
return $collection['column'].'='.$collection['column'].$collection['operation'].$this->compileCollectionValues($collection['type'], $collection['value']);
} else {
return $collection['column'].'='.$this->compileCollectionValues($collection['type'], $collection['value']);
}
})->implode(', ');
return $updateCollectionCql;
}
/**
* Compiles the values assigned to collections.
*
* @param string $type
* @param string $value
*
* @return string
*/
public function compileCollectionValues($type, $value)
{
if (is_array($value)) {
if ('set' == $type) {
$collection = '{'.$this->buildCollectionString($type, $value).'}';
} elseif ('list' == $type) {
$collection = '['.$this->buildCollectionString($type, $value).']';
} elseif ('map' == $type) {
$collection = '{'.$this->buildCollectionString($type, $value).'}';
}
return $collection;
}
}
/**
* Builds the insert string.
*
* @param string $type
* @param string $value
*
* @return string
*/
public function buildCollectionString($type, $value)
{
$items = [];
if ($type === 'map') {
foreach ($value as $item) {
list($key, $value, $qoutk, $qoutv) = [$item[0], $item[1], $item['key'] ?? null, $item['value'] ?? null];
if (!is_bool($qoutk)) {
$qoutk = 'string' == strtolower(gettype($key));
}
if (!is_bool($qoutv)) {
$qoutv = 'string' == strtolower(gettype($value));
}
$key = $qoutk ? "'{$key}'" : $key;
$value = $qoutv ? "'{$value}'" : $value;
$items[] = "{$key}:{$value}";
}
} elseif ($type === 'set' || $type === 'list') {
foreach ($value as $item) {
$qoutv = 'string' == strtolower(gettype($item));
$items[] = $qoutv ? "'{$item}'" : $item;
}
}
return implode(',', $items);
}
/**
* @param Builder $query
* @param string $columns
*
* @return string
*/
public function compileIndex($query, $columns)
{
$table = $this->wrapTable($query->from);
$value = implode(', ', $columns);
return 'CREATE INDEX IF NOT EXISTS ON '.$table.'('.$value.')';
}
public function compileAllowFiltering($query, $allow_filtering)
{
return $allow_filtering ? 'allow filtering' : '';
}
}