Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
1a8e3a0
bigint
fogelito Jun 17, 2025
bf59887
autoincrement
fogelito Jun 17, 2025
b566721
Fix tests
fogelito Jun 17, 2025
6924ad9
Move higher
fogelito Jun 17, 2025
302d51a
lint
fogelito Jun 17, 2025
51c9b78
Add UNSIGNED
fogelito Jul 21, 2025
c6cba6e
Merge branch 'main' of github.com:utopia-php/database into primary-bi…
fogelito Jul 21, 2025
115e341
Spatial
fogelito Sep 10, 2025
745d785
Merge branch 'main' of github.com:utopia-php/database into spatial-en…
fogelito Sep 11, 2025
a313da3
Merge branch 'main' of github.com:utopia-php/database into spatial-en…
fogelito Sep 14, 2025
6f8bdf9
Postgres point
fogelito Sep 14, 2025
4ef5206
decodePoint
fogelito Sep 14, 2025
37db0b1
same decode
fogelito Sep 14, 2025
d01fdbc
decodeLinestring
fogelito Sep 14, 2025
6f27ff9
Remove commented-out test descriptions for new NOT query types in Que…
shimonewman Sep 14, 2025
1f8cdb2
Enhance MongoDB query capabilities by adding support for NOT operator…
shimonewman Sep 14, 2025
f25ede9
polygon
fogelito Sep 14, 2025
9e9d50a
Postgres linestring
fogelito Sep 14, 2025
4e195f8
Postgres polygon
fogelito Sep 14, 2025
bb4b3e4
Mysql polygon
fogelito Sep 14, 2025
b94dada
clean var_dump
fogelito Sep 14, 2025
7f7dfbb
Remove decodeSpatialData method
fogelito Sep 15, 2025
773df87
Remove try catch
fogelito Sep 15, 2025
f6bd630
Add hints
fogelito Sep 15, 2025
cc972d2
dbg
fogelito Sep 15, 2025
1b9b095
formatting
fogelito Sep 15, 2025
9a0e285
signature
fogelito Sep 15, 2025
5962c7b
Enhance MongoDB adapter by adding support for the '$nin' operator and…
shimonewman Sep 15, 2025
5d2c0c3
fix Pool adapter
fogelito Sep 15, 2025
7d61019
linter
shimonewman Sep 15, 2025
298196d
Clarify comment in Mongo adapter regarding handling of empty search t…
shimonewman Sep 15, 2025
87af182
link comment
shimonewman Sep 15, 2025
943c42e
Implement cursor paging in Mongo adapter with default batch size for …
shimonewman Sep 15, 2025
44cc3f3
formatting
fogelito Sep 15, 2025
e1ef8b5
Merge branch 'feat-mongo-tmp-pulls' of github.com:utopia-php/database…
shimonewman Sep 15, 2025
7849c6c
formatting
fogelito Sep 15, 2025
2aed9dc
fix getAttributeProjection
fogelito Sep 15, 2025
4ed19c6
Runn tests
fogelito Sep 15, 2025
ee7fbc1
decode polygon
fogelito Sep 15, 2025
2648ef9
remove $spatialAttributes
fogelito Sep 15, 2025
a0b963a
unpack
fogelito Sep 15, 2025
cfe9b84
stopOnFailure
fogelito Sep 15, 2025
d1f639a
Merge branch 'main' of github.com:utopia-php/database into primary-bi…
fogelito Sep 16, 2025
89a5ec6
Revert autoincrement set to default behaviour
fogelito Sep 16, 2025
6c5b7df
test find
fogelito Sep 16, 2025
b55d806
Merge pull request #609 from utopia-php/primary-bigint
abnegate Sep 16, 2025
a1a52f6
fix decode point
fogelito Sep 16, 2025
0275563
Merge branch 'main' of github.com:utopia-php/database into spatial-en…
fogelito Sep 16, 2025
c349c08
DatabaseException
fogelito Sep 16, 2025
96e8a5e
Postgres update point
fogelito Sep 16, 2025
09be1c0
formatting
fogelito Sep 16, 2025
fcc550b
Enable reconnection + retry
abnegate Sep 16, 2025
6496e63
Enable reconnect test
abnegate Sep 16, 2025
f499a46
Merge pull request #698 from utopia-php/spatial-encode-decode
abnegate Sep 16, 2025
8f2840d
Check in transaction before reconnect
abnegate Sep 16, 2025
9a046f2
Log connection error message
abnegate Sep 16, 2025
066e2bd
Merge pull request #705 from utopia-php/feat-enable-reconnect
abnegate Sep 16, 2025
f4f7453
Refactor Key and UID validators to use a constant for maximum length,…
shimonewman Sep 16, 2025
732ffef
Merge pull request #706 from utopia-php/1.x
abnegate Sep 16, 2025
00723f2
Refactor Mongo adapter to ensure cursor IDs are cast to integers for …
shimonewman Sep 16, 2025
ef36ef6
Refactor Key and UID validators to use a constant for maximum length,…
shimonewman Sep 16, 2025
f5cc21f
Merge branch 'main' of github.com:utopia-php/database into feat-mongo…
shimonewman Sep 17, 2025
f37e22a
sync with main
shimonewman Sep 18, 2025
cc841a4
remove inversion
shimonewman Sep 18, 2025
09793af
remove inversion
shimonewman Sep 18, 2025
901a7d8
remove inversion
shimonewman Sep 18, 2025
9152972
Merge branch 'feat-mongo-tmp-pulls' of github.com:utopia-php/database…
shimonewman Sep 18, 2025
075a2e3
Merge remote-tracking branch 'origin/feat-mongo-inversion' into feat-…
shimonewman Sep 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/Database/Adapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -1271,12 +1271,45 @@ abstract public function getInternalIndexesKeys(): array;
*/
abstract public function getSchemaAttributes(string $collection): array;

