Spin off from #3609986: Improve database exception messages.
Problem/Motivation
Database exceptions' hierarchy is a bit messy, and couple of them are unused. Found out in #3609986: Improve database exception messages.
Proposed resolution
- Cleanup the hierarchy structure and deprecate those that are unused.
- Introduce dedicated exceptions for table existence checks: SchemaTableDoesNotExistException and SchemaTableAlreadyExistsException
- Introduce a
SqlExecutionInfo value object that carries the details of the SQL operation that failed.
- Change database wrapper exceptions to include the
SqlExecutionInfo optionally.
New hierarchy structure of the database exceptions after MR merge (AI assisted in drawing the tree):
DatabaseException [interface]
├── ConnectionNotDefinedException
│ [Drupal\Core\Database\ConnectionNotDefinedException]
├── DriverNotSpecifiedException
│ [Drupal\Core\Database\DriverNotSpecifiedException]
├── InvalidQueryException
│ [Drupal\Core\Database\InvalidQueryException]
├── Query\FieldsOverlapException
│ [Drupal\Core\Database\Query\FieldsOverlapException]
├── Query\InvalidMergeQueryException
│ [Drupal\Core\Database\Query\InvalidMergeQueryException]
├── Query\NoFieldsException
│ [Drupal\Core\Database\Query\NoFieldsException]
├── Query\NoUniqueFieldException
│ [Drupal\Core\Database\Query\NoUniqueFieldException]
├── RowCountException
│ [Drupal\Core\Database\RowCountException]
├── SchemaDefinitionException
│ [Drupal\Core\Database\SchemaDefinitionException]
└── DatabaseExceptionWrapper
[Drupal\Core\Database\DatabaseExceptionWrapper]
├── DatabaseAccessDeniedException
│ [Drupal\Core\Database\DatabaseAccessDeniedException]
├── DatabaseConnectionRefusedException
│ [Drupal\Core\Database\DatabaseConnectionRefusedException]
├── DatabaseNotFoundException
│ [Drupal\Core\Database\DatabaseNotFoundException]
├── IntegrityConstraintViolationException
│ [Drupal\Core\Database\IntegrityConstraintViolationException]
├── SchemaException
│ [Drupal\Core\Database\SchemaException]
│ ├── SchemaObjectDoesNotExistException
│ │ [Drupal\Core\Database\SchemaObjectDoesNotExistException]
│ │ └── SchemaTableDoesNotExistException
│ │ [Drupal\Core\Database\Exception\SchemaTableDoesNotExistException]
│ ├── SchemaObjectExistsException
│ │ [Drupal\Core\Database\SchemaObjectExistsException]
│ │ └── SchemaTableAlreadyExistsException
│ │ [Drupal\Core\Database\Exception\SchemaTableAlreadyExistsException]
│ ├── SchemaPrimaryKeyMustBeDroppedException
│ │ [Drupal\Core\Database\Exception\SchemaPrimaryKeyMustBeDroppedException]
│ ├── SchemaTableColumnSizeTooLargeException
│ │ [Drupal\Core\Database\Exception\SchemaTableColumnSizeTooLargeException]
│ └── SchemaTableKeyTooLargeException
│ [Drupal\Core\Database\Exception\SchemaTableKeyTooLargeException]
└── TransactionException
[Drupal\Core\Database\TransactionException]
├── TransactionCommitFailedException
│ [Drupal\Core\Database\TransactionCommitFailedException]
├── TransactionExplicitCommitNotAllowedException [deprecated]
│ [Drupal\Core\Database\TransactionExplicitCommitNotAllowedException]
├── TransactionNameNonUniqueException
│ [Drupal\Core\Database\TransactionNameNonUniqueException]
├── TransactionNoActiveException [deprecated]
│ [Drupal\Core\Database\TransactionNoActiveException]
└── TransactionOutOfOrderException
[Drupal\Core\Database\TransactionOutOfOrderException]
Remaining tasks
User interface changes
API changes
Data model changes
Release notes snippet
For the committer
The changes to the .gitlab-ci.yml file need to be removed before merging!
Comments
Comment #2
mondrakeComment #3
mondrakeComment #5
mondrakeComment #6
dcam commentedI think this is mostly OK, but I did find one issue. There's a docblock reference to the deprecated
TransactionNoActiveExceptionatTransationManagerInterface::rollback(), https://git.drupalcode.org/project/drupal/-/blob/main/core/lib/Drupal/Co.... As far as I can tell onlyTransationManagerBase::rollback()implements this function and it does not throwTransactionNoActiveException. Maybe it did at one time. I attempted a brief search through its Git history to see if I could find out. Eventually I decided it wasn't that important and stopped. Anyway, I'm not familiar enough with the database subsystem to know for certain, but it seems like we can just delete this@throwsannotation.Comment #7
mondrakeDone #6 (thanks), added draft CR, linked in deprecation messages, bumped deprecation version to 12.1.
Comment #8
dcam commentedThank you for taking care of the deprecation messages. I knew there was something that I was forgetting. I did this review over several days and it slipped my mind.
Anyway, that was all the feedback I had. I'll go ahead and RTBC it so we can see what a committer says.
Comment #9
daffie commentedThe code changes in the MR look good.
Just a single old deprecation that needs to be updated.
What I am missing in the IS and maybe the CR is information about the new class SqlExecutionInfo. Also why it is added. I am not saying that it wrong or anything. Just explain what is added and why it should be added.
The same for the other changes in the MR. I am getting the feeling that they should be in different issues.
A lot of exception are getting an extra parameter. Should that be documented. At least in the IS.
Comment #10
mondrakeIntroduce dedicated exceptions for table existence checks: SchemaTableDoesNotExistException and SchemaTableAlreadyExistsException, to help simplifying #2371709: Move the on-demand-table creation into the database API.
Comment #12
longwaveNormalized all exceptions across all drivers as some SQLite and Postgres cases were missing before. Also improved the formatter a bit.
Parts of these commits were assisted by GPT 6.
Comment #13
mondrakeCouple of comments inline. Man AI-built tests are hard to follow...
Comment #14
mondrakeAll drivers' ExceptionHandlers now implement the pattern
handleStatementException()->rethrowNormalizedException(), but the base class does not and is now dead code for core anyway.Can we deprecate calling the base class'
handleStatementException()on the basis that it will become abstract in 13? and implement a stubrethrowNormalizedException()in the base class too that just throws?Comment #15
daffie commentedThis issue has become a blocker for #2371709: Move the on-demand-table creation into the database API and that issue has the priority critical. Therefore this issue also gets the priority critical.
Comment #16
mondrakeFiled a follow up.
Comment #17
longwaveAll pipelines are green.
Addressed the feedback from #13 and #14:
query()on every driver.SchemaTableDoesNotExistException.#[TestWith].Comment #18
longwaveDid another self-review pass, removed a bunch of tests that were testing nothing useful or duplicates. Also removed
fromThrowable()andstripFromMessage()as nothing calls them - we can reintroduce them when we need them? Hopefully this addresses @daffie's concerns in #9.Comment #19
mondrakeLovely and neat. Thanks!
+1 for RTBC but I cannot mark.
Comment #20
longwaveAdded two CRs, one for database driver maintainers who need to add the new exceptions, and another for the message format changes and
SqlExceptionInfo.Comment #21
daffie commentedI was a bit worried about a possible BC break with all the changes to the exceptions. Only I could not find an exception change that would be a BC break.
All the code changes look good to me.
@longwave what to do a minor documentation change in the MR, which is fine by me.
After that is it RTBC for me.
Comment #22
longwaveRenamed the variable, updated some comments and the CR to match.
Comment #23
daffie commentedThe last changes from @longwave look good to me.
There is enough testing added.
All code changes look good to me.
For me it is RTBC.
Comment #24
mondrakefound a small inconsistency, will update soon
Comment #25
mondrakeWith this MR,
DatabaseExceptionWrapperbecomes the base exception class for any error that is thrown by the lower-level database driver client (PDO, mysqli, etc).Based on this, IMHO
DatabaseAccessDeniedException,DatabaseConnectionRefusedExceptionandDatabaseNotFoundExceptionshould be descendants ofDatabaseExceptionWrapper, since they are triggered after the client returns an error.SchemaDefinitionExceptionshould no longer be a descendent ofSchemaException, since that inherits fromDatabaseExceptionWrapperand is triggered when execution of DML fails. But errors from the schema definition have nothing to do with the client connection. So it could be a direct implementation of the interface instead.Made changes accordingly in the last commit.
Comment #26
mondrakeUpdated the inheritance tree in the IS (AI assisted generation)
Comment #27
mondrakeComment #28
daffie commentedI have 2 remarks on the code changes:
Comment #29
daffie commentedThe changes to the gtilab-ci file need to be removed before merging the code changes into the main branch.
Comment #30
mondrake@daffie can't you run the other drivers' jobs from the UI instead of changing the ci setup? Just asking, I think I saw issues in relation to this, wanted to check.
Comment #31
mondrakeThanks. AI was lazy. Hopefully corrected now.
Comment #32
daffie commentedI am not allowed to do that. As a subsystem maintainer, I do not have that permission. There is somewhere a ticket open to fix that.
Comment #33
mondrakethanks for checking that. Done.
ok now I connect the dots, thx
Comment #34
daffie commentedAll my remarks have been addressed.
All code changes look good to me.
The IS and the CR's are in order.
For me it is RTBC.