Skip to content
Merged
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
### 12.1.0 <small>(2026-??-??)</small>

#### New

* Added vector query support to the query builder for the following databases:
- `MariaDB`
- `MySQL`
- `Postgres`
* Now possible to define custom input/output value objects for the query builder.

--------------------------------------------------------


### 11.4.6, 12.0.2 <small>(2026-01-08)</small>

Expand Down
6 changes: 3 additions & 3 deletions src/mako/Mako.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ final class Mako
/**
* Mako version.
*/
public const string VERSION = '12.0.2';
public const string VERSION = '12.1.0';

/**
* Mako major version.
Expand All @@ -25,10 +25,10 @@ final class Mako
/**
* Mako minor version.
*/
public const int VERSION_MINOR = 0;
public const int VERSION_MINOR = 1;

/**
* Mako patch version.
*/
public const int VERSION_PATCH = 2;
public const int VERSION_PATCH = 0;
}
7 changes: 7 additions & 0 deletions src/mako/application/cli/commands/app/preloader/core.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@
mako\database\query\Result::class,
mako\database\query\ResultSet::class,
mako\database\query\Subquery::class,
mako\database\query\values\in\Vector::class,
mako\database\query\values\out\Value::class,
mako\database\query\values\out\ValueWithAliasInterface::class,
mako\database\query\values\out\Vector::class,
mako\database\query\values\out\VectorDistance::class,
mako\database\query\values\ValueInterface::class,
mako\database\query\VectorDistance::class,
mako\error\ErrorHandler::class,
mako\file\FileSystem::class,
mako\file\Permission::class,
Expand Down
82 changes: 72 additions & 10 deletions src/mako/database/query/Query.php
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,16 @@ public function where(array|Closure|Raw|string $column, ?string $operator = null
return $this;
}

/**
* Adds a OR WHERE clause.
*
* @return $this
*/
public function orWhere(array|Closure|Raw|string $column, ?string $operator = null, mixed $value = null): static
{
return $this->where($column, $operator, $value, 'OR');
}

/**
* Adds a raw WHERE clause.
*
Expand All @@ -565,16 +575,6 @@ public function whereRaw(array|Raw|string $column, null|array|string $operator =
return $this->where($column, $operator, new Raw($raw), $separator);
}

/**
* Adds a OR WHERE clause.
*
* @return $this
*/
public function orWhere(array|Closure|Raw|string $column, ?string $operator = null, mixed $value = null): static
{
return $this->where($column, $operator, $value, 'OR');
}

/**
* Adds a raw OR WHERE clause.
*
Expand Down Expand Up @@ -642,6 +642,35 @@ public function orWhereColumn(array|string $column1, string $operator, array|str
return $this->whereColumn($column1, $operator, $column2, 'OR');
}

/**
* Adds a vector distance clause.
*
* @return $this
*/
public function whereVectorDistance(string $column, array|string|Subquery $vector, float $maxDistance = 0.2, VectorDistance $vectorDistance = VectorDistance::COSINE, string $separator = 'AND'): static
{
$this->wheres[] = [
'type' => 'whereVectorDistance',
'column' => $column,
'vector' => $vector,
'maxDistance' => $maxDistance,
'vectorDistance' => $vectorDistance,
'separator' => $separator,
];

return $this;
}

/**
* Adds a vector distance clause.
*
* @return $this
*/
public function orWhereVectorDistance(string $column, array|string|Subquery $vector, float $maxDistance = 0.2, VectorDistance $vectorDistance = VectorDistance::COSINE): static
{
return $this->whereVectorDistance($column, $vector, $maxDistance, $vectorDistance, 'OR');
}

/**
* Adds a BETWEEN clause.
*
Expand Down Expand Up @@ -1040,6 +1069,7 @@ public function orHavingRaw(string $raw, string $operator, mixed $value): static
public function orderBy(array|Raw|string $columns, string $order = 'ASC'): static
{
$this->orderings[] = [
'type' => 'basicOrdering',
'column' => is_array($columns) ? $columns : [$columns],
'order' => ($order === 'ASC' || $order === 'asc') ? 'ASC' : 'DESC',
];
Expand Down Expand Up @@ -1097,6 +1127,38 @@ public function descendingRaw(string $raw, array $parameters = []): static
return $this->orderByRaw($raw, $parameters, 'DESC');
}

/**
* Adds a vector ORDER BY clause.
*/
public function orderByVectorDistance(string $column, array|string|Subquery $vector, VectorDistance $vectorDistance = VectorDistance::COSINE, string $order = 'ASC'): static
{
$this->orderings[] = [
'type' => 'vectorDistanceOrdering',
'column' => $column,
'vector' => $vector,
'vectorDistance' => $vectorDistance,
'order' => ($order === 'ASC' || $order === 'asc') ? 'ASC' : 'DESC',
];

return $this;
}

/**
* Adds a ascending vector ORDER BY clause.
*/
public function ascendingVectorDistance(string $column, array|string|Subquery $vector, VectorDistance $vectorDistance = VectorDistance::COSINE): static
{
return $this->orderByVectorDistance($column, $vector, $vectorDistance, 'ASC');
}

/**
* Adds a descending vector ORDER BY clause.
*/
public function descendingVectorDistance(string $column, array|string|Subquery $vector, VectorDistance $vectorDistance = VectorDistance::COSINE): static
{
return $this->orderByVectorDistance($column, $vector, $vectorDistance, 'DESC');
}

/**
* Clears the ordering clauses.
*
Expand Down
17 changes: 17 additions & 0 deletions src/mako/database/query/VectorDistance.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

/**
* @copyright Frederic G. Østby
* @license http://www.makoframework.com/license
*/

namespace mako\database\query;

