added database seeder 🔥🎉

The db:seed command invokes the root seeder DatabaseSeeder which can
invoke another seeders using the call() related methods. The root seeder
can be set by the --class command-line option and also by the --seeder
cmd. line option on some other commands like migrate:fresh/refresh.

Seeders can be passed to the Tom application in the similar way like the
migrations using the TomApplication::seeders<>() method or through the
constructor.

Arguments can be passed to the call<>() method, then the seeders run()
method will not be called using the virtual dispatch but using the type
traits. Of course the arguments passed to the call<>() method has to
match with the run() method parameters.

Others:

 - unified usingConnection() across all commands, this functionality was
   extracted to own class, previous it was a part of the Migrator class,
   so now every command even if it doesn't use Migrator can call
   the usingConnection() and it correctly sets a default connection and
   SQL queries debugging on the base of the -vvv command-line argument
 - a default database connection is now required, if not set then
   the exception will be thrown
 - added example seeders to the Tom application
This commit is contained in:
silverqx
2022-05-16 13:08:33 +02:00
parent 1b3b431873
commit d4557fc705
41 changed files with 1152 additions and 203 deletions

View File

@@ -253,6 +253,7 @@ function(tinytom_sources out_headers out_sources)
list(APPEND headers
application.hpp
commands/command.hpp
commands/database/seedcommand.hpp
commands/database/wipecommand.hpp
commands/environmentcommand.hpp
commands/helpcommand.hpp
@@ -274,6 +275,7 @@ function(tinytom_sources out_headers out_sources)
concerns/guesscommandname.hpp
concerns/interactswithio.hpp
concerns/printsoptions.hpp
concerns/usingconnection.hpp
config.hpp
exceptions/invalidargumenterror.hpp
exceptions/invalidtemplateargumenterror.hpp
@@ -284,6 +286,7 @@ function(tinytom_sources out_headers out_sources)
migrationcreator.hpp
migrationrepository.hpp
migrator.hpp
seeder.hpp
tableguesser.hpp
terminal.hpp
tomconstants.hpp
@@ -304,6 +307,7 @@ function(tinytom_sources out_headers out_sources)
list(APPEND sources
application.cpp
commands/command.cpp
commands/database/seedcommand.cpp
commands/database/wipecommand.cpp
commands/environmentcommand.cpp
commands/helpcommand.cpp
@@ -323,11 +327,13 @@ function(tinytom_sources out_headers out_sources)
concerns/guesscommandname.cpp
concerns/interactswithio.cpp
concerns/printsoptions.cpp
concerns/usingconnection.cpp
exceptions/tomlogicerror.cpp
exceptions/tomruntimeerror.cpp
migrationcreator.cpp
migrationrepository.cpp
migrator.cpp
seeder.cpp
tableguesser.cpp
terminal.cpp
tomutils.cpp

View File

@@ -1,11 +1,16 @@
# Migrations header files
# Migrations and Seeders header files
# ---
# Tests' migrations as example migrations
# Tom example migrations
include($$TINYORM_SOURCE_TREE/tests/database/migrations.pri)
# Or include yours migrations
#include(/home/xyz/your_project/database/migrations.pri)
# Tom example seeders
include($$TINYORM_SOURCE_TREE/tests/database/seeders.pri)
# Or include yours seeders
#include(/home/xyz/your_project/database/seeders.pri)
# Dependencies include and library paths
# ---

View File

@@ -7,6 +7,8 @@
#include "migrations/2014_10_12_200000_create_properties_table.hpp"
#include "migrations/2014_10_12_300000_create_phones_table.hpp"
#include "seeders/databaseseeder.hpp"
using Orm::Constants::EMPTY;
using Orm::Constants::H127001;
using Orm::Constants::P3306;
@@ -37,6 +39,7 @@ using Orm::DB;
using TomApplication = Tom::Application;
using namespace Migrations; // NOLINT(google-build-using-namespace)
using namespace Seeders; // NOLINT(google-build-using-namespace)
/*! Build the database manager instance and add a database connection. */
std::shared_ptr<DatabaseManager> setupManager();
@@ -63,6 +66,14 @@ int main(int argc, char *argv[])
AddFactorColumnToPostsTable,
CreatePropertiesTable,
CreatePhonesTable>()
/* Seeder classes, the DatabaseSeeder is the default/root seeder class,
it must always exist if the --class command-line argument is not
provided, or you can provide a custom name through the --class
argument.
The order of seeder classes doesn't matter, they will be called
in the order defined by the call<>() method inside the seeders
themselves. */
.seeders<DatabaseSeeder>()
// Fire it up 🔥🚀✨
.run();

View File

@@ -0,0 +1,6 @@
INCLUDEPATH += $$PWD
HEADERS += \
$$PWD/seeders/databaseseeder.hpp \
$$PWD/seeders/phonesseeder.hpp \
$$PWD/seeders/propertiesseeder.hpp \

View File

@@ -0,0 +1,31 @@
#pragma once
#include <tom/seeder.hpp>
#include "seeders/phonesseeder.hpp"
#include "seeders/propertiesseeder.hpp"
/* This class serves as a showcase, so all possible features are defined / used. */
namespace Seeders
{
/*! Main database seeder. */
struct DatabaseSeeder : Seeder
{
/*! Run the database seeders. */
void run() override
{
DB::table("posts")->insert({
{{"name", "1. post"}, {"factor", 10}},
{{"name", "2. post"}, {"factor", 20}},
});
call<PhonesSeeder, PropertiesSeeder>();
// You can also pass arguments to the call() related methods
// callWith<UsersSeeder>(shouldSeedPasswd);
}
};
} // namespace Seeders

View File

@@ -0,0 +1,37 @@
#pragma once
#ifndef TINYORM_DISABLE_ORM
# include <orm/tiny/model.hpp>
#endif
#include <tom/seeder.hpp>
#ifndef TINYORM_DISABLE_ORM
namespace Models
{
class Phone final : public Orm::Tiny::Model<Phone>
{};
}
#endif
namespace Seeders
{
struct PhonesSeeder : Seeder
{
/*! Run the database seeders. */
void run() override
{
#ifdef TINYORM_DISABLE_ORM
DB::table("phones")->insert({
{{"name", "1. phone"}},
{{"name", "2. phone"}},
});
#else
// This tests GuardedModel::unguarded()
Models::Phone::create({{"name", QDateTime::currentDateTime()}});
#endif
}
};
} // namespace Seeders

View File

@@ -0,0 +1,20 @@
#pragma once
#include <tom/seeder.hpp>
namespace Seeders
{
struct PropertiesSeeder : Seeder
{
/*! Run the database seeders. */
void run() override
{
DB::table("properties")->insert({
{{"name", "1. property"}},
{{"name", "2. property"}},
});
}
};
} // namespace Seeders

View File

@@ -10,6 +10,7 @@ else: \
headersList += \
$$PWD/tom/application.hpp \
$$PWD/tom/commands/command.hpp \
$$PWD/tom/commands/database/seedcommand.hpp \
$$PWD/tom/commands/database/wipecommand.hpp \
$$PWD/tom/commands/environmentcommand.hpp \
$$PWD/tom/commands/helpcommand.hpp \
@@ -31,6 +32,7 @@ headersList += \
$$PWD/tom/concerns/guesscommandname.hpp \
$$PWD/tom/concerns/interactswithio.hpp \
$$PWD/tom/concerns/printsoptions.hpp \
$$PWD/tom/concerns/usingconnection.hpp \
$$PWD/tom/config.hpp \
$$PWD/tom/exceptions/invalidargumenterror.hpp \
$$PWD/tom/exceptions/invalidtemplateargumenterror.hpp \
@@ -41,6 +43,7 @@ headersList += \
$$PWD/tom/migrationcreator.hpp \
$$PWD/tom/migrationrepository.hpp \
$$PWD/tom/migrator.hpp \
$$PWD/tom/seeder.hpp \
$$PWD/tom/tableguesser.hpp \
$$PWD/tom/terminal.hpp \
$$PWD/tom/tomconstants.hpp \

View File

@@ -23,6 +23,7 @@ TINYORM_BEGIN_COMMON_NAMESPACE
namespace Orm
{
class ConnectionResolverInterface;
class DatabaseManager;
}
@@ -43,6 +44,7 @@ namespace Concerns
class Migration;
class MigrationRepository;
class Migrator;
class Seeder;
/*! Tom application. */
class SHAREDLIB_EXPORT Application : public Concerns::InteractsWithIO,
@@ -63,6 +65,8 @@ namespace Concerns
// To access createCommandsVector(), errorWall(), exitApplication()
friend Concerns::GuessCommandName;
/*! Alias for the ConnectionResolverInterface. */
using ConnectionResolverInterface = Orm::ConnectionResolverInterface;
/*! Alias for the DatabaseManager. */
using DatabaseManager = Orm::DatabaseManager;
@@ -71,14 +75,19 @@ namespace Concerns
Application(int &argc, char **argv, std::shared_ptr<DatabaseManager> db,
const char *environmentEnvName = "TOM_ENV",
QString migrationTable = QStringLiteral("migrations"),
std::vector<std::shared_ptr<Migration>> migrations = {});
std::vector<std::shared_ptr<Migration>> migrations = {},
std::vector<std::shared_ptr<Seeder>> seeders = {});
/*! Virtual destructor. */
inline ~Application() override = default;
/*! Instantiate/initialize all migration classes. */
template<typename ...M>
template<typename ...Migrations>
Application &migrations();
/*! Instantiate/initialize all seeder classes. */
template<typename ...Seeders>
Application &seeders();
/*! Run the tom application. */
int run();
@@ -109,6 +118,13 @@ namespace Concerns
/*! Get the default migrations path used by the make:migration command. */
inline const fspath &getMigrationsPath() const noexcept;
/*! Get a reference to the all migrations instances. */
inline const std::vector<std::shared_ptr<Migration>> &
getMigrations() const noexcept;
/*! Get a reference to the all seeders instances. */
inline const std::vector<std::shared_ptr<Seeder>> &
getSeeders() const noexcept;
#ifdef TINYTOM_TESTS_CODE
/*! Alias for the test output row from the status command. */
using StatusRow = std::vector<std::string>;
@@ -213,6 +229,12 @@ namespace Concerns
/*! Initialize the migrations path (prepend pwd and make_prefered). */
static fspath initializeMigrationsPath(fspath &&path);
/*! Get database connection resolver. */
std::shared_ptr<ConnectionResolverInterface> resolver() const noexcept;
/*! Throw if a default connection is empty. */
void throwIfEmptyDefaultConnection() const;
/*! Current application argc. */
int &m_argc;
/*! Current application argv. */
@@ -254,6 +276,9 @@ namespace Concerns
std::unordered_map<std::type_index,
MigrationProperties> m_migrationsProperties {};
/*! Seeders vector to process. */
std::vector<std::shared_ptr<Seeder>> m_seeders;
/*! Is this input means interactive? */
bool m_interactive = true;
@@ -280,15 +305,22 @@ namespace Concerns
/* Helps to avoid declaring the FileName, connection, and withinTransaction as
getters in migrations, I can tell that this is really crazy. 🤪🙃😎 */
m_migrationsProperties = {
{typeid (Migrations),
{Migrations::FileName,
Migrations().connection,
Migrations().withinTransaction}}...
{typeid (Migrations), {Migrations::FileName,
Migrations().connection,
Migrations().withinTransaction}}...
};
return *this;
}
template<typename ...Seeders>
Application &Application::seeders()
{
m_seeders = {std::make_shared<Seeders>()...};
return *this;
}
/* Getters / Setters */
const QString &Application::environment() const noexcept
@@ -323,6 +355,18 @@ namespace Concerns
return m_migrationsPath;
}
const std::vector<std::shared_ptr<Migration>> &
Application::getMigrations() const noexcept
{
return m_migrations;
}
const std::vector<std::shared_ptr<Seeder>> &
Application::getSeeders() const noexcept
{
return m_seeders;
}
/* protected */
auto Application::getCommandsInNamespace(const QString &name)