/**
* Get the query to check for tenant when in shared tables mode
*
* @param string $collection The collection being queried
* @param string $alias The alias of the parent collection if in a subquery
* @return string
*/
abstract public function getTenantQuery(string $collection, string $alias = ''): string;

/**
* @param mixed $stmt
* @return bool
*/
abstract protected function execute(mixed $stmt): bool;

/**
* Decode a WKB or textual POINT into [x, y]
*
* @param string $wkb
* @return float[] Array with two elements: [x, y]
*/
abstract public function decodePoint(string $wkb): array;

/**
* Decode a WKB or textual LINESTRING into [[x1, y1], [x2, y2], ...]
*
* @param string $wkb
* @return float[][] Array of points, each as [x, y]
*/
abstract public function decodeLinestring(string $wkb): array;

/**
* Decode a WKB or textual POLYGON into [[[x1, y1], [x2, y2], ...], ...]
*
* @param string $wkb
* @return float[][][] Array of rings, each ring is an array of points [x, y]
*/
abstract public function decodePolygon(string $wkb): array;

/**
* Returns the document after casting
* @param Document $collection
Expand Down
4 changes: 2 additions & 2 deletions src/Database/Adapter/MariaDB.php
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ public function createCollection(string $name, array $attributes = [], array $in

$collection = "
CREATE TABLE {$this->getSQLTable($id)} (
_id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
_uid VARCHAR(255) NOT NULL,
_createdAt DATETIME(3) DEFAULT NULL,
_updatedAt DATETIME(3) DEFAULT NULL,
Expand Down Expand Up @@ -186,7 +186,7 @@ public function createCollection(string $name, array $attributes = [], array $in

$permissions = "
CREATE TABLE {$this->getSQLTable($id . '_perms')} (
_id int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
_type VARCHAR(12) NOT NULL,
_permission VARCHAR(255) NOT NULL,
_document VARCHAR(255) NOT NULL,
Expand Down
141 changes: 127 additions & 14 deletions src/Database/Adapter/Mongo.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,24 @@ class Mongo extends Adapter
'$gt',
'$gte',
'$in',
'$nin',
'$text',
'$search',
'$or',
'$and',
'$match',
'$regex',
'$not',
'$nor',
];

protected Client $client;

/**
* Default batch size for cursor operations
*/
private const DEFAULT_BATCH_SIZE = 1000;

//protected ?int $timeout = null;

