Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
43 changes: 2 additions & 41 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,9 @@ RUN composer install \
--no-scripts \
--prefer-dist

FROM php:8.4.18-cli-alpine3.22 AS compile
FROM appwrite/utopia-base:php-8.4-0.2.1 AS compile

ENV PHP_REDIS_VERSION="6.3.0" \
PHP_SWOOLE_VERSION="v6.1.6" \
PHP_XDEBUG_VERSION="3.4.2" \
PHP_MONGODB_VERSION="2.1.1"
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ENV PHP_MONGODB_VERSION="2.1.1"

RUN apk update && apk add --no-cache \
libpq \
Expand All @@ -28,8 +24,6 @@ RUN apk update && apk add --no-cache \
autoconf \
gcc \
g++ \
git \
brotli-dev \
linux-headers \
docker-cli \
docker-cli-compose \
Expand All @@ -48,24 +42,6 @@ RUN apk update && apk add --no-cache \
&& apk del libpq-dev \
&& rm -rf /var/cache/apk/*

# Redis Extension
FROM compile AS redis
RUN \
git clone --depth 1 --branch $PHP_REDIS_VERSION https://github.com/phpredis/phpredis.git \
&& cd phpredis \
&& phpize \
&& ./configure \
&& make && make install

## Swoole Extension
FROM compile AS swoole
RUN \
git clone --depth 1 --branch $PHP_SWOOLE_VERSION https://github.com/swoole/swoole-src.git \
&& cd swoole-src \
&& phpize \
&& ./configure --enable-http2 \
&& make && make install

## PCOV Extension
FROM compile AS pcov
RUN \
Expand All @@ -75,15 +51,6 @@ RUN \
&& ./configure --enable-pcov \
&& make && make install

## XDebug Extension
FROM compile AS xdebug
RUN \
git clone --depth 1 --branch $PHP_XDEBUG_VERSION https://github.com/xdebug/xdebug && \
cd xdebug && \
phpize && \
./configure && \
make && make install

FROM compile AS final

LABEL maintainer="team@appwrite.io"
Expand All @@ -93,10 +60,7 @@ ENV DEBUG=$DEBUG

WORKDIR /usr/src/code

RUN echo extension=redis.so >> /usr/local/etc/php/conf.d/redis.ini
RUN echo extension=swoole.so >> /usr/local/etc/php/conf.d/swoole.ini
RUN echo extension=pcov.so >> /usr/local/etc/php/conf.d/pcov.ini
RUN echo extension=xdebug.so >> /usr/local/etc/php/conf.d/xdebug.ini

RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"

Expand All @@ -105,10 +69,7 @@ RUN echo "opcache.enable_cli=1" >> $PHP_INI_DIR/php.ini
RUN echo "memory_limit=1024M" >> $PHP_INI_DIR/php.ini

COPY --from=composer /usr/local/src/vendor /usr/src/code/vendor
COPY --from=swoole /usr/local/lib/php/extensions/no-debug-non-zts-20240924/swoole.so /usr/local/lib/php/extensions/no-debug-non-zts-20240924/
COPY --from=redis /usr/local/lib/php/extensions/no-debug-non-zts-20240924/redis.so /usr/local/lib/php/extensions/no-debug-non-zts-20240924/
COPY --from=pcov /usr/local/lib/php/extensions/no-debug-non-zts-20240924/pcov.so /usr/local/lib/php/extensions/no-debug-non-zts-20240924/
COPY --from=xdebug /usr/local/lib/php/extensions/no-debug-non-zts-20240924/xdebug.so /usr/local/lib/php/extensions/no-debug-non-zts-20240924/

COPY ./bin /usr/src/code/bin
COPY ./src /usr/src/code/src
Expand Down
16 changes: 10 additions & 6 deletions src/Database/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -4271,11 +4271,14 @@ public function getDocument(string $collection, string $id, array $queries = [],
$selections
);

try {
$cached = $this->cache->load($documentKey, self::TTL, $hashKey);
} catch (Exception $e) {
Console::warning('Warning: Failed to get document from cache: ' . $e->getMessage());
$cached = null;
$cached = null;
if (!$forUpdate) {
try {
$cached = $this->cache->load($documentKey, self::TTL, $hashKey);
} catch (Exception $e) {
Console::warning('Warning: Failed to get document from cache: ' . $e->getMessage());
$cached = null;
}
}

if ($cached) {
Expand Down Expand Up @@ -4348,7 +4351,7 @@ public function getDocument(string $collection, string $id, array $queries = [],
);

// Don't save to cache if it's part of a relationship
if (empty($relationships)) {
if (!$forUpdate && empty($relationships)) {
try {
$this->cache->save($documentKey, $document->getArrayCopy(), $hashKey);
$this->cache->save($collectionKey, 'empty', $documentKey);
Expand Down Expand Up @@ -5698,6 +5701,7 @@ public function updateDocument(string $collection, string $id, Document $documen
// If values are not equal we need to update document.
if ($value !== $oldValue) {
$shouldUpdate = true;
error_log("DEBUG shouldUpdate: key={$key} type_new=" . gettype($value) . " type_old=" . gettype($oldValue) . " val_new=" . json_encode($value) . " val_old=" . json_encode($oldValue));
break;
}
}
Expand Down
48 changes: 48 additions & 0 deletions tests/e2e/Adapter/Scopes/DocumentTests.php
Original file line number Diff line number Diff line change
Expand Up @@ -7456,6 +7456,54 @@ public function testRegexInjection(): void
$database->deleteCollection($collectionName);
}

public function testUpdateDocumentUsesFreshForUpdateReadWhenCacheIsStale(): void
{
/** @var Database $database */
$database = $this->getDatabase();

$collectionId = 'for_update_cache';
$database->createCollection($collectionId);

if ($database->getAdapter()->getSupportForAttributes()) {
$this->assertEquals(true, $database->createAttribute($collectionId, 'a', Database::VAR_STRING, 255, false));
$this->assertEquals(true, $database->createAttribute($collectionId, 'b', Database::VAR_STRING, 255, false));
}

$database->createDocument($collectionId, new Document([
'$id' => 'doc1',
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
],
'a' => 'A1',
'b' => 'B1',
]));

// Prime cache with initial values.
$cached = $database->getDocument($collectionId, 'doc1');
$this->assertEquals('B1', $cached->getAttribute('b'));

$collection = $database->getCollection($collectionId);

// Simulate an out-of-band write that bypasses cache invalidation.
$outOfBand = $database->getAdapter()->getDocument($collection, 'doc1');
$outOfBand->setAttribute('b', 'B2');
$database->getAdapter()->updateDocument($collection, 'doc1', $outOfBand, true);

// Partial update should not overwrite untouched fields with stale cached values.
$updated = $database->updateDocument($collectionId, 'doc1', new Document([
'a' => 'A2',
]));

$this->assertEquals('A2', $updated->getAttribute('a'));
$this->assertEquals('B2', $updated->getAttribute('b'));

$fresh = $database->getDocument($collectionId, 'doc1');
$this->assertEquals('B2', $fresh->getAttribute('b'));

$database->deleteCollection($collectionId);
}

/**
* Test ReDoS (Regular Expression Denial of Service) with timeout protection
* This test verifies that ReDoS patterns either timeout properly or complete quickly,
Expand Down
Loading