Skip to content

fix: validate relationship document ID#11193

Merged
abnegate merged 19 commits intoappwrite:1.8.xfrom
premtsd-code:fix-10612-validate-relationship-document-id
Jan 29, 2026
Merged

fix: validate relationship document ID#11193
abnegate merged 19 commits intoappwrite:1.8.xfrom
premtsd-code:fix-10612-validate-relationship-document-id

Conversation

@premtsd-code
Copy link
Copy Markdown
Contributor

What does this PR do?

Validates relationship document IDs using CustomId validator to prevent creating documents/rows with invalid IDs.

Changes:

  • Adds validateRelationship() helper method in base Action.php class to avoid code duplication
  • Validates relationship document $id values (max 36 chars, valid characters: a-z, A-Z, 0-9, period, hyphen, underscore, can't start with special char)
  • Rejects invalid relationship value types with a clear error message

Files changed:

  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php
  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php
  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php
  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php
  • tests/e2e/Services/Databases/TablesDB/DatabasesBase.php

Test Plan

  • Added comprehensive test cases in tests/e2e/Services/Databases/TablesDB/DatabasesBase.php
  • All existing tests pass (109 tests, 2907 assertions)

Related PRs and Issues

Checklist

  • Have you read the Contributing Guidelines on issues?
  • If the PR includes a change to an API's metadata (desc, label, params, etc.), does it also include updated API specs and example docs?

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 26, 2026

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • 🔍 Trigger a full review
📝 Walkthrough

Walkthrough

Adds use Appwrite\Utopia\Database\Validator\CustomId; and introduces a protected method validateRelationship(mixed $relation): void in src/.../Action.php to normalize and validate relationship values (accepts Document, string IDs, or associative arrays; validates IDs with CustomId and throws a GENERAL_BAD_REQUEST on invalid input). Calls to validateRelationship were inserted into relation-processing loops in Create, Update, and Upsert handlers. An end-to-end test testInvalidRelationshipDocumentId was added to tests/e2e/Services/Databases/TablesDB/DatabasesBase.php twice (duplicate).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: validating relationship document IDs.
Description check ✅ Passed The description is well-detailed and directly related to the changeset, explaining the validation logic and affected files.
Linked Issues check ✅ Passed The PR addresses the primary objective from issue #10612 by preventing creation of rows with invalid IDs through CustomId validation.
Out of Scope Changes check ✅ Passed All changes are focused on validating relationship document IDs as specified in issue #10612; no out-of-scope changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Jan 26, 2026

Security Scan Results for PR

Docker Image Scan Results

Package Version Vulnerability Severity
libcrypto3 3.5.4-r0 CVE-2025-15467 CRITICAL
libcrypto3 3.5.4-r0 CVE-2025-69419 HIGH
libpng 1.6.51-r0 CVE-2025-66293 HIGH
libpng 1.6.51-r0 CVE-2026-22695 HIGH
libpng 1.6.51-r0 CVE-2026-22801 HIGH
libpng-dev 1.6.51-r0 CVE-2025-66293 HIGH
libpng-dev 1.6.51-r0 CVE-2026-22695 HIGH
libpng-dev 1.6.51-r0 CVE-2026-22801 HIGH
libssl3 3.5.4-r0 CVE-2025-15467 CRITICAL
libssl3 3.5.4-r0 CVE-2025-69419 HIGH
openssl 3.5.4-r0 CVE-2025-15467 CRITICAL
openssl 3.5.4-r0 CVE-2025-69419 HIGH
openssl-dev 3.5.4-r0 CVE-2025-15467 CRITICAL
openssl-dev 3.5.4-r0 CVE-2025-69419 HIGH
py3-urllib3 1.26.20-r0 CVE-2026-21441 HIGH
py3-urllib3-pyc 1.26.20-r0 CVE-2026-21441 HIGH

Source Code Scan Results

🎉 No vulnerabilities found!

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php`:
- Around line 254-281: The validateRelationship method currently overwrites any
client-provided associative-array relation $id by always assigning ID::unique();
change this so that when $relation is an associative array you only assign
ID::unique() if $relation['$id'] is not set or empty, and if a $id is provided
validate it with CustomId() exactly like the string/Document path; ensure the
validation and potential Exception::GENERAL_BAD_REQUEST uses the
validator->getDescription(), and return the normalized Document (wrapping the
array in new Document($relation)) only after validation so custom IDs are not
silently replaced (references: validateRelationship, Document, CustomId,
Exception::GENERAL_BAD_REQUEST).

In `@tests/e2e/Services/Databases/TablesDB/DatabasesBase.php`:
- Around line 7682-7695: The test currently posts a relationship via
$this->client->call to '/tablesdb/.../columns/relationship' using
Database::RELATION_ONE_TO_MANY and then just sleep(1), which can cause false
negatives if the column/relationship isn't ready; modify the test to assert the
relationship creation response (e.g., check response code and returned
column/relationship id) immediately after the call and replace the blind sleep
with a short poll that calls the column list/get endpoint until the new column
id appears or a timeout elapses before proceeding to negative ID-validation
cases.
🧹 Nitpick comments (4)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php (1)

214-215: Unused $relationId variable extracted from destructuring.

The $relationId value returned by validateRelationship() is never used after assignment. Use the ignore syntax to suppress the static analysis warning and clarify intent.

Suggested fix
-                    [$relationId, $relation] = $this->validateRelationship($relation);
+                    [, $relation] = $this->validateRelationship($relation);
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php (1)

312-313: Unused $relationId variable extracted from destructuring.

Same issue as in Upsert.php—$relationId is never referenced after assignment.

Suggested fix
-                    [$relationId, $relation] = $this->validateRelationship($relation);
+                    [, $relation] = $this->validateRelationship($relation);
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php (1)

204-205: Unused $relationId variable extracted from destructuring.

Same issue as in Upsert.php and Create.php—$relationId is never referenced after assignment.

Suggested fix
-                    [$relationId, $relation] = $this->validateRelationship($relation);
+                    [, $relation] = $this->validateRelationship($relation);
tests/e2e/Services/Databases/TablesDB/DatabasesBase.php (1)

7696-7878: Pin at least one error type/message to confirm the validator path.
Only checking 400 allows unrelated failures to pass. Adding a representative assertion on body['type'] (or message) strengthens the test’s intent.

✅ Minimal example
         $this->assertEquals(400, $response['headers']['status-code']);
+        $this->assertEquals(Exception::GENERAL_ARGUMENT_INVALID, $response['body']['type']);

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php`:
- Around line 311-319: The loop currently calls validateRelationship($relation)
before converting placeholder IDs, causing CustomId validation to reject
ID::unique() placeholders; move normalization so that you assign missing
$relation['$id'] = ID::unique() (and convert string "unique()" if your code uses
that placeholder) and wrap $relation into new Document($relation) prior to
calling validateRelationship, or alternatively update validateRelationship to
accept the placeholder (e.g., treat ID::unique() / "unique()" as valid) — adjust
the foreach around $relations, the validateRelationship method, and the Document
creation logic accordingly.

In
`@src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php`:
- Around line 203-211: The loop over $relations validates each relation before
normalizing the ID placeholder, causing inline relationship updates using
ID::unique() to fail; move the normalization step that replaces $relation['$id']
= ID::unique() (or otherwise resolves the "unique()" placeholder) to occur
before calling validateRelationship($relation), or update validateRelationship
to accept the placeholder; specifically adjust the foreach that currently calls
$this->validateRelationship($relation) prior to converting array relations into
Document instances so that normalization of '$id' (and conversion to Document)
happens first, then call validateRelationship on the normalized Document/array.
♻️ Duplicate comments (1)
tests/e2e/Services/Databases/TablesDB/DatabasesBase.php (1)

7695-7708: Assert relationship column readiness after polling.

If the column never appears, the test continues and failures can be misattributed to ID validation rather than schema readiness. Add an explicit assertion after the loop to fail fast. Line 7695 is the critical block.

🔧 Proposed fix
-        $maxAttempts = 10;
-        for ($i = 0; $i < $maxAttempts; $i++) {
+        $maxAttempts = 10;
+        $columnReady = false;
+        for ($i = 0; $i < $maxAttempts; $i++) {
             $columns = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $parentTableId . '/columns', array_merge([
                 'content-type' => 'application/json',
                 'x-appwrite-project' => $this->getProject()['$id'],
                 'x-appwrite-key' => $this->getProject()['apiKey']
             ]));
             $columnKeys = array_column($columns['body']['columns'], 'key');
             if (in_array('children', $columnKeys)) {
-                break;
+                $columnReady = true;
+                break;
             }
             usleep(200000);
         }
+        $this->assertTrue($columnReady, 'Relationship column "children" was not created in time.');

@premtsd-code premtsd-code force-pushed the fix-10612-validate-relationship-document-id branch from d13fe04 to e4dd435 Compare January 26, 2026 15:09
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@tests/e2e/Services/Databases/TablesDB/DatabasesBase.php`:
- Around line 7695-7709: The loop that polls for the relationship column using
$maxAttempts and the for ($i = 0; $i < $maxAttempts; $i++) construct can
silently continue if the 'children' column never appears; add a boolean flag
(e.g., $found = false) before the loop, set $found = true and break when
in_array('children', $columnKeys) is true, and after the loop assert that $found
is true (or fail the test with a clear message including $databaseId and
$parentTableId); optionally include the last $columns response/status in the
failure message for debugging.
♻️ Duplicate comments (1)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php (1)

253-275: Confirm CustomId accepts the unique() placeholder for relationship IDs.

validateRelationship() now applies CustomId before later logic converts 'unique()' to a generated ID in create/upsert flows. If CustomId doesn’t allow 'unique()', nested relationship creation that relies on that placeholder will start failing.

#!/bin/bash
# Locate CustomId validator and inspect handling of the unique() placeholder.
fd -a "CustomId.php" src
fd -a "CustomId.php" src -x rg -n "unique\\(\\)" -C2 {}
fd -a "CustomId.php" src -x rg -n "function isValid" -C3 {}

@premtsd-code premtsd-code force-pushed the fix-10612-validate-relationship-document-id branch 2 times, most recently from 8f71186 to 233ed80 Compare January 26, 2026 15:35
@premtsd-code premtsd-code force-pushed the fix-10612-validate-relationship-document-id branch from 233ed80 to 63e6a51 Compare January 26, 2026 15:45
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php`:
- Around line 253-275: The validateRelationship method currently ignores values
that are not Document, string, or associative array; update validateRelationship
to reject unsupported types by adding an else branch that throws an Exception
(use Exception::GENERAL_BAD_REQUEST) with a clear message (e.g. "Unsupported
relationship value type") so callers get a validation error for inputs like
integers/booleans; keep existing behavior for Document, string, and associative
array branches and reuse the same Exception class as used for CustomId
validation.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php`:
- Around line 261-275: After extracting $relationId (from Document, string, or
associative array) add a strict type guard so non-string values never reach
CustomId::isValid(): before creating the CustomId validator in Action.php, check
is_string($relationId) and if not throw Exception::GENERAL_BAD_REQUEST with an
appropriate message; then call (new CustomId())->isValid($relationId) and use
getDescription() as before. This ensures relationId, CustomId::isValid, and the
Exception path are only executed for string IDs and prevents TypeError 500s.
🧹 Nitpick comments (1)
tests/e2e/Services/Databases/TablesDB/DatabasesBase.php (1)

7706-7888: Optional: table‑drive the invalid/valid ID cases to reduce repetition.
This block repeats the same request shape with only the $id and expected status changing. A small helper or data-driven loop would make the test easier to extend and maintain.

@premtsd-code premtsd-code force-pushed the fix-10612-validate-relationship-document-id branch from bab6026 to cbe2d23 Compare January 28, 2026 10:24
@abnegate abnegate changed the base branch from main to 1.8.x January 28, 2026 11:02
@abnegate abnegate dismissed ArnabChatterjee20k’s stale review January 28, 2026 11:02

The base branch was changed.

@abnegate abnegate merged commit b1ca73a into appwrite:1.8.x Jan 29, 2026
159 of 165 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Can create row with invalid id

6 participants