Use 'insert ... returning' instead of 'Db::GetLastInsertedId()'

This commit is contained in:
Alex Cabal 2025-03-10 11:04:20 -05:00
parent 4446b6d161
commit 5066252355
12 changed files with 44 additions and 57 deletions

View file

@ -215,3 +215,11 @@ Before submitting design contributions, please discuss them with the Standard Eb
else else
print('Bar!'); print('Bar!');
```` ````
- SQL statements have the first keyword in ALL CAPS to facilitate syntax highlighting; however subsequent keywords are all lowercase.
````sql
SELECT * from Users where Username = 'Foo'
````
- When using SQL to insert a record and later retrieve the last inserted ID, use the `insert ... returning` construct, and not `insert` followed by `select last_insert_id()`.

View file

@ -268,14 +268,13 @@ class Artist{
*/ */
public function Create(): void{ public function Create(): void{
$this->Validate(); $this->Validate();
Db::Query(' $this->ArtistId = Db::QueryInt('
INSERT into Artists (Name, UrlName, DeathYear) INSERT into Artists (Name, UrlName, DeathYear)
values (?, values (?,
?, ?,
?) ?)
returning ArtistId
', [$this->Name, $this->UrlName, $this->DeathYear]); ', [$this->Name, $this->UrlName, $this->DeathYear]);
$this->ArtistId = Db::GetLastInsertedId();
} }
/** /**

View file

@ -720,7 +720,7 @@ class Artwork{
$this->Artist = Artist::GetOrCreate($this->Artist); $this->Artist = Artist::GetOrCreate($this->Artist);
Db::Query(' $this->ArtworkId = Db::QueryInt('
INSERT into INSERT into
Artworks (ArtistId, Name, UrlName, CompletedYear, CompletedYearIsCirca, Created, Updated, Status, SubmitterUserId, ReviewerUserId, MuseumUrl, Artworks (ArtistId, Name, UrlName, CompletedYear, CompletedYearIsCirca, Created, Updated, Status, SubmitterUserId, ReviewerUserId, MuseumUrl,
PublicationYear, PublicationYearPageUrl, CopyrightPageUrl, ArtworkPageUrl, IsPublishedInUs, PublicationYear, PublicationYearPageUrl, CopyrightPageUrl, ArtworkPageUrl, IsPublishedInUs,
@ -745,13 +745,12 @@ class Artwork{
?, ?,
?, ?,
?) ?)
returning ArtworkId
', [$this->Artist->ArtistId, $this->Name, $this->UrlName, $this->CompletedYear, $this->CompletedYearIsCirca, ', [$this->Artist->ArtistId, $this->Name, $this->UrlName, $this->CompletedYear, $this->CompletedYearIsCirca,
$this->Created, $this->Updated, $this->Status, $this->SubmitterUserId, $this->ReviewerUserId, $this->MuseumUrl, $this->PublicationYear, $this->PublicationYearPageUrl, $this->Created, $this->Updated, $this->Status, $this->SubmitterUserId, $this->ReviewerUserId, $this->MuseumUrl, $this->PublicationYear, $this->PublicationYearPageUrl,
$this->CopyrightPageUrl, $this->ArtworkPageUrl, $this->IsPublishedInUs, $this->EbookUrl, $this->MimeType, $this->Exception, $this->Notes] $this->CopyrightPageUrl, $this->ArtworkPageUrl, $this->IsPublishedInUs, $this->EbookUrl, $this->MimeType, $this->Exception, $this->Notes]
); );
$this->ArtworkId = Db::GetLastInsertedId();
foreach($this->Tags as $tag){ foreach($this->Tags as $tag){
Db::Query(' Db::Query('
INSERT into ArtworkTags (ArtworkId, TagId) INSERT into ArtworkTags (ArtworkId, TagId)

View file

@ -65,13 +65,14 @@ class ArtworkTag extends Tag{
public function Create(): void{ public function Create(): void{
$this->Validate(); $this->Validate();
Db::Query(' $this->TagId = Db::QueryInt('
INSERT into Tags (Name, UrlName, Type) INSERT into Tags (Name, UrlName, Type)
values (?, values (?,
?, ?,
?) ?)
returning
TagId
', [$this->Name, $this->UrlName, $this->Type]); ', [$this->Name, $this->UrlName, $this->Type]);
$this->TagId = Db::GetLastInsertedId();
} }
/** /**

View file

@ -162,13 +162,13 @@ class Collection{
public function Create(): void{ public function Create(): void{
$this->Validate(); $this->Validate();
Db::Query(' $this->CollectionId = Db::QueryInt('
INSERT into Collections (Name, UrlName, Type) INSERT into Collections (Name, UrlName, Type)
values (?, values (?,
?, ?,
?) ?)
returning CollectionId
', [$this->Name, $this->UrlName, $this->Type]); ', [$this->Name, $this->UrlName, $this->Type]);
$this->CollectionId = Db::GetLastInsertedId();
} }
/** /**

View file

@ -11,10 +11,13 @@ class Db{
/** /**
* Returns a single integer value for the first column database query result. * Returns a single integer value for the first column database query result.
* *
* This is useful for queries that return a single integer as a result, like `count(*)` or `sum(*)`. * This is useful for queries that return a single integer as a result, like `count(*)`, `sum(*)`, or `insert ... returning`.
* *
* @param string $query * @param string $query
* @param array<mixed> $args * @param array<mixed> $args
*
* @throws Exceptions\DuplicateDatabaseKeyException If a unique key constraint has been violated.
* @throws Exceptions\DatabaseQueryException If an error occurs during execution of the query.
*/ */
public static function QueryInt(string $query, array $args = []): int{ public static function QueryInt(string $query, array $args = []): int{
$result = static::Query($query, $args); $result = static::Query($query, $args);
@ -33,6 +36,8 @@ class Db{
* *
* @param string $query * @param string $query
* @param array<mixed> $args * @param array<mixed> $args
*
* @throws Exceptions\DatabaseQueryException If an error occurs during execution of the query.
*/ */
public static function QueryFloat(string $query, array $args = []): float{ public static function QueryFloat(string $query, array $args = []): float{
$result = static::Query($query, $args); $result = static::Query($query, $args);
@ -51,6 +56,8 @@ class Db{
* *
* @param string $query * @param string $query
* @param array<mixed> $args * @param array<mixed> $args
*
* @throws Exceptions\DatabaseQueryException If an error occurs during execution of the query.
*/ */
public static function QueryBool(string $query, array $args = []): bool{ public static function QueryBool(string $query, array $args = []): bool{
$result = static::Query($query, $args); $result = static::Query($query, $args);
@ -145,8 +152,8 @@ class Db{
* *
* @return array<T> An array of objects of type `$class`, or `stdClass` if `$class` is `null`. * @return array<T> An array of objects of type `$class`, or `stdClass` if `$class` is `null`.
* *
* @throws Exceptions\DuplicateDatabaseKeyException When a unique key constraint has been violated. * @throws Exceptions\DuplicateDatabaseKeyException If a unique key constraint has been violated.
* @throws Exceptions\DatabaseQueryException When an error occurs during execution of the query. * @throws Exceptions\DatabaseQueryException If an error occurs during execution of the query.
*/ */
public static function Query(string $sql, array $params = [], string $class = 'stdClass'): array{ public static function Query(string $sql, array $params = [], string $class = 'stdClass'): array{
$handle = static::PreparePdoHandle($sql, $params); $handle = static::PreparePdoHandle($sql, $params);
@ -202,7 +209,7 @@ class Db{
* @return array<T> An array of `$class`. * @return array<T> An array of `$class`.
* *
* @throws Exceptions\MultiSelectMethodNotFoundException If the class doesn't have a `FromMultiTableRow()` method. * @throws Exceptions\MultiSelectMethodNotFoundException If the class doesn't have a `FromMultiTableRow()` method.
* @throws Exceptions\DatabaseQueryException When an error occurs during execution of the query. * @throws Exceptions\DatabaseQueryException If an error occurs during execution of the query.
*/ */
public static function MultiTableSelect(string $sql, array $params, string $class): array{ public static function MultiTableSelect(string $sql, array $params, string $class): array{
if(!method_exists($class, 'FromMultiTableRow')){ if(!method_exists($class, 'FromMultiTableRow')){
@ -278,7 +285,7 @@ class Db{
* *
* @return array<array<string, stdClass>> An array of `$class` if `$class` is not `null`, otherwise an array of rows of the form `["LeftTableName" => $stdClass, "RightTableName" => $stdClass]`. * @return array<array<string, stdClass>> An array of `$class` if `$class` is not `null`, otherwise an array of rows of the form `["LeftTableName" => $stdClass, "RightTableName" => $stdClass]`.
* *
* @throws Exceptions\DatabaseQueryException When an error occurs during execution of the query. * @throws Exceptions\DatabaseQueryException If an error occurs during execution of the query.
*/ */
public static function MultiTableSelectGeneric(string $sql, array $params): array{ public static function MultiTableSelectGeneric(string $sql, array $params): array{
$handle = static::PreparePdoHandle($sql, $params); $handle = static::PreparePdoHandle($sql, $params);
@ -317,7 +324,7 @@ class Db{
* *
* @return \PDOStatement The `\PDOStatement` to be used to execute the query. * @return \PDOStatement The `\PDOStatement` to be used to execute the query.
* *
* @throws Exceptions\DatabaseQueryException When an error occurs during execution of the query. * @throws Exceptions\DatabaseQueryException If an error occurs during execution of the query.
*/ */
protected static function PreparePdoHandle(string $sql, array $params): \PDOStatement{ protected static function PreparePdoHandle(string $sql, array $params): \PDOStatement{
try{ try{
@ -365,7 +372,7 @@ class Db{
* *
* @return array<T> An array of objects of type `$class`, or `stdClass` if `$class` is `null`. * @return array<T> An array of objects of type `$class`, or `stdClass` if `$class` is `null`.
* *
* @throws \PDOException When an error occurs during execution of the query. * @throws \PDOException If an error occurs during execution of the query.
*/ */
protected static function ExecuteQuery(\PDOStatement $handle, string $class = 'stdClass'): array{ protected static function ExecuteQuery(\PDOStatement $handle, string $class = 'stdClass'): array{
$handle->execute(); $handle->execute();
@ -445,7 +452,7 @@ class Db{
* *
* @return array<T>|array<array<string, stdClass>> An array of `$class` if `$class` is not `stdClass`, otherwise an array of rows of the form `["LeftTableName" => $stdClass, "RightTableName" => $stdClass]`. * @return array<T>|array<array<string, stdClass>> An array of `$class` if `$class` is not `stdClass`, otherwise an array of rows of the form `["LeftTableName" => $stdClass, "RightTableName" => $stdClass]`.
* *
* @throws \PDOException When an error occurs during execution of the query. * @throws \PDOException If an error occurs during execution of the query.
*/ */
protected static function ExecuteMultiTableSelect(\PDOStatement $handle, string $class): array{ protected static function ExecuteMultiTableSelect(\PDOStatement $handle, string $class): array{
$handle->execute(); $handle->execute();
@ -569,29 +576,6 @@ class Db{
} }
} }
/**
* Get the ID of the last row that was inserted during this database connection.
*
* @return int The ID of the last row that was inserted during this database connection.
*
* @throws Exceptions\DatabaseQueryException When the last inserted ID can't be determined.
*/
public static function GetLastInsertedId(): int{
try{
$id = static::$Link->lastInsertId();
}
catch(\PDOException){
$id = false;
}
if($id === false || $id == '0'){
throw new Exceptions\DatabaseQueryException('Couldn\'t get last insert ID.');
}
else{
return intval($id);
}
}
/** /**
* Create a detailed `Exceptions\DatabaseQueryException` from a `\PDOException`. * Create a detailed `Exceptions\DatabaseQueryException` from a `\PDOException`.
* *

View file

@ -1777,7 +1777,7 @@ final class Ebook{
throw $error; throw $error;
} }
Db::Query(' $this->EbookId = Db::QueryInt('
INSERT into Ebooks (Identifier, WwwFilesystemPath, RepoFilesystemPath, KindleCoverUrl, EpubUrl, INSERT into Ebooks (Identifier, WwwFilesystemPath, RepoFilesystemPath, KindleCoverUrl, EpubUrl,
AdvancedEpubUrl, KepubUrl, Azw3Url, DistCoverUrl, Title, FullTitle, AlternateTitle, AdvancedEpubUrl, KepubUrl, Azw3Url, DistCoverUrl, Title, FullTitle, AlternateTitle,
Description, LongDescription, Language, WordCount, ReadingEase, GitHubUrl, WikipediaUrl, Description, LongDescription, Language, WordCount, ReadingEase, GitHubUrl, WikipediaUrl,
@ -1808,6 +1808,7 @@ final class Ebook{
?, ?,
?, ?,
?) ?)
returning EbookId
', [$this->Identifier, $this->WwwFilesystemPath, $this->RepoFilesystemPath, $this->KindleCoverUrl, $this->EpubUrl, ', [$this->Identifier, $this->WwwFilesystemPath, $this->RepoFilesystemPath, $this->KindleCoverUrl, $this->EpubUrl,
$this->AdvancedEpubUrl, $this->KepubUrl, $this->Azw3Url, $this->DistCoverUrl, $this->Title, $this->AdvancedEpubUrl, $this->KepubUrl, $this->Azw3Url, $this->DistCoverUrl, $this->Title,
$this->FullTitle, $this->AlternateTitle, $this->Description, $this->LongDescription, $this->FullTitle, $this->AlternateTitle, $this->Description, $this->LongDescription,
@ -1815,8 +1816,6 @@ final class Ebook{
$this->EbookCreated, $this->EbookUpdated, $this->TextSinglePageByteCount, $this->IndexableText, $this->EbookCreated, $this->EbookUpdated, $this->TextSinglePageByteCount, $this->IndexableText,
$this->IndexableAuthors, $this->IndexableCollections]); $this->IndexableAuthors, $this->IndexableCollections]);
$this->EbookId = Db::GetLastInsertedId();
try{ try{
$this->AddTags(); $this->AddTags();
$this->AddLocSubjects(); $this->AddLocSubjects();

View file

@ -56,13 +56,13 @@ class EbookTag extends Tag{
public function Create(): void{ public function Create(): void{
$this->Validate(); $this->Validate();
Db::Query(' $this->TagId = Db::QueryInt('
INSERT into Tags (Name, UrlName, Type) INSERT into Tags (Name, UrlName, Type)
values (?, values (?,
?, ?,
?) ?)
returning TagId
', [$this->Name, $this->UrlName, $this->Type]); ', [$this->Name, $this->UrlName, $this->Type]);
$this->TagId = Db::GetLastInsertedId();
} }

View file

@ -39,11 +39,11 @@ class LocSubject{
public function Create(): void{ public function Create(): void{
$this->Validate(); $this->Validate();
Db::Query(' $this->LocSubjectId = Db::QueryInt('
INSERT into LocSubjects (Name) INSERT into LocSubjects (Name)
values (?) values (?)
returning LocSubjectId
', [$this->Name]); ', [$this->Name]);
$this->LocSubjectId = Db::GetLastInsertedId();
} }
/** /**

View file

@ -95,7 +95,7 @@ class Payment{
} }
try{ try{
Db::Query(' $this->PaymentId = Db::QueryInt('
INSERT into Payments (UserId, Created, Processor, TransactionId, Amount, Fee, IsRecurring, IsMatchingDonation) INSERT into Payments (UserId, Created, Processor, TransactionId, Amount, Fee, IsRecurring, IsMatchingDonation)
values(?, values(?,
?, ?,
@ -105,12 +105,11 @@ class Payment{
?, ?,
?, ?,
?) ?)
returning PaymentId
', [$this->UserId, $this->Created, $this->Processor, $this->TransactionId, $this->Amount, $this->Fee, $this->IsRecurring, $this->IsMatchingDonation]); ', [$this->UserId, $this->Created, $this->Processor, $this->TransactionId, $this->Amount, $this->Fee, $this->IsRecurring, $this->IsMatchingDonation]);
} }
catch(Exceptions\DuplicateDatabaseKeyException){ catch(Exceptions\DuplicateDatabaseKeyException){
throw new Exceptions\PaymentExistsException(); throw new Exceptions\PaymentExistsException();
} }
$this->PaymentId = Db::GetLastInsertedId();
} }
} }

View file

@ -347,7 +347,7 @@ final class Project{
throw new Exceptions\ProjectExistsException(); throw new Exceptions\ProjectExistsException();
} }
Db::Query(' $this->ProjectId = Db::QueryInt('
INSERT into Projects INSERT into Projects
( (
EbookId, EbookId,
@ -384,10 +384,9 @@ final class Project{
?, ?,
? ?
) )
returning ProjectId
', [$this->EbookId, $this->Status, $this->ProducerName, $this->ProducerEmail, $this->DiscussionUrl, $this->VcsUrl, NOW, NOW, $this->Started, $this->Ended, $this->ManagerUserId, $this->ReviewerUserId, $this->LastCommitTimestamp, $this->LastDiscussionTimestamp, $this->IsStatusAutomaticallyUpdated]); ', [$this->EbookId, $this->Status, $this->ProducerName, $this->ProducerEmail, $this->DiscussionUrl, $this->VcsUrl, NOW, NOW, $this->Started, $this->Ended, $this->ManagerUserId, $this->ReviewerUserId, $this->LastCommitTimestamp, $this->LastDiscussionTimestamp, $this->IsStatusAutomaticallyUpdated]);
$this->ProjectId = Db::GetLastInsertedId();
// Notify the manager and reviewer. // Notify the manager and reviewer.
if($this->Status == Enums\ProjectStatusType::InProgress){ if($this->Status == Enums\ProjectStatusType::InProgress){
// The manager is also the reviewer, just send one email. // The manager is also the reviewer, just send one email.

View file

@ -251,20 +251,19 @@ class User{
} }
try{ try{
Db::Query(' $this->UserId = Db::QueryInt('
INSERT into Users (Email, Name, Uuid, Created, PasswordHash) INSERT into Users (Email, Name, Uuid, Created, PasswordHash)
values (?, values (?,
?, ?,
?, ?,
?, ?,
?) ?)
returning UserId
', [$this->Email, $this->Name, $this->Uuid, $this->Created, $this->PasswordHash]); ', [$this->Email, $this->Name, $this->Uuid, $this->Created, $this->PasswordHash]);
} }
catch(Exceptions\DuplicateDatabaseKeyException){ catch(Exceptions\DuplicateDatabaseKeyException){
throw new Exceptions\UserExistsException(); throw new Exceptions\UserExistsException();
} }
$this->UserId = Db::GetLastInsertedId();
} }
/** /**