/**
* Vector distance.
*/
enum VectorDistance
{
case COSINE;
case EUCLIDEAN;
}
85 changes: 72 additions & 13 deletions src/mako/database/query/compilers/Compiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
use mako\database\query\Query;
use mako\database\query\Raw;
use mako\database\query\Subquery;
use mako\database\query\values\out\ValueWithAliasInterface;
use mako\database\query\values\ValueInterface;

use function array_keys;
use function array_shift;
Expand Down Expand Up @@ -83,6 +85,28 @@ protected function raw(Raw $raw): string
return $raw->getSql();
}

/**
* Compiles a value.
*/
protected function value(ValueInterface $value): string
{
$parameters = $value->getParameters();

if (!empty($parameters)) {
$this->params = [...$this->params, ...$parameters];
}

$sql = $value->getSql($this);

if ($value instanceof ValueWithAliasInterface) {
if (($alias = $value->getAlias()) !== null) {
$sql .= " AS {$this->escapeIdentifier($alias)}";
}
}

return $sql;
}

/**
* Compiles a subselect and merges the parameters.
*/
Expand Down Expand Up @@ -210,11 +234,13 @@ public function escapeTableNames(array $tables): string
*/
public function table(Raw|string|Subquery $table): string
{
if ($table instanceof Raw) {
return $this->raw($table);
}
elseif ($table instanceof Subquery) {
return $this->subquery($table);
if (is_object($table)) {
if ($table instanceof Raw) {
return $this->raw($table);
}
elseif ($table instanceof Subquery) {
return $this->subquery($table);
}
}

return $this->escapeTableNameWithAlias($table);
Expand Down Expand Up @@ -286,15 +312,21 @@ public function columnNames(array $columns): string
/**
* Compiles a column.
*/
public function column(Raw|string|Subquery $column, bool $allowAlias = false): string
public function column(Raw|string|Subquery|ValueInterface $column, bool $allowAlias = false): string
{
if ($column instanceof Raw) {
return $this->raw($column);
}
elseif ($column instanceof Subquery) {
return $this->subquery($column);
if (is_object($column)) {
if ($column instanceof Raw) {
return $this->raw($column);
}
elseif ($column instanceof Subquery) {
return $this->subquery($column);
}
elseif ($column instanceof ValueInterface) {
return $this->value($column);
}
}
elseif ($allowAlias && stripos($column, ' AS ') !== false) {

if ($allowAlias && stripos($column, ' AS ') !== false) {
[$column, , $alias] = explode(' ', $column, 3);

return "{$this->columnName($column)} AS {$this->columnName($alias)}";
Expand Down Expand Up @@ -378,6 +410,9 @@ protected function param(mixed $param, bool $enclose = true): string

return '?';
}
elseif ($param instanceof ValueInterface) {
return $this->value($param);
}
}

$this->params[] = $param;
Expand Down Expand Up @@ -483,6 +518,14 @@ protected function whereColumn(array $where): string
return "{$this->columnName($where['column1'])} {$where['operator']} {$this->columnName($where['column2'])}";
}

/**
* Compiles vector distance clauses.
*/
protected function whereVectorDistance(array $where): string
{
throw new DatabaseException(sprintf('The [ %s ] query compiler does not support vector distance calculations.', static::class));
}

/**
* Compiles BETWEEN clauses.
*/
Expand Down Expand Up @@ -631,6 +674,22 @@ protected function groupings(array $groupings): string
return empty($groupings) ? '' : " GROUP BY {$this->columns($groupings)}";
}

/**
* Compiles a basic ordering clause.
*/
protected function basicOrdering(array $order): string
{
return "{$this->columns($order['column'])} {$order['order']}";
}

/**
* Compiles vector distance ordering clause.
*/
protected function vectorDistanceOrdering(array $order): string
{
throw new DatabaseException(sprintf('The [ %s ] query compiler does not support vector distance calculations.', static::class));
}

/**
* Compiles ORDER BY clauses.
*/
Expand All @@ -643,7 +702,7 @@ protected function orderings(array $orderings): string
$sql = [];

foreach ($orderings as $order) {
$sql[] = "{$this->columns($order['column'])} {$order['order']}";
$sql[] = $this->{$order['type']}($order);
}

return ' ORDER BY ' . implode(', ', $sql);
Expand Down
49 changes: 49 additions & 0 deletions src/mako/database/query/compilers/MariaDB.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,62 @@

namespace mako\database\query\compilers;

use mako\database\query\Subquery;
use mako\database\query\VectorDistance;
use Override;

use function is_array;
use function json_encode;

/**
* Compiles MariaDB queries.
*/
class MariaDB extends MySQL
{
/**
* Returns a vector distance calculation.
*/
protected function vectorDistance(array $vectorDistance): string
{
$vector = $vectorDistance['vector'];

if ($vector instanceof Subquery) {
$vector = $this->subquery($vector);
}
else {
if (is_array($vector)) {
$vector = json_encode($vector);
}

$vector = "VEC_FromText({$this->param($vector)})";
}

$function = match ($vectorDistance['vectorDistance']) {
VectorDistance::COSINE => 'VEC_DISTANCE_COSINE',
VectorDistance::EUCLIDEAN => 'VEC_DISTANCE_EUCLIDEAN',
};

return "{$function}({$this->column($vectorDistance['column'], false)}, {$vector})";
}

/**
* {@inheritDoc}
*/
#[Override]
protected function whereVectorDistance(array $where): string
{
return "{$this->vectorDistance($where)} <= {$this->param($where['maxDistance'])}";
}

/**
* {@inheritDoc}
*/
#[Override]
protected function vectorDistanceOrdering(array $order): string
{
return "{$this->vectorDistance($order)} {$order['order']}";
}

/**
* {@inheritDoc}
*/
Expand Down
Loading