/**
Expand Down Expand Up @@ -1277,15 +1285,43 @@ public function getSequences(string $collection, array $documents): array
$filters['_tenant'] = $this->getTenantFilters($collection, $documentTenants);
}
try {
$results = $this->client->find($name, $filters, ['projection' => ['_uid' => 1, '_id' => 1]]);
// Use cursor paging for large result sets
$options = [
'projection' => ['_uid' => 1, '_id' => 1],
'batchSize' => self::DEFAULT_BATCH_SIZE
];

$response = $this->client->find($name, $filters, $options);
$results = $response->cursor->firstBatch ?? [];

// Process first batch
foreach ($results as $result) {
$sequences[$result->_uid] = (string)$result->_id;
}

// Get cursor ID for subsequent batches
$cursorId = $response->cursor->id ?? null;

// Continue fetching with getMore
while ($cursorId && $cursorId !== 0) {
$moreResponse = $this->client->getMore((int)$cursorId, $name, self::DEFAULT_BATCH_SIZE);
$moreResults = $moreResponse->cursor->nextBatch ?? [];

if (empty($moreResults)) {
break;
}

foreach ($moreResults as $result) {
$sequences[$result->_uid] = (string)$result->_id;
}

// Update cursor ID for next iteration
$cursorId = (int)($moreResponse->cursor->id ?? 0);
}
} catch (MongoException $e) {
throw $this->processException($e);
}

foreach ($results->cursor->firstBatch as $result) {
$sequences[$result->_uid] = (string)$result->_id;
}

foreach ($documents as $document) {
if (isset($sequences[$document->getId()])) {
$document['$sequence'] = $sequences[$document->getId()];
Expand Down Expand Up @@ -1575,12 +1611,10 @@ public function find(Document $collection, array $queries = [], ?int $limit = 25

try {
// Use proper cursor iteration with reasonable batch size
$batchSize = 1000;
$options['batchSize'] = $batchSize;
$options['batchSize'] = self::DEFAULT_BATCH_SIZE;

$response = $this->client->find($name, $filters, $options);
$results = $response->cursor->firstBatch ?? [];

// Process first batch
foreach ($results as $result) {
$record = $this->replaceChars('_', '$', (array)$result);
Expand All @@ -1597,7 +1631,7 @@ public function find(Document $collection, array $queries = [], ?int $limit = 25
break;
}

$moreResponse = $this->client->getMore($cursorId, $name, $batchSize);
$moreResponse = $this->client->getMore((int)$cursorId, $name, self::DEFAULT_BATCH_SIZE);
$moreResults = $moreResponse->cursor->nextBatch ?? [];

if (empty($moreResults)) {
Expand All @@ -1614,7 +1648,7 @@ public function find(Document $collection, array $queries = [], ?int $limit = 25
}
}

$cursorId = $moreResponse->cursor->id ?? 0;
$cursorId = (int)($moreResponse->cursor->id ?? 0);
}

} catch (MongoException $e) {
Expand Down Expand Up @@ -1985,11 +2019,38 @@ protected function buildFilter(Query $query): array
} else {
$filter[$attribute]['$in'] = $query->getValues();
}
} elseif ($operator === 'notContains') {
if (!$query->onArray()) {
$filter[$attribute] = ['$not' => new Regex(".*{$this->escapeWildcards($value)}.*", 'i')];
} else {
$filter[$attribute]['$nin'] = $query->getValues();
}
} elseif ($operator == '$search') {
$filter['$text'][$operator] = $value;
if ($query->getMethod() === Query::TYPE_NOT_SEARCH) {
// MongoDB doesn't support negating $text expressions directly
// Use regex as fallback for NOT search while keeping fulltext for positive search
if (empty($value)) {
// If value is not passed, don't add any filter - this will match all documents
} else {
// Escape special regex characters and create a pattern that matches the search term as substring
$escapedValue = preg_quote($value, '/');
$filter[$attribute] = ['$not' => new Regex(".*{$escapedValue}.*", 'i')];
}
} else {
$filter['$text'][$operator] = $value;
}
} elseif ($operator === Query::TYPE_BETWEEN) {
$filter[$attribute]['$lte'] = $value[1];
$filter[$attribute]['$gte'] = $value[0];
} elseif ($operator === Query::TYPE_NOT_BETWEEN) {
$filter['$or'] = [
[$attribute => ['$lt' => $value[0]]],
[$attribute => ['$gt' => $value[1]]]
];
} elseif ($operator === '$regex' && $query->getMethod() === Query::TYPE_NOT_STARTS_WITH) {
$filter[$attribute] = ['$not' => new Regex('^' . $value, 'i')];
} elseif ($operator === '$regex' && $query->getMethod() === Query::TYPE_NOT_ENDS_WITH) {
$filter[$attribute] = ['$not' => new Regex($value . '$', 'i')];
} else {
$filter[$attribute][$operator] = $value;
}
Expand Down Expand Up @@ -2017,13 +2078,18 @@ protected function getQueryOperator(string $operator): string
Query::TYPE_GREATER => '$gt',
Query::TYPE_GREATER_EQUAL => '$gte',
Query::TYPE_CONTAINS => '$in',
Query::TYPE_NOT_CONTAINS => 'notContains',
Query::TYPE_SEARCH => '$search',
Query::TYPE_NOT_SEARCH => '$search',
Query::TYPE_BETWEEN => 'between',
Query::TYPE_NOT_BETWEEN => 'notBetween',
Query::TYPE_STARTS_WITH,
Query::TYPE_ENDS_WITH => '$regex',
Query::TYPE_NOT_STARTS_WITH,
Query::TYPE_ENDS_WITH,
Query::TYPE_NOT_ENDS_WITH => '$regex',
Query::TYPE_OR => '$or',
Query::TYPE_AND => '$and',
default => throw new DatabaseException('Unknown operator:' . $operator . '. Must be one of ' . Query::TYPE_EQUAL . ', ' . Query::TYPE_NOT_EQUAL . ', ' . Query::TYPE_LESSER . ', ' . Query::TYPE_LESSER_EQUAL . ', ' . Query::TYPE_GREATER . ', ' . Query::TYPE_GREATER_EQUAL . ', ' . Query::TYPE_IS_NULL . ', ' . Query::TYPE_IS_NOT_NULL . ', ' . Query::TYPE_BETWEEN . ', ' . Query::TYPE_CONTAINS . ', ' . Query::TYPE_SEARCH . ', ' . Query::TYPE_SELECT),
default => throw new DatabaseException('Unknown operator:' . $operator . '. Must be one of ' . Query::TYPE_EQUAL . ', ' . Query::TYPE_NOT_EQUAL . ', ' . Query::TYPE_LESSER . ', ' . Query::TYPE_LESSER_EQUAL . ', ' . Query::TYPE_GREATER . ', ' . Query::TYPE_GREATER_EQUAL . ', ' . Query::TYPE_IS_NULL . ', ' . Query::TYPE_IS_NOT_NULL . ', ' . Query::TYPE_BETWEEN . ', ' . Query::TYPE_NOT_BETWEEN . ', ' . Query::TYPE_STARTS_WITH . ', ' . Query::TYPE_NOT_STARTS_WITH . ', ' . Query::TYPE_ENDS_WITH . ', ' . Query::TYPE_NOT_ENDS_WITH . ', ' . Query::TYPE_CONTAINS . ', ' . Query::TYPE_NOT_CONTAINS . ', ' . Query::TYPE_SEARCH . ', ' . Query::TYPE_NOT_SEARCH . ', ' . Query::TYPE_SELECT),
};
}

Expand All @@ -2033,9 +2099,15 @@ protected function getQueryValue(string $method, mixed $value): mixed
case Query::TYPE_STARTS_WITH:
$value = $this->escapeWildcards($value);
return $value . '.*';
case Query::TYPE_NOT_STARTS_WITH:
$value = $this->escapeWildcards($value);
return $value . '.*';
case Query::TYPE_ENDS_WITH:
$value = $this->escapeWildcards($value);
return '.*' . $value;
case Query::TYPE_NOT_ENDS_WITH:
$value = $this->escapeWildcards($value);
return '.*' . $value;
default:
return $value;
}
Expand Down Expand Up @@ -2232,7 +2304,7 @@ public function getSupportForFulltextWildcardIndex(): bool
*/
public function getSupportForQueryContains(): bool
{
return false;
return true;
}

