Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
106 changes: 58 additions & 48 deletions src/Database/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -4432,15 +4432,17 @@ public function createDocument(string $collection, Document $document): Document
}
}

$structure = new Structure(
$collection,
$this->adapter->getIdAttributeType(),
$this->adapter->getMinDateTime(),
$this->adapter->getMaxDateTime(),
$this->adapter->getSupportForAttributes()
);
if (!$structure->isValid($document)) {
throw new StructureException($structure->getDescription());
if ($this->validate) {
$structure = new Structure(
$collection,
$this->adapter->getIdAttributeType(),
$this->adapter->getMinDateTime(),
$this->adapter->getMaxDateTime(),
$this->adapter->getSupportForAttributes()
);
if (!$structure->isValid($document)) {
throw new StructureException($structure->getDescription());
}
}

$document = $this->adapter->castingBefore($collection, $document);
Expand Down Expand Up @@ -4534,15 +4536,17 @@ public function createDocuments(

$document = $this->encode($collection, $document);

$validator = new Structure(
$collection,
$this->adapter->getIdAttributeType(),
$this->adapter->getMinDateTime(),
$this->adapter->getMaxDateTime(),
$this->adapter->getSupportForAttributes()
);
if (!$validator->isValid($document)) {
throw new StructureException($validator->getDescription());
if ($this->validate) {
$validator = new Structure(
$collection,
$this->adapter->getIdAttributeType(),
$this->adapter->getMinDateTime(),
$this->adapter->getMaxDateTime(),
$this->adapter->getSupportForAttributes()
);
if (!$validator->isValid($document)) {
throw new StructureException($validator->getDescription());
}
}

if ($this->resolveRelationships) {
Expand Down Expand Up @@ -5094,16 +5098,18 @@ public function updateDocument(string $collection, string $id, Document $documen

$document = $this->encode($collection, $document);

$structureValidator = new Structure(
$collection,
$this->adapter->getIdAttributeType(),
$this->adapter->getMinDateTime(),
$this->adapter->getMaxDateTime(),
$this->adapter->getSupportForAttributes(),
$old
);
if (!$structureValidator->isValid($document)) { // Make sure updated structure still apply collection rules (if any)
throw new StructureException($structureValidator->getDescription());
if ($this->validate) {
$structureValidator = new Structure(
$collection,
$this->adapter->getIdAttributeType(),
$this->adapter->getMinDateTime(),
$this->adapter->getMaxDateTime(),
$this->adapter->getSupportForAttributes(),
$old
);
if (!$structureValidator->isValid($document)) { // Make sure updated structure still apply collection rules (if any)
throw new StructureException($structureValidator->getDescription());
}
}

if ($this->resolveRelationships) {
Expand Down Expand Up @@ -5250,17 +5256,19 @@ public function updateDocuments(
applyDefaults: false
);

$validator = new PartialStructure(
$collection,
$this->adapter->getIdAttributeType(),
$this->adapter->getMinDateTime(),
$this->adapter->getMaxDateTime(),
$this->adapter->getSupportForAttributes(),
null // No old document available in bulk updates
);
if ($this->validate) {
$validator = new PartialStructure(
$collection,
$this->adapter->getIdAttributeType(),
$this->adapter->getMinDateTime(),
$this->adapter->getMaxDateTime(),
$this->adapter->getSupportForAttributes(),
null // No old document available in bulk updates
);

if (!$validator->isValid($updates)) {
throw new StructureException($validator->getDescription());
if (!$validator->isValid($updates)) {
throw new StructureException($validator->getDescription());
}
}

$originalLimit = $limit;
Expand Down Expand Up @@ -6019,17 +6027,19 @@ public function upsertDocumentsWithIncrease(
}
}

$validator = new Structure(
$collection,
$this->adapter->getIdAttributeType(),
$this->adapter->getMinDateTime(),
$this->adapter->getMaxDateTime(),
$this->adapter->getSupportForAttributes(),
$old->isEmpty() ? null : $old
);
if ($this->validate) {
$validator = new Structure(
$collection,
$this->adapter->getIdAttributeType(),
$this->adapter->getMinDateTime(),
$this->adapter->getMaxDateTime(),
$this->adapter->getSupportForAttributes(),
$old->isEmpty() ? null : $old
);

if (!$validator->isValid($document)) {
throw new StructureException($validator->getDescription());
if (!$validator->isValid($document)) {
throw new StructureException($validator->getDescription());
}
}

$document = $this->encode($collection, $document);
Expand Down
178 changes: 178 additions & 0 deletions tests/e2e/Adapter/Scopes/DocumentTests.php
Original file line number Diff line number Diff line change
Expand Up @@ -6185,4 +6185,182 @@ public function testCreateUpdateDocumentsMismatch(): void
$database->deleteCollection($colName);
}

public function testBypassStructureWithSupportForAttributes(): void
{
/** @var Database $database */
$database = static::getDatabase();
// for schemaless the validation will be automatically skipped
if (!$database->getAdapter()->getSupportForAttributes()) {
$this->expectNotToPerformAssertions();
return;
}

$collectionId = 'successive_update_single';

$database->createCollection($collectionId);
$database->createAttribute($collectionId, 'attrA', Database::VAR_STRING, 50, true);
$database->createAttribute($collectionId, 'attrB', Database::VAR_STRING, 50, true);

// bypass required
$database->disableValidation();

$permissions = [Permission::read(Role::any()), Permission::write(Role::any()), Permission::update(Role::any()), Permission::delete(Role::any())];
$docs = $database->createDocuments($collectionId, [
new Document(['attrA' => null,'attrB' => 'B','$permissions' => $permissions])
]);

$docs = $database->find($collectionId);
foreach ($docs as $doc) {
$this->assertArrayHasKey('attrA', $doc->getAttributes());
$this->assertNull($doc->getAttribute('attrA'));
$this->assertEquals('B', $doc->getAttribute('attrB'));
}
// reset
$database->enableValidation();

try {
$database->createDocuments($collectionId, [
new Document(['attrA' => null,'attrB' => 'B','$permissions' => $permissions])
]);
$this->fail('Failed to throw exception');
} catch (Exception $e) {
$this->assertInstanceOf(StructureException::class, $e);
}

$database->deleteCollection($collectionId);
}

public function testValidationGuardsWithNullRequired(): void
{
/** @var Database $database */
$database = static::getDatabase();

if (!$database->getAdapter()->getSupportForAttributes()) {
$this->expectNotToPerformAssertions();
return;
}

// Base collection and attributes
$collection = 'validation_guard_all';
$database->createCollection($collection, permissions: [
Permission::read(Role::any()),
Permission::create(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
], documentSecurity: true);
$database->createAttribute($collection, 'name', Database::VAR_STRING, 32, true);
$database->createAttribute($collection, 'age', Database::VAR_INTEGER, 0, true);
$database->createAttribute($collection, 'value', Database::VAR_INTEGER, 0, false);

// 1) createDocument with null required should fail when validation enabled, pass when disabled
try {
$database->createDocument($collection, new Document([
'$permissions' => [Permission::read(Role::any()), Permission::create(Role::any())],
'name' => null,
'age' => null,
]));
$this->fail('Failed to throw exception');
} catch (Throwable $e) {
$this->assertInstanceOf(StructureException::class, $e);
}

$database->disableValidation();
$doc = $database->createDocument($collection, new Document([
'$id' => 'created-null',
'$permissions' => [Permission::read(Role::any()), Permission::create(Role::any()), Permission::update(Role::any())],
'name' => null,
'age' => null,
]));
$this->assertEquals('created-null', $doc->getId());
$database->enableValidation();

// Seed a valid document for updates
$valid = $database->createDocument($collection, new Document([
'$id' => 'valid',
'$permissions' => [Permission::read(Role::any()), Permission::update(Role::any())],
'name' => 'ok',
'age' => 10,
]));
$this->assertEquals('valid', $valid->getId());

// 2) updateDocument set required to null should fail when validation enabled, pass when disabled
try {
$database->updateDocument($collection, 'valid', new Document([
'age' => null,
]));
$this->fail('Failed to throw exception');
} catch (Throwable $e) {
$this->assertInstanceOf(StructureException::class, $e);
}

$database->disableValidation();
$updated = $database->updateDocument($collection, 'valid', new Document([
'age' => null,
]));
$this->assertNull($updated->getAttribute('age'));
$database->enableValidation();

// Seed a few valid docs for bulk update
for ($i = 0; $i < 2; $i++) {
$database->createDocument($collection, new Document([
'$id' => 'b' . $i,
'$permissions' => [Permission::read(Role::any()), Permission::update(Role::any())],
'name' => 'ok',
'age' => 1,
]));
}

// 3) updateDocuments setting required to null should fail when validation enabled, pass when disabled
if ($database->getAdapter()->getSupportForBatchOperations()) {
try {
$database->updateDocuments($collection, new Document([
'name' => null,
]));
$this->fail('Failed to throw exception');
} catch (Throwable $e) {
$this->assertInstanceOf(StructureException::class, $e);
}

$database->disableValidation();
$count = $database->updateDocuments($collection, new Document([
'name' => null,
]));
$this->assertGreaterThanOrEqual(3, $count); // at least the seeded docs are updated
$database->enableValidation();
}

// 4) upsertDocumentsWithIncrease with null required should fail when validation enabled, pass when disabled
if ($database->getAdapter()->getSupportForUpserts()) {
try {
$database->upsertDocumentsWithIncrease(
collection: $collection,
attribute: 'value',
documents: [new Document([
'$id' => 'u1',
'name' => null, // required null
'value' => 1,
])]
);
$this->fail('Failed to throw exception');
} catch (Throwable $e) {
$this->assertInstanceOf(StructureException::class, $e);
}

$database->disableValidation();
$ucount = $database->upsertDocumentsWithIncrease(
collection: $collection,
attribute: 'value',
documents: [new Document([
'$id' => 'u1',
'name' => null,
'value' => 1,
])]
);
$this->assertEquals(1, $ucount);
$database->enableValidation();
}

// Cleanup
$database->deleteCollection($collection);
}
}