View File

@@ -14,6 +14,7 @@ class QCommandLineOption;
namespace Orm
{
class ConnectionResolverInterface;
class DatabaseConnection;
}
@@ -41,6 +42,9 @@ namespace Tom::Commands
{
Q_DISABLE_COPY(Command)
/*! Alias for the ConnectionResolverInterface. */
using ConnectionResolverInterface = Orm::ConnectionResolverInterface;
public:
/*! Constructor. */
Command(Application &application, QCommandLineParser &parser);
@@ -96,17 +100,21 @@ namespace Tom::Commands
/*! Check whether a positional argument at the given index was set. */
bool hasArgument(ArgumentsSizeType index) const;
/*! Check whether a positional argument by the given name was set. */
bool hasArgument(const QString &name) const;
/*! Get a list of positional arguments. */
QStringList arguments() const;
/*! Get a positional argument at the given index position. */
QString argument(ArgumentsSizeType index) const;
QString argument(ArgumentsSizeType index, bool useDefault = true) const;
/*! Get a positional argument by the given name. */
QString argument(const QString &name) const;
QString argument(const QString &name, bool useDefault = true) const;
/*! Get a database connection. */
Orm::DatabaseConnection &connection(const QString &name) const;
/*! Get a command-line parser. */
QCommandLineParser &parser() const noexcept;
/*! Get database connection resolver. */
std::shared_ptr<ConnectionResolverInterface> resolver() const noexcept;
/*! Reference to the tom application. */
std::reference_wrapper<Application> m_application;

View File

@@ -0,0 +1,98 @@
#pragma once
#ifndef TOM_COMMANDS_DATABASE_SEEDCOMMAND_HPP
#define TOM_COMMANDS_DATABASE_SEEDCOMMAND_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include "tom/commands/command.hpp"
#include "tom/concerns/confirmable.hpp"
#include "tom/concerns/usingconnection.hpp"
#include "tom/tomconstants.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom
{
class Seeder;
namespace Commands::Database
{
/*! Drop all tables, views, and types. */
class SeedCommand : public Command,
public Concerns::Confirmable,
public Concerns::UsingConnection
{
Q_DISABLE_COPY(SeedCommand)
/*! Alias for the Command. */
using Command = Commands::Command;
/*! Alias for the ConnectionResolverInterface. */
using ConnectionResolverInterface = Orm::ConnectionResolverInterface;
public:
/*! Constructor. */
SeedCommand(Application &application, QCommandLineParser &parser,
std::shared_ptr<ConnectionResolverInterface> &&resolver);
/*! Virtual destructor. */
inline ~SeedCommand() override = default;
/*! The console command name. */
inline QString name() const override;
/*! The console command description. */
inline QString description() const override;
/*! The console command positional arguments signature. */
const std::vector<PositionalArgument> &positionalArguments() const override;
/*! The signature of the console command. */
QList<QCommandLineOption> optionsSignature() const override;
/*! Execute the console command. */
int run() override;
protected:
/*! Result of the getSeeder(). */
struct GetSeederResult
{
/*! Root seeder name. */
QString name;
/*! Reference to the root seeder. */
std::reference_wrapper<Seeder> seeder;
};
/*! Get a seeder instance. */
GetSeederResult getSeeder() const;
/*! Get a reference to the all seeder instances. */
const std::vector<std::shared_ptr<Seeder>> &seeders() const noexcept;
/*! The database connection resolver instance. */
std::shared_ptr<ConnectionResolverInterface> m_resolver;
private:
/*! Default name of the root database seeder. */
static const QString DatabaseSeeder;
/*! Throw if the root seeder is not defined. */
void throwIfDoesntContainSeeder(const QString &seederClass) const;
};
/* public */
QString SeedCommand::name() const
{
return Constants::DbSeed;
}
QString SeedCommand::description() const
{
return QStringLiteral("Seed the database with records");
}
} // namespace Commands::Database
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_COMMANDS_DATABASE_SEEDCOMMAND_HPP

View File

@@ -7,6 +7,7 @@ TINY_SYSTEM_HEADER
#include "tom/commands/command.hpp"
#include "tom/concerns/confirmable.hpp"
#include "tom/concerns/usingconnection.hpp"
#include "tom/tomconstants.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
@@ -16,7 +17,8 @@ namespace Tom::Commands::Database
/*! Drop all tables, views, and types. */
class WipeCommand : public Command,
public Concerns::Confirmable
public Concerns::Confirmable,
public Concerns::UsingConnection
{
Q_DISABLE_COPY(WipeCommand)
@@ -41,9 +43,6 @@ namespace Tom::Commands::Database
int run() override;
protected:
/*! Set the debug sql for the current connection. */
void setConnectionDebugSql(const QString &connectionName) const;
/*! Drop all of the database tables. */
void dropAllTables(const QString &database) const;
/*! Drop all of the database views. */

View File

@@ -47,9 +47,9 @@ namespace Commands::Migrations
protected:
/*! Determine if the developer has requested database seeding. */
// bool needsSeeding() const;
bool needsSeeding() const;
/*! Run the database seeder command. */
// void runSeeder(QString &&databaseCmd) const;
void runSeeder(QString &&databaseCmd) const;
/*! The migrator service instance. */
std::shared_ptr<Migrator> m_migrator;

View File

@@ -6,6 +6,7 @@
TINY_SYSTEM_HEADER
#include "tom/commands/command.hpp"
#include "tom/concerns/usingconnection.hpp"
#include "tom/tomconstants.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
@@ -18,7 +19,8 @@ namespace Commands::Migrations
{
/*! Create the migration database repository. */
class InstallCommand : public Command
class InstallCommand : public Command,
public Concerns::UsingConnection
{
Q_DISABLE_COPY(InstallCommand)

View File

@@ -7,6 +7,7 @@ TINY_SYSTEM_HEADER
#include "tom/commands/command.hpp"
#include "tom/concerns/confirmable.hpp"
#include "tom/concerns/usingconnection.hpp"
#include "tom/tomconstants.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
@@ -20,7 +21,8 @@ namespace Commands::Migrations
/*! Run the database migrations up/down. */
class MigrateCommand : public Command,
public Concerns::Confirmable
public Concerns::Confirmable,
public Concerns::UsingConnection
{
Q_DISABLE_COPY(MigrateCommand)
@@ -52,9 +54,9 @@ namespace Commands::Migrations
void loadSchemaState() const;
/*! Determine if the developer has requested database seeding. */
// bool needsSeeding() const;
bool needsSeeding() const;
/*! Run the database seeder command. */
// void runSeeder() const;
void runSeeder() const;
/*! The migrator service instance. */
std::shared_ptr<Migrator> m_migrator;

View File

@@ -7,6 +7,7 @@ TINY_SYSTEM_HEADER
#include "tom/commands/command.hpp"
#include "tom/concerns/confirmable.hpp"
#include "tom/concerns/usingconnection.hpp"
#include "tom/tomconstants.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
@@ -20,7 +21,8 @@ namespace Commands::Migrations
/*! Rollback the last database migration. */
class ResetCommand : public Command,
public Concerns::Confirmable
public Concerns::Confirmable,
public Concerns::UsingConnection
{
Q_DISABLE_COPY(ResetCommand)

View File

@@ -7,6 +7,7 @@ TINY_SYSTEM_HEADER
#include "tom/commands/command.hpp"
#include "tom/concerns/confirmable.hpp"
#include "tom/concerns/usingconnection.hpp"
#include "tom/tomconstants.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
@@ -20,7 +21,8 @@ namespace Commands::Migrations
/*! Rollback the last database migration. */
class RollbackCommand : public Command,
public Concerns::Confirmable
public Concerns::Confirmable,
public Concerns::UsingConnection
{
Q_DISABLE_COPY(RollbackCommand)

View File

@@ -14,6 +14,7 @@ TINY_SYSTEM_HEADER
#endif
#include "tom/commands/command.hpp"
#include "tom/concerns/usingconnection.hpp"
#include "tom/tomconstants.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
@@ -26,7 +27,8 @@ namespace Commands::Migrations
{
/*! Show the status of each migration. */
class StatusCommand : public Command
class StatusCommand : public Command,
public Concerns::UsingConnection
{
Q_DISABLE_COPY(StatusCommand)

View File

@@ -0,0 +1,99 @@
#pragma once
#ifndef TOM_CONCERNS_USINGCONNECTION_HPP
#define TOM_CONCERNS_USINGCONNECTION_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include <QStringList>
#include <optional>
#include <orm/macros/commonnamespace.hpp>
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Orm
{
class ConnectionResolverInterface;
class DatabaseConnection;
}
namespace Tom
{
class MigrationRepository;
namespace Concerns
{
/*! Invoke the given callback inside the defined connection and restore connection
to the previous state after finish. */
class UsingConnection
{
Q_DISABLE_COPY(UsingConnection)
/*! Alias for the ConnectionResolverInterface. */
using ConnectionResolverInterface = Orm::ConnectionResolverInterface;
/*! Alias for the DatabaseConnection. */
using DatabaseConnection = Orm::DatabaseConnection;
public:
/*! Constructor. */
explicit UsingConnection(std::shared_ptr<ConnectionResolverInterface> &&resolver);
/*! Default destructor. */
inline ~UsingConnection() = default;
/*! Execute the given callback using the given connection as the default
connection. */
int usingConnection(QString name, bool debugSql,
std::optional<std::reference_wrapper<
MigrationRepository>> repository,
std::function<int()> &&callback);
/*! Execute the given callback using the given connection as the default
connection. */
int usingConnection(QString &&name, bool debugSql,
std::function<int()> &&callback);
/*! Execute the given callback using the given connection as the default
connection. */
int usingConnection(const QString &name, bool debugSql,
std::function<int()> &&callback);
/*! Resolve the database connection instance. */
DatabaseConnection &resolveConnection(const QString &name = "") const;
/* Getters / Setters */
/*! Get the currently used connection name. */
inline const QString &getConnectionName() const noexcept;
private:
/*! Set the default connection name. */
void setConnection(
QString &&name, std::optional<bool> &&debugSql,
std::optional<std::reference_wrapper<
MigrationRepository>> repository = std::nullopt,
bool restore = false);
/*! Get the debug sql by the connection name. */
std::optional<bool> getConnectionDebugSql(const QString &name) const;
/*! Set the debug sql for the current repository connection. */
void setConnectionDebugSql(std::optional<bool> &&debugSql) const;
/*! The database connection resolver instance. */
std::shared_ptr<ConnectionResolverInterface> m_resolver = nullptr;
/*! The name of the database connection to use. */
QString m_connection {};
};
/* public */
const QString &UsingConnection::getConnectionName() const noexcept
{
return m_connection;
}
} // namespace Concerns
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_CONCERNS_USINGCONNECTION_HPP

View File

@@ -67,9 +67,9 @@ namespace Tom
void deleteRepository() const;
/*! Resolve the database connection instance. */
DatabaseConnection &getConnection() const;
DatabaseConnection &connection() const;
/*! Set the connection name to use in the repository. */
void setConnection(const QString &name, std::optional<bool> &&debugSql);
inline void setConnection(const QString &name);
protected:
/*! Get a query builder for the migration table. */
@@ -78,9 +78,6 @@ namespace Tom
/*! Hydrate a vector of migration items from a raw QSqlQuery. */
std::vector<MigrationItem> hydrateMigrations(QSqlQuery &query) const;
/*! Set the debug sql for the current repository connection. */
void setConnectionDebugSql(std::optional<bool> &&debugSql) const;
/*! The database connection resolver instance. */
std::shared_ptr<ConnectionResolverInterface> m_resolver;
/*! The name of the migration table. */
@@ -89,6 +86,13 @@ namespace Tom
QString m_connection {};
};
/* public */
void MigrationRepository::setConnection(const QString &name)
{
m_connection = name;
}
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE

View File

@@ -60,12 +60,6 @@ namespace Tom
/*! Rolls all of the currently applied migrations back. */
std::vector<RollbackItem> reset(bool pretend = false) const;
/* Database connection related */
/*! Execute the given callback using the given connection as the default
connection. */
int usingConnection(QString &&name, bool debugSql,
std::function<int()> &&callback);
/* Proxies to MigrationRepository */
/*! Determine if the migration repository exists. */
bool repositoryExists() const;
@@ -73,10 +67,6 @@ namespace Tom
bool hasRunAnyMigrations() const;
/* Getters / Setters */
/*! Get the default connection name. */
inline const QString &getConnection() const noexcept;
/*! Set the default connection name. */
void setConnection(QString &&name, std::optional<bool> &&debugSql);
/*! Get the migration repository instance. */
inline MigrationRepository &repository() const noexcept;
/*! Get migration names list. */
@@ -86,8 +76,6 @@ namespace Tom
/* Database connection related */
/*! Resolve the database connection instance. */
DatabaseConnection &resolveConnection(const QString &name = "") const;
/*! Get the debug sql by the connection name. */
std::optional<bool> getConnectionDebugSql(const QString &name) const;
/* Migration instances lists and hashes */
/*! Create a map that maps migration names by migrations type-id (type_index). */
@@ -147,8 +135,6 @@ namespace Tom
std::shared_ptr<MigrationRepository> m_repository;
/*! The database connection resolver instance. */
std::shared_ptr<ConnectionResolverInterface> m_resolver;
/*! The name of the database connection to use. */
QString m_connection {};
/*! Reference to the migrations vector to process. */
std::reference_wrapper<
@@ -169,11 +155,6 @@ namespace Tom
/* public */
const QString &Migrator::getConnection() const noexcept
{
return m_connection;
}
MigrationRepository &Migrator::repository() const noexcept
{
return *m_repository;

246
tom/include/tom/seeder.hpp Normal file
View File

@@ -0,0 +1,246 @@
#pragma once
#ifndef TOM_SEEDER_HPP
#define TOM_SEEDER_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include <orm/db.hpp>
#include <orm/query/querybuilder.hpp>
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom
{
class Seeder;
namespace Concerns
{
class InteractsWithIO;
}
/*! Concept for the derived Seeder class. */
template<typename T, typename ...Args>
concept SeederConcept = std::derived_from<T, Seeder>;
/*! Seeders base class. */
class SHAREDLIB_EXPORT Seeder
{
Q_DISABLE_COPY(Seeder)
/*! Alias for the InteractsWithIO. */
using InteractsWithIO = Concerns::InteractsWithIO;
public:
/*! Default constructor. */
inline Seeder() = default;
/*! Pure virtual destructor. */
inline virtual ~Seeder() = 0;
/*! Seed the application's database. */
inline virtual void run();
/*! Call operator overload. */
inline void operator()();
/*! Run the given seeder classes. */
template<SeederConcept ...T, typename ...Args>
void call(bool silent = false, Args &&...args);
/*! Run the given seeder classes, const version. */
template<SeederConcept ...T, typename ...Args>
void call(bool silent = false, Args &&...args) const;
/*! Run the given seeder classes. */
template<SeederConcept ...T, typename ...Args>
void callWith(Args &&...args);
/*! Run the given seeder classes, const version. */
template<SeederConcept ...T, typename ...Args>
void callWith(Args &&...args) const;
/*! Silently run the given seeder classes. */
template<SeederConcept ...T, typename ...Args>
void callSilent(Args &&...args);
/*! Silently run the given seeder classes, const version. */
template<SeederConcept ...T, typename ...Args>
void callSilent(Args &&...args) const;
/*! Set the console input/ouput. */
Seeder &setIO(const InteractsWithIO &io);
private:
/*! Run the given seeder classes. */
template<typename ...Args>
void callUnfolded(bool silent = false, Args &&...args);
/*! Run the given seeder classes, const version. */
template<typename ...Args>
void callUnfolded(bool silent = false, Args &&...args) const;
/*! Run the given seeder classes captured in the callback (helps to avoid
duplicates). */
void callInternal(bool silent, std::function<void()> &&callback) const;
/*! Reference to the IO. */
std::optional<
std::reference_wrapper<const InteractsWithIO>> m_io = std::nullopt;
};
/* public */
Seeder::~Seeder() = default;
void Seeder::run()
{}
void Seeder::operator()()
{
run();
}
template<SeederConcept ...T, typename ...Args>
void Seeder::call(const bool silent, Args &&...args)
{
(T().setIO(m_io.value())
.callUnfolded(silent, std::forward<Args>(args)...), ...);
}
template<SeederConcept ...T, typename ...Args>
void Seeder::call(const bool silent, Args &&...args) const
{
(std::add_const_t<T>().setIO(m_io.value())
.callUnfolded(silent, std::forward<Args>(args)...), ...);
}
template<SeederConcept ...T, typename ...Args>
void Seeder::callWith(Args &&...args)
{
call(false, std::forward<Args>(args)...);
}
template<SeederConcept ...T, typename ...Args>
void Seeder::callWith(Args &&...args) const
{
call(false, std::forward<Args>(args)...);
}
template<SeederConcept ...T, typename ...Args>
void Seeder::callSilent(Args &&...args)
{
call(true, std::forward<Args>(args)...);
}
template<SeederConcept ...T, typename ...Args>
void Seeder::callSilent(Args &&...args) const
{
call(true, std::forward<Args>(args)...);
}
/* private */
template<typename ...Args>
void Seeder::callUnfolded(const bool silent, Args &&...args)
{
callInternal(silent, [this, ...args = std::forward<Args>(args)]() mutable
{
run(std::forward<Args>(args)...);
});
}
template<typename ...Args>
void Seeder::callUnfolded(const bool silent, Args &&...args) const
{
callInternal(silent, [this, ...args = std::forward<Args>(args)]() mutable
{
run(std::forward<Args>(args)...);
});
}
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE
// Predefine some aliases so the user doesn't have to
namespace Seeders
{
/*! Alias for the DB facade. */
using TINYORM_COMMON_NAMESPACE::Orm::DB; // NOLINT(misc-unused-using-decls)
/*! Alias for the Tom Seeder. */
using TINYORM_COMMON_NAMESPACE::Tom::Seeder; // NOLINT(misc-unused-using-decls)
// Aliases for the most used string constants
/*! Alias for the string constant "id". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::ID; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "name". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::NAME; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "size". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::SIZE_; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "created_at". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::CREATED_AT; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "updated_at". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::UPDATED_AT; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "MySQL". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::MYSQL_; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "PostgreSQL". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::POSTGRESQL; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "SQLite". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::SQLITE; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "driver". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::driver_; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "host". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::host_; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "port". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::port_; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "database". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::database_; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "schema". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::schema_; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "username". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::username_; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "password". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::password_; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "charset". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::charset_; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "collation". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::collation_; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "timezone". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::timezone_; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "prefix". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::prefix_; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "options". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::options_; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "strict". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::strict_; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "engine". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::engine_; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "127.0.0.1". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::H127001; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "localhost". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::LOCALHOST; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "3306". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::P3306; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "5432". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::P5432; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "root". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::ROOT; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "UTC". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::UTC; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "LOCAL". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::LOCAL; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "SYSTEM". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::SYSTEM; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "public". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::PUBLIC; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "utf8". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::UTF8; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "utf8mb4". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::UTF8MB4; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "InnoDB". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::InnoDB; // NOLINT(misc-unused-using-decls)
/*! Alias for the string constant "MyISAM". */
using TINYORM_COMMON_NAMESPACE::Orm::Constants::MyISAM; // NOLINT(misc-unused-using-decls)
} // namespace Seeders
#endif // TOM_SEEDER_HPP

View File

@@ -48,12 +48,18 @@ namespace Tom::Constants
// Used by more commands
SHAREDLIB_EXPORT extern const QString force;
SHAREDLIB_EXPORT extern const QString pretend;
SHAREDLIB_EXPORT extern const QString seed;
SHAREDLIB_EXPORT extern const QString seeder;
SHAREDLIB_EXPORT extern const QString step_;
// Default value names
SHAREDLIB_EXPORT extern const QString class_up;
SHAREDLIB_EXPORT extern const QString database_up;
SHAREDLIB_EXPORT extern const QString seeder_up;
SHAREDLIB_EXPORT extern const QString step_up;
// list
SHAREDLIB_EXPORT extern const QString raw_;
// db:seed
SHAREDLIB_EXPORT extern const QString class_;
// db:wipe
SHAREDLIB_EXPORT extern const QString drop_views;
SHAREDLIB_EXPORT extern const QString drop_types;

View File

@@ -47,12 +47,18 @@ namespace Tom::Constants
// Used by more commands
inline const QString force = QStringLiteral("force");
inline const QString pretend = QStringLiteral("pretend");
inline const QString seed = QStringLiteral("seed");
inline const QString seeder = QStringLiteral("seeder");
inline const QString step_ = QStringLiteral("step");
// Default value names
inline const QString class_up = QStringLiteral("CLASS");
inline const QString database_up = QStringLiteral("DATABASE");
inline const QString seeder_up = QStringLiteral("SEEDER");
inline const QString step_up = QStringLiteral("STEP");
// list
inline const QString raw_ = QStringLiteral("raw");
// db:seed
inline const QString class_ = QStringLiteral("class");
// db:wipe
inline const QString drop_views = QStringLiteral("drop-views");
inline const QString drop_types = QStringLiteral("drop-types");

View File

@@ -5,6 +5,7 @@ extern_constants: \
sourcesList += \
$$PWD/tom/application.cpp \
$$PWD/tom/commands/command.cpp \
$$PWD/tom/commands/database/seedcommand.cpp \
$$PWD/tom/commands/database/wipecommand.cpp \
$$PWD/tom/commands/environmentcommand.cpp \
$$PWD/tom/commands/helpcommand.cpp \
@@ -24,11 +25,13 @@ sourcesList += \
$$PWD/tom/concerns/guesscommandname.cpp \
$$PWD/tom/concerns/interactswithio.cpp \
$$PWD/tom/concerns/printsoptions.cpp \
$$PWD/tom/concerns/usingconnection.cpp \
$$PWD/tom/exceptions/tomlogicerror.cpp \
$$PWD/tom/exceptions/tomruntimeerror.cpp \
$$PWD/tom/migrationcreator.cpp \
$$PWD/tom/migrationrepository.cpp \
$$PWD/tom/migrator.cpp \
$$PWD/tom/seeder.cpp \
$$PWD/tom/tableguesser.cpp \
$$PWD/tom/terminal.cpp \
$$PWD/tom/tomutils.cpp \

View File

@@ -11,6 +11,7 @@
#include <orm/utils/type.hpp>
#include <orm/version.hpp>
#include "tom/commands/database/seedcommand.hpp"
#include "tom/commands/database/wipecommand.hpp"
#include "tom/commands/environmentcommand.hpp"
#include "tom/commands/helpcommand.hpp"
@@ -25,6 +26,7 @@
#include "tom/commands/migrations/resetcommand.hpp"
#include "tom/commands/migrations/rollbackcommand.hpp"
#include "tom/commands/migrations/statuscommand.hpp"
#include "tom/exceptions/runtimeerror.hpp"
#include "tom/migrationrepository.hpp"
#include "tom/migrator.hpp"
#include "tom/terminal.hpp"
@@ -41,6 +43,7 @@ using Orm::Constants::NEWLINE;
using TypeUtils = Orm::Utils::Type;
using Tom::Commands::Command;
using Tom::Commands::Database::SeedCommand;
using Tom::Commands::Database::WipeCommand;
using Tom::Commands::EnvironmentCommand;
using Tom::Commands::HelpCommand;
@@ -64,6 +67,7 @@ using Tom::Constants::nointeraction;
using Tom::Constants::quiet;
using Tom::Constants::verbose;
using Tom::Constants::version;
using Tom::Constants::DbSeed;
using Tom::Constants::DbWipe;
using Tom::Constants::Env;
using Tom::Constants::Help;
@@ -96,13 +100,14 @@ namespace Tom {
update indexes in the Application::commandsIndexes() and if the command introduces
a new namespace add it to the Application::namespaceNames().
I have everything extracted and placed it to the bottom of application.cpp so it is
nicely on one place. */
nicely in one place. */
/* public */
Application::Application(int &argc, char **argv, std::shared_ptr<DatabaseManager> db,
const char *const environmentEnvName, QString migrationTable,
std::vector<std::shared_ptr<Migration>> migrations)
std::vector<std::shared_ptr<Migration>> migrations,
std::vector<std::shared_ptr<Seeder>> seeders)
: m_argc(argc)
, m_argv(argv)
, m_db(std::move(db))
@@ -114,6 +119,7 @@ Application::Application(int &argc, char **argv, std::shared_ptr<DatabaseManager
, m_migrationsPath(initializeMigrationsPath(
TINYORM_STRINGIFY(TINYTOM_MIGRATIONS_DIR)))
, m_migrations(std::move(migrations))
, m_seeders(std::move(seeders))
{
// Enable UTF-8 encoding and vt100 support
Terminal::initialize();
@@ -138,6 +144,9 @@ Application::Application(int &argc, char **argv, std::shared_ptr<DatabaseManager
int Application::run()
{
// Default database connection is required in the migrations application
throwIfEmptyDefaultConnection();
// Process the actual command-line arguments given by the user
parseCommandLine();
@@ -390,6 +399,9 @@ Application::createCommand(const QString &command, const OptionalParserRef parse
// Use a custom parser if passed as the argument, needed by CallsCommands::call()
auto parserRef = parser ? *parser : std::ref(m_parser);
if (command == DbSeed)
return std::make_unique<SeedCommand>(*this, parserRef, m_db);
if (command == DbWipe)
return std::make_unique<WipeCommand>(*this, parserRef);
@@ -527,7 +539,7 @@ Application::commandNames() const
// global namespace
Env, Help, Inspire, List, Migrate,
// db
DbWipe,
DbSeed, DbWipe,
// make
MakeMigration, /*MakeProject,*/
// migrate
@@ -567,10 +579,10 @@ const std::vector<std::tuple<int, int>> &Application::commandsIndexes() const
static const std::vector<std::tuple<int, int>> cached {
{0, 5}, // "" - also global
{0, 5}, // global
{5, 6}, // db
{6, 7}, // make
{7, 13}, // migrate
{5, 13}, // namespaced
{5, 7}, // db
{7, 8}, // make
{8, 14}, // migrate
{5, 14}, // namespaced
};
return cached;
@@ -584,6 +596,19 @@ fspath Application::initializeMigrationsPath(fspath &&path)
return path.make_preferred();
}
std::shared_ptr<ConnectionResolverInterface> Application::resolver() const noexcept
{
return std::dynamic_pointer_cast<ConnectionResolverInterface>(m_db);
}
void Application::throwIfEmptyDefaultConnection() const
{
if (!m_db->getDefaultConnection().isEmpty())
return;
throw Exceptions::RuntimeError("Default database connection not configured.");
}
/* Auto tests helpers */
#ifdef TINYTOM_TESTS_CODE

View File

@@ -12,6 +12,8 @@
#include "tom/tomconstants.hpp"
#include "tom/version.hpp"
using Orm::ConnectionResolverInterface;
using Tom::Constants::Help;
using Tom::Constants::LongOption;
@@ -128,7 +130,13 @@ bool Command::hasArgument(const ArgumentsSizeType index) const
{
/* Has to be isNull(), an argument passed on the command line still can be an empty
value, like "", in this case it has to return a true value. */
return !argument(index).isNull();
return !argument(index, false).isNull();
}
bool Command::hasArgument(const QString &name) const
{
return m_positionalArguments.contains(name) &&
!argument(m_positionalArguments.at(name), false).isNull();
}
QStringList Command::arguments() const
@@ -136,25 +144,29 @@ QStringList Command::arguments() const
return parser().positionalArguments();
}
QString Command::argument(const ArgumentsSizeType index) const
QString Command::argument(const ArgumentsSizeType index, const bool useDefault) const
{
const auto &positionalArgumentsRef = positionalArguments();
using ArgumentsStdSizeType =
std::remove_cvref_t<decltype (positionalArgumentsRef)>::size_type;
const auto positionalArguments = parser().positionalArguments();
if (!useDefault)
return positionalArguments.value(index);
// Default value supported
return parser().positionalArguments()
.value(index,
positionalArgumentsRef.at(
static_cast<ArgumentsStdSizeType>(index) - 1).defaultValue);
auto defaultValue = positionalArgumentsRef.at(
static_cast<ArgumentsStdSizeType>(index) - 1).defaultValue;
return positionalArguments.value(index, std::move(defaultValue));
}
QString Command::argument(const QString &name) const
QString Command::argument(const QString &name, const bool useDefault) const
{
// Default value supported
return argument(m_positionalArguments.at(name));
return argument(m_positionalArguments.at(name), useDefault);
}
Orm::DatabaseConnection &Command::connection(const QString &name) const
@@ -167,6 +179,11 @@ QCommandLineParser &Command::parser() const noexcept
return m_parser;
}
std::shared_ptr<ConnectionResolverInterface> Command::resolver() const noexcept
{
return application().resolver();
}
/* private */
void Command::initializePositionalArguments()

View File

@@ -0,0 +1,159 @@
#include "tom/commands/database/seedcommand.hpp"
#include <orm/utils/type.hpp>
#ifndef TINYORM_DISABLE_ORM
# include <orm/tiny/model.hpp>
#endif
#include <range/v3/algorithm/contains.hpp>
#include <range/v3/algorithm/find.hpp>
#include "tom/application.hpp"
#include "tom/exceptions/invalidtemplateargumenterror.hpp"
#include "tom/seeder.hpp"
using Orm::Constants::database_;
#ifndef TINYORM_DISABLE_ORM
using Orm::Tiny::GuardedModel;
#endif
using Tom::Constants::class_;
using Tom::Constants::class_up;
using Tom::Constants::database_up;
using Tom::Constants::force;
using TypeUtils = Orm::Utils::Type;
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Commands::Database
{
const QString SeedCommand::DatabaseSeeder = QStringLiteral("Seeders::DatabaseSeeder");
/* public */
SeedCommand::SeedCommand(Application &application, QCommandLineParser &parser,
std::shared_ptr<ConnectionResolverInterface> &&resolver)
: Command(application, parser)
, Concerns::Confirmable(*this, 0)
, Concerns::UsingConnection(this->resolver())
, m_resolver(std::move(resolver))
{}
const std::vector<PositionalArgument> &SeedCommand::positionalArguments() const
{
static const std::vector<PositionalArgument> cached {
{class_, QStringLiteral("The class name of the root seeder"), {}, true},
};
return cached;
}
QList<QCommandLineOption> SeedCommand::optionsSignature() const
{
return {
{class_, QStringLiteral("The class name of the root seeder"), class_up,
DatabaseSeeder}, // Value
{database_, QStringLiteral("The database connection to use"), database_up}, // Value
{force, QStringLiteral("Force the operation to run when in production")},
};
}
int SeedCommand::run()
{
Command::run();
// Ask for confirmation in the production environment
if (!confirmToProceed())
return EXIT_FAILURE;
// Database connection to use
return usingConnection(value(database_), isDebugVerbosity(), [this]
{
auto seederResult = getSeeder();
comment(QStringLiteral("Seeding: "), false);
note(QStringLiteral("%1 (root)").arg(seederResult.name));
QElapsedTimer timer;
timer.start();
// Fire it up 🔥
#ifdef TINYORM_DISABLE_ORM
seederResult.seeder.get().run();
#else
GuardedModel::unguarded([&seederResult]
{
seederResult.seeder.get().run();
});
#endif
const auto elapsedTime = timer.elapsed();
info(QStringLiteral("Seeded:"), false);
note(QStringLiteral(" %1 (%2ms total)").arg(std::move(seederResult.name))
.arg(elapsedTime));
info("Database seeding completed successfully.");
return EXIT_SUCCESS;
});
}
/* protected */
SeedCommand::GetSeederResult Tom::Commands::Database::SeedCommand::getSeeder() const
{
// The --class option also provides a default value 'Seeders::DatabaseSeeder'
auto seederClass = hasArgument(class_) ? argument(class_) : value(class_);
// Prepend Seeders:: namespace, it's requirement
if (!seederClass.contains(QStringLiteral("::")))
seederClass.prepend(QStringLiteral("Seeders::"));
// Throw if the root seeder is not defined
throwIfDoesntContainSeeder(seederClass);
// Find a reference to the root seeder, doesn't need to check the std::end()
auto &rootSeeder = **ranges::find(seeders(), seederClass,
[](const auto &seeder)
{
return TypeUtils::classPureBasename(*seeder, true);
});
/* This command is used as the InteractsWithIO interface inside the seeder and passed
down to other seeders. */
rootSeeder.setIO(*this);
return {std::move(seederClass), rootSeeder};
}
const std::vector<std::shared_ptr<Seeder>> &SeedCommand::seeders() const noexcept
{
return application().getSeeders();
}
/* private */
void SeedCommand::throwIfDoesntContainSeeder(const QString &seederClass) const
{
const auto containsSeeder = ranges::contains(seeders(), seederClass,
[](const auto &seeder)
{
return TypeUtils::classPureBasename(*seeder, true);
});
if (containsSeeder)
return;
throw Exceptions::InvalidTemplateArgumentError(
QStringLiteral("The root seeder '%1' is not defined.")
.arg(seederClass));
}
} // namespace Tom::Commands::Database
TINYORM_END_COMMON_NAMESPACE

View File

@@ -23,6 +23,7 @@ namespace Tom::Commands::Database
WipeCommand::WipeCommand(Application &application, QCommandLineParser &parser)
: Command(application, parser)
, Concerns::Confirmable(*this, 0)
, Concerns::UsingConnection(resolver())
{}
QList<QCommandLineOption> WipeCommand::optionsSignature() const
@@ -43,43 +44,33 @@ int WipeCommand::run()
if (!confirmToProceed())
return EXIT_FAILURE;
// Database connection to use
const auto database = value(database_);
// Set the debug sql for the current connection
setConnectionDebugSql(database);
// Database connection to use
return usingConnection(database, isDebugVerbosity(), [this, &database]
{
if (isSet(drop_views)) {
dropAllViews(database);
if (isSet(drop_views)) {
dropAllViews(database);
info(QStringLiteral("Dropped all views successfully."));
}
info(QStringLiteral("Dropped all views successfully."));
}
dropAllTables(database);
dropAllTables(database);
info(QStringLiteral("Dropped all tables successfully."));
info(QStringLiteral("Dropped all tables successfully."));
if (isSet(drop_types)) {
dropAllTypes(database);
if (isSet(drop_types)) {
dropAllTypes(database);
info(QStringLiteral("Dropped all types successfully."));
}
info(QStringLiteral("Dropped all types successfully."));
}
return EXIT_SUCCESS;
return EXIT_SUCCESS;
});
}
/* protected */
void WipeCommand::setConnectionDebugSql(const QString &connectionName) const
{
auto &connection = this->connection(connectionName);
if (isDebugVerbosity())
connection.enableDebugSql();
else
connection.disableDebugSql();
}
void WipeCommand::dropAllTables(const QString &database) const
{
connection(database).getSchemaBuilder()->dropAllTables();

View File

@@ -8,12 +8,16 @@
using Orm::Constants::database_;
using Tom::Constants::class_;
using Tom::Constants::database_up;
using Tom::Constants::drop_types;
using Tom::Constants::drop_views;
using Tom::Constants::force;
using Tom::Constants::seed;
using Tom::Constants::seeder;
using Tom::Constants::seeder_up;
using Tom::Constants::step_;
//using Tom::Constants::DbSeed;
using Tom::Constants::DbSeed;
using Tom::Constants::DbWipe;
using Tom::Constants::Migrate;
@@ -41,8 +45,8 @@ QList<QCommandLineOption> FreshCommand::optionsSignature() const
{drop_types, QStringLiteral("Drop all tables and types (Postgres only)")},
{force, QStringLiteral("Force the operation to run when in production")},
// {"schema-path", QStringLiteral("The path to a schema dump file")}, // Value
// {"seed", QStringLiteral("Indicates if the seed task should be re-run")},
// {"seeder", QStringLiteral("The class name of the root seeder"), "seeded"}, // Value
{seed, QStringLiteral("Indicates if the seed task should be re-run")},
{seeder, QStringLiteral("The class name of the root seeder"), seeder_up}, // Value
{step_, QStringLiteral("Force the migrations to be run so they can be "
"rolled back individually")},
};
@@ -69,25 +73,26 @@ int FreshCommand::run()
boolCmd(step_)});
// valueCmd("schema-path")});
// if (needsSeeding())
// runSeeder(std::move(databaseCmd));
// Invoke seeder
if (needsSeeding())
runSeeder(std::move(databaseCmd));
return EXIT_SUCCESS;
}
/* protected */
//bool FreshCommand::needsSeeding() const
//{
// return isSet("seed") || !value("seeder").isEmpty();
//}
bool FreshCommand::needsSeeding() const
{
return isSet(seed) || !value(seeder).isEmpty();
}
//void FreshCommand::runSeeder(QString &&databaseCmd) const
//{
// call(DbSeed, {std::move(databaseCmd),
// longOption(force),
// valueCmd("seeder", "class")});
//}
void FreshCommand::runSeeder(QString &&databaseCmd) const
{
call(DbSeed, {std::move(databaseCmd),
longOption(force),
valueCmd(seeder, class_)});
}
} // namespace Tom::Commands::Migrations

View File

@@ -22,6 +22,7 @@ InstallCommand::InstallCommand(
std::shared_ptr<MigrationRepository> repository
)
: Command(application, parser)
, Concerns::UsingConnection(resolver())
, m_repository(std::move(repository))
{}
@@ -37,13 +38,14 @@ int InstallCommand::run()
Command::run();
// Database connection to use
m_repository->setConnection(value(database_), isDebugVerbosity());
return usingConnection(value(database_), isDebugVerbosity(), *m_repository, [this]
{
m_repository->createRepository();
m_repository->createRepository();
info(QStringLiteral("Migration table created successfully."));
info(QStringLiteral("Migration table created successfully."));
return EXIT_SUCCESS;
return EXIT_SUCCESS;
});
}
} // namespace Tom::Commands::Migrations

View File

@@ -11,7 +11,9 @@ using Orm::Constants::database_;
using Tom::Constants::database_up;
using Tom::Constants::force;
using Tom::Constants::pretend;
using Tom::Constants::seed;
using Tom::Constants::step_;
using Tom::Constants::DbSeed;
using Tom::Constants::MigrateInstall;
TINYORM_BEGIN_COMMON_NAMESPACE
@@ -27,6 +29,7 @@ MigrateCommand::MigrateCommand(
)
: Command(application, parser)
, Concerns::Confirmable(*this, 0)
, Concerns::UsingConnection(resolver())
, m_migrator(std::move(migrator))
{}
@@ -37,8 +40,7 @@ QList<QCommandLineOption> MigrateCommand::optionsSignature() const
{force, QStringLiteral("Force the operation to run when in production")},
{pretend, QStringLiteral("Dump the SQL queries that would be run")},
// {"schema-path", QStringLiteral("The path to a schema dump file")}, // Value
// {"seed", QStringLiteral("Indicates if the seed task should be re-run")},
// {"seeder", QStringLiteral("The class name of the root seeder"), "seeded"}, // Value
{seed, QStringLiteral("Indicates if the seed task should be re-run")},
{step_, QStringLiteral("Force the migrations to be run so they can be "
"rolled back individually")},
};
@@ -53,7 +55,8 @@ int MigrateCommand::run()
return EXIT_FAILURE;
// Database connection to use
return m_migrator->usingConnection(value(database_), isDebugVerbosity(), [this]
return usingConnection(value(database_), isDebugVerbosity(), m_migrator->repository(),
[this]
{
// Install db repository and load schema state
prepareDatabase();
@@ -63,11 +66,13 @@ int MigrateCommand::run()
so that migrations may be run for any path within the applications. */
m_migrator->run({isSet(pretend), isSet(step_)});
info("Database migaration completed successfully.");
/* Finally, if the "seed" option has been given, we will re-run the database
seed task to re-populate the database, which is convenient when adding
a migration and a seed at the same time, as it is only this command. */
// if (needsSeeding()
// runSeeder();
if (needsSeeding())
runSeeder();
return EXIT_SUCCESS;
});
@@ -89,17 +94,16 @@ void MigrateCommand::loadSchemaState() const
// CUR tom, finish load schema silverqx
}
//bool MigrateCommand::needsSeeding() const
//{
// return !isSet(pretend) && (isSet("seed") || !value("seeder").isEmpty());
//}
// CUR tom, remove silverqx
bool MigrateCommand::needsSeeding() const
{
return !isSet(pretend) && isSet(seed);
}
//void MigrateCommand::runSeeder() const
//{
// call(DbSeed, {valueCmd(database_),
// longOption(force),
// valueCmd("seeder", "class")});
//}
void MigrateCommand::runSeeder() const
{
call(DbSeed, {valueCmd(database_), longOption(force)});
}
} // namespace Tom::Commands::Migrations

View File

@@ -8,8 +8,12 @@
using Orm::Constants::database_;
using Tom::Constants::class_;
using Tom::Constants::database_up;
using Tom::Constants::force;
using Tom::Constants::seed;
using Tom::Constants::seeder;
using Tom::Constants::seeder_up;
using Tom::Constants::step_;
using Tom::Constants::step_up;
using Tom::Constants::step_migrate;
@@ -39,8 +43,8 @@ QList<QCommandLineOption> RefreshCommand::optionsSignature() const
return {
{database_, QStringLiteral("The database connection to use"), database_up}, // Value
{force, QStringLiteral("Force the operation to run when in production")},
// {"seed", QStringLiteral("Indicates if the seed task should be re-run")},
// {"seeder", QStringLiteral("The class name of the root seeder", "seeded")}, // Value
{seed, QStringLiteral("Indicates if the seed task should be re-run")},
{seeder, QStringLiteral("The class name of the root seeder"), seeder_up}, // Value
{step_, QStringLiteral("The number of migrations to be reverted & "
"re-run"), step_up}, // Value
{step_migrate, QStringLiteral("Force the migrations to be run so they can be "
@@ -73,8 +77,9 @@ int RefreshCommand::run()
longOption(force),
boolCmd(step_migrate, step_)});
// if (needsSeeding())
// runSeeder(std::move(databaseCmd));
// Invoke seeder
if (needsSeeding())
runSeeder(std::move(databaseCmd));
return EXIT_SUCCESS;
}
@@ -83,14 +88,14 @@ int RefreshCommand::run()
bool RefreshCommand::needsSeeding() const
{
return isSet("seed") || !value("seeder").isEmpty();
return isSet(seed) || !value(seeder).isEmpty();
}
void RefreshCommand::runSeeder(QString &&databaseCmd) const
{
call(DbSeed, {std::move(databaseCmd),
longOption(force),
valueCmd("seeder", "class")});
valueCmd(seeder, class_)});
}
} // namespace Tom::Commands::Migrations

View File

@@ -25,6 +25,7 @@ ResetCommand::ResetCommand(
)
: Command(application, parser)
, Concerns::Confirmable(*this, 0)
, Concerns::UsingConnection(resolver())
, m_migrator(std::move(migrator))
{}
@@ -46,7 +47,8 @@ int ResetCommand::run()
return EXIT_FAILURE;
// Database connection to use
return m_migrator->usingConnection(value(database_), isDebugVerbosity(), [this]
return usingConnection(value(database_), isDebugVerbosity(), m_migrator->repository(),
[this]
{
if (!m_migrator->repositoryExists()) {
comment(QStringLiteral("Migration table not found."));

View File

@@ -27,6 +27,7 @@ RollbackCommand::RollbackCommand(
)
: Command(application, parser)
, Concerns::Confirmable(*this, 0)
, Concerns::UsingConnection(resolver())
, m_migrator(std::move(migrator))
{}
@@ -49,7 +50,8 @@ int RollbackCommand::run()
return EXIT_FAILURE;
// Database connection to use
return m_migrator->usingConnection(value(database_), isDebugVerbosity(), [this]
return usingConnection(value(database_), isDebugVerbosity(), m_migrator->repository(),
[this]
{
// Validation not needed as the toInt() returns 0 if conversion fails, like it
m_migrator->rollback({.pretend = isSet(pretend),

View File

@@ -30,6 +30,7 @@ StatusCommand::StatusCommand(
std::shared_ptr<Migrator> migrator
)
: Command(application, parser)
, Concerns::UsingConnection(resolver())
, m_migrator(std::move(migrator))
{}
@@ -45,7 +46,8 @@ int StatusCommand::run()
Command::run();
// Database connection to use
return m_migrator->usingConnection(value(database_), isDebugVerbosity(), [this]
return usingConnection(value(database_), isDebugVerbosity(), m_migrator->repository(),
[this]
{
if (!m_migrator->repositoryExists()) {
error(QStringLiteral("Migration table not found."));

View File

@@ -0,0 +1,109 @@
#include "tom/concerns/usingconnection.hpp"
#include <orm/connectionresolverinterface.hpp>
#include <orm/databaseconnection.hpp>
#include "tom/migrationrepository.hpp"
using Orm::DatabaseConnection;
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Concerns
{
/* public */
UsingConnection::UsingConnection(std::shared_ptr<ConnectionResolverInterface> &&resolver)
: m_resolver(std::move(resolver))
{}
int UsingConnection::usingConnection(
QString name, const bool debugSql,
std::optional<std::reference_wrapper<MigrationRepository>> repository,
std::function<int()> &&callback)
{
auto previousConnection = m_resolver->getDefaultConnection();
/* Default connection can also be "" empty string, eg. auto tests are using empty
string as the default connection. */
auto previousDebugSql = getConnectionDebugSql(previousConnection);
/* Case, when no connection name was passed on the command-line (--database argument),
in this case, set a current connection to the default connection. */
if (name.isEmpty())
name = previousConnection;
// No need to update the same values
const auto isSameConnection = previousConnection == name &&
previousDebugSql == debugSql;
if (!isSameConnection)
setConnection(std::move(name), debugSql, repository);
auto exitCode = std::invoke(std::move(callback));
if (!isSameConnection)
setConnection(std::move(previousConnection), std::move(previousDebugSql),
repository, true);
return exitCode;
}
int UsingConnection::usingConnection(QString &&name, const bool debugSql,
std::function<int()> &&callback)
{
return usingConnection(std::move(name), debugSql, std::nullopt, std::move(callback));
}
int UsingConnection::usingConnection(const QString &name, const bool debugSql,
std::function<int()> &&callback)
{
return usingConnection(std::move(name), debugSql, std::nullopt, std::move(callback));
}
DatabaseConnection &UsingConnection::resolveConnection(const QString &name) const
{
return m_resolver->connection(name.isEmpty() ? m_connection : name);
}
/* private */
void UsingConnection::setConnection(QString &&name, std::optional<bool> &&debugSql,
std::optional<std::reference_wrapper<
MigrationRepository>> repository,
const bool restore)
{
// Restore even an empty "" connection, it makes sense
if (restore || !name.isEmpty())
m_resolver->setDefaultConnection(name);
if (repository)
repository->get().setConnection(name);
m_connection = std::move(name);
setConnectionDebugSql(std::move(debugSql));
}
std::optional<bool> UsingConnection::getConnectionDebugSql(const QString &name) const
{
return name.isEmpty() ? std::nullopt
: std::make_optional(m_resolver->connection(name).debugSql());
}
void UsingConnection::setConnectionDebugSql(std::optional<bool> &&debugSql) const
{
if (m_connection.isEmpty())
return;
auto &connection = resolveConnection(m_connection);
if (*debugSql)
connection.enableDebugSql();
else
connection.disableDebugSql();
}
} // namespace Tom::Concerns
TINYORM_END_COMMON_NAMESPACE

View File

@@ -110,7 +110,7 @@ int MigrationRepository::getLastBatchNumber() const
void MigrationRepository::createRepository() const
{
// Ownership of a unique_ptr()
const auto schema = getConnection().getSchemaBuilder();
const auto schema = connection().getSchemaBuilder();
/* The migrations table is responsible for keeping track of which migrations have
actually run for the application. We'll create the table to hold the migration
@@ -127,38 +127,26 @@ void MigrationRepository::createRepository() const
bool MigrationRepository::repositoryExists() const
{
// Ownership of a unique_ptr()
const auto schema = getConnection().getSchemaBuilder();
const auto schema = connection().getSchemaBuilder();
return schema->hasTable(m_table);
}
void MigrationRepository::deleteRepository() const
{
getConnection().getSchemaBuilder()->drop(m_table);
connection().getSchemaBuilder()->drop(m_table);
}
DatabaseConnection &MigrationRepository::getConnection() const
DatabaseConnection &MigrationRepository::connection() const
{
return m_resolver->connection(m_connection);
}
void MigrationRepository::setConnection(const QString &name,
std::optional<bool> &&debugSql)
{
m_connection = name;
if (!debugSql)
return;
// Enable/disable showing of sql queries in the console
setConnectionDebugSql(std::move(debugSql));
}
/* protected */
QSharedPointer<QueryBuilder> MigrationRepository::table() const
{
return getConnection().table(m_table);
return connection().table(m_table);
}
std::vector<MigrationItem>
@@ -181,16 +169,6 @@ MigrationRepository::hydrateMigrations(QSqlQuery &query) const
return migration;
}
void MigrationRepository::setConnectionDebugSql(std::optional<bool> &&debugSql) const
{
auto &connection = getConnection();
if (*debugSql)
connection.enableDebugSql();
else
connection.disableDebugSql();
}
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE

View File

@@ -104,25 +104,6 @@ std::vector<RollbackItem> Migrator::reset(const bool pretend) const
pretend);
}
/* Database connection related */
int Migrator::usingConnection(QString &&name, const bool debugSql,
std::function<int()> &&callback)
{
auto previousConnection = m_resolver->getDefaultConnection();
/* Default connection can also be "" empty string, eg. auto tests are using empty
string as the default connection. */
auto previousDebugSql = getConnectionDebugSql(previousConnection);
setConnection(std::move(name), debugSql);
auto exitCode = std::invoke(std::move(callback));
setConnection(std::move(previousConnection), std::move(previousDebugSql));
return exitCode;
}
/* Proxies to MigrationRepository */
bool Migrator::repositoryExists() const
@@ -135,32 +116,13 @@ bool Migrator::hasRunAnyMigrations() const
return repositoryExists() && !m_repository->getRanSimple().isEmpty();
}
/* Getters / Setters */
void Migrator::setConnection(QString &&name, std::optional<bool> &&debugSql)
{
// It indicates "" empty string for the default connection, eg. in auto tests
if (!name.isEmpty())
m_resolver->setDefaultConnection(name);
m_repository->setConnection(name, std::move(debugSql));
m_connection = std::move(name);
}
/* protected */
/* Database connection related */
DatabaseConnection &Migrator::resolveConnection(const QString &name) const
{
return m_resolver->connection(name.isEmpty() ? m_connection : name);
}
std::optional<bool> Migrator::getConnectionDebugSql(const QString &name) const
{
return name.isEmpty() ? std::nullopt
: std::make_optional(m_resolver->connection(name).debugSql());
return m_resolver->connection(name);
}
/* Migration instances lists and hashes */
@@ -253,8 +215,8 @@ void Migrator::runUp(const Migration &migration, const int batch,
in the application. A migration repository keeps the migrate order. */
m_repository->log(migrationName, batch);
info(QStringLiteral("Migrated: "), false);
note(QStringLiteral("%1 (%2ms)").arg(std::move(migrationName)).arg(elapsedTime));
info(QStringLiteral("Migrated:"), false);
note(QStringLiteral(" %1 (%2ms)").arg(std::move(migrationName)).arg(elapsedTime));
}
/* Rollback */
@@ -340,8 +302,8 @@ void Migrator::runDown(const RollbackItem &migrationToRollback, const bool prete
by the application then will be able to fire by any later operation. */
m_repository->deleteMigration(id);
info(QStringLiteral("Rolled back: "), false);
note(QStringLiteral("%1 (%2ms)").arg(migrationName).arg(elapsedTime));
info(QStringLiteral("Rolled back:"), false);
note(QStringLiteral(" %1 (%2ms)").arg(migrationName).arg(elapsedTime));
}
/* Pretend */
@@ -402,7 +364,7 @@ void Migrator::runMigration(const Migration &migration, const MigrateMethod meth
try {
migrateByMethod(migration, method);
} catch (const std::exception &/*unused*/) {
} catch (...) {
connection.rollBack();
// Re-throw

57
tom/src/tom/seeder.cpp Normal file
View File

@@ -0,0 +1,57 @@
#include "tom/seeder.hpp"
#include <QElapsedTimer>
#include <orm/utils/type.hpp>
#include "tom/concerns/interactswithio.hpp"
using TypeUtils = Orm::Utils::Type;
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom
{
/* public */
Seeder &Seeder::setIO(const InteractsWithIO &io)
{
m_io = io;
return *this;
}
/* private */
void Seeder::callInternal(const bool silent, std::function<void()> &&callback) const
{
QElapsedTimer timer;
const auto shouldLog = !silent && m_io;
QString seederName;
if (shouldLog) {
seederName = TypeUtils::classPureBasename(*this, true);
m_io->get().comment(QStringLiteral("Seeding: "), false).note(seederName);
timer.start();
}
std::invoke(std::move(callback));
if (!shouldLog)
return;
const auto elapsedTime = timer.elapsed();
m_io->get().info(QStringLiteral("Seeded:"), false);
m_io->get().note(QStringLiteral(" %1 (%2ms)").arg(std::move(seederName))
.arg(elapsedTime));
}
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE

View File

@@ -36,12 +36,18 @@ namespace Tom::Constants
// Used by more commands
const QString force = QStringLiteral("force");
const QString pretend = QStringLiteral("pretend");
const QString seed = QStringLiteral("seed");
const QString seeder = QStringLiteral("seeder");
const QString step_ = QStringLiteral("step");
// Default value names
const QString class_up = QStringLiteral("CLASS");
const QString database_up = QStringLiteral("DATABASE");
const QString seeder_up = QStringLiteral("SEEDER");
const QString step_up = QStringLiteral("STEP");
// list
const QString raw_ = QStringLiteral("raw");
// db:seed
const QString class_ = QStringLiteral("class");
// db:wipe
const QString drop_views = QStringLiteral("drop-views");
const QString drop_types = QStringLiteral("drop-types");