mirror of
https://github.com/standardebooks/web.git
synced 2025-07-08 07:40:39 -04:00
Use 'insert ... returning' instead of 'Db::GetLastInsertedId()'
This commit is contained in:
parent
4446b6d161
commit
5066252355
12 changed files with 44 additions and 57 deletions
|
@ -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()`.
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
46
lib/Db.php
46
lib/Db.php
|
@ -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`.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue