Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unexpected NULL in createOrFirst() with Multiple Unique Constraints #48235

Closed
fuwasegu opened this issue Aug 30, 2023 · 0 comments · Fixed by #48234
Closed

Unexpected NULL in createOrFirst() with Multiple Unique Constraints #48235

fuwasegu opened this issue Aug 30, 2023 · 0 comments · Fixed by #48234

Comments

@fuwasegu
Copy link
Contributor

Laravel Version

10.21.0

PHP Version

8.2.8

Database Driver & Version

No response

Description

Summary

The current implementation of createOrFirst() in Laravel's Eloquent Builder has a behavior that could lead to unintended consequences when there are multiple unique constraints.

Current Behavior

The createOrFirst() method tries to create a record first and, if a UniqueConstraintViolationException occurs, it falls back to finding the record based on $attributes.

Here is the current implementation:

public function createOrFirst(array $attributes = [], array $values = [])
{
try {
return $this->withSavepointIfNeeded(fn () => $this->create(array_merge($attributes, $values)));
} catch (UniqueConstraintViolationException) {
return $this->useWritePdo()->where($attributes)->first();
}
}

Issue

Assume that we have two columns id_a and id_b, both of which are unique, and id_a is the primary key.

If the database already has the following records:

(id_a, id_b) = [(1, 100), (2, 200), (3, 300), (4, 400)]
And we call createOrFirst(['id_a' => 5], ['id_b' => 400])

The create() call in the try block will fail because of id_b being unique. However, the catch block only looks for a record where id_a = 5, which will return NULL.

Steps To Reproduce

  1. Create a database table with two unique columns, for example id_a and id_b. Make id_a the primary key.
CREATE TABLE example (
    id_a INT PRIMARY KEY,
    id_b UNIQUE
);
  1. Insert some initial records into the table.
INSERT INTO example (id_a, id_b) VALUES (1, 100), (2, 200), (3, 300), (4, 400);
  1. Use Laravel's Eloquent Builder to call the createOrFirst() method.
ExampleModel::createOrFirst(['id_a' => 5], ['id_b' => 400]);
  1. Observe that a UniqueConstraintViolationException is thrown for id_b.
  2. Check the returned value; it will be NULL because the catch block is only considering id_a for the search.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
1 participant