mirror of
https://github.com/silverqx/TinyORM.git
synced 2026-02-21 11:18:42 -06:00
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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
# ---
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
6
tests/database/seeders.pri
Normal file
6
tests/database/seeders.pri
Normal file
@@ -0,0 +1,6 @@
|
||||
INCLUDEPATH += $$PWD
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/seeders/databaseseeder.hpp \
|
||||
$$PWD/seeders/phonesseeder.hpp \
|
||||
$$PWD/seeders/propertiesseeder.hpp \
|
||||
31
tests/database/seeders/databaseseeder.hpp
Normal file
31
tests/database/seeders/databaseseeder.hpp
Normal 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
|
||||
37
tests/database/seeders/phonesseeder.hpp
Normal file
37
tests/database/seeders/phonesseeder.hpp
Normal 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
|
||||
20
tests/database/seeders/propertiesseeder.hpp
Normal file
20
tests/database/seeders/propertiesseeder.hpp
Normal 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
|
||||
@@ -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 \
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
98
tom/include/tom/commands/database/seedcommand.hpp
Normal file
98
tom/include/tom/commands/database/seedcommand.hpp
Normal 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
|
||||
@@ -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. */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
99
tom/include/tom/concerns/usingconnection.hpp
Normal file
99
tom/include/tom/concerns/usingconnection.hpp
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
246
tom/include/tom/seeder.hpp
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
159
tom/src/tom/commands/database/seedcommand.cpp
Normal file
159
tom/src/tom/commands/database/seedcommand.cpp
Normal 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
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."));
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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."));
|
||||
|
||||
109
tom/src/tom/concerns/usingconnection.cpp
Normal file
109
tom/src/tom/concerns/usingconnection.cpp
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
57
tom/src/tom/seeder.cpp
Normal 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
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user