/**
Expand Down Expand Up @@ -2628,4 +2700,45 @@ public function getTenantFilters(

return ['$in' => $values];
}

public function decodePoint(string $wkb): array
{
return [];
}

/**
* Decode a WKB or textual LINESTRING into [[x1, y1], [x2, y2], ...]
*
* @param string $wkb
* @return float[][] Array of points, each as [x, y]
*/
public function decodeLinestring(string $wkb): array
{
return [];
}

/**
* Decode a WKB or textual POLYGON into [[[x1, y1], [x2, y2], ...], ...]
*
* @param string $wkb
* @return float[][][] Array of rings, each ring is an array of points [x, y]
*/
public function decodePolygon(string $wkb): array
{
return [];
}

/**
* Get the query to check for tenant when in shared tables mode
*
* @param string $collection The collection being queried
* @param string $alias The alias of the parent collection if in a subquery
* @return string
*/
public function getTenantQuery(string $collection, string $alias = ''): string
{
return '';
}


}
23 changes: 16 additions & 7 deletions src/Database/Adapter/Pool.php
Original file line number Diff line number Diff line change
Expand Up @@ -470,13 +470,7 @@ public function getKeywords(): array
return $this->delegate(__FUNCTION__, \func_get_args());
}

/**
* @param array<string,mixed> $selections
* @param string $prefix
* @param array<string,mixed> $spatialAttributes
* @return mixed
*/
protected function getAttributeProjection(array $selections, string $prefix, array $spatialAttributes = []): mixed
protected function getAttributeProjection(array $selections, string $prefix): mixed
{
return $this->delegate(__FUNCTION__, \func_get_args());
}
Expand Down Expand Up @@ -550,6 +544,21 @@ public function getSupportForSpatialAxisOrder(): bool
return $this->delegate(__FUNCTION__, \func_get_args());
}

public function decodePoint(string $wkb): array
{
return $this->delegate(__FUNCTION__, \func_get_args());
}

public function decodeLinestring(string $wkb): array
{
return $this->delegate(__FUNCTION__, \func_get_args());
}

public function decodePolygon(string $wkb): array
{
return $this->delegate(__FUNCTION__, \func_get_args());
}

public function castingBefore(Document $collection, Document $document): Document
{
return $this->delegate(__FUNCTION__, \func_get_args());
Expand Down
Loading