added migrations 🔥🚀

Almost everything is implemented, possible to migrate up, down
(rollback), migrate by steps (works for up/down too), reset, refresh,
fresh, wipe database, showing status, installing migration table.

Command line interface is superb, it supports ansi coloring, verbosity,
no-interactive mode, force option, env option to select current env.

It has enhanced ansi coloring (vt100 terminal) detection, when ansi or
no-ansi option is not passed it can detect whether terminal supports
coloring.
Ansi coloring is disabled when redirection to file is detected (can
be overridden by ansi/no-ansi options).
Supports NO_COLOR env. variable (https://no-color.org/) and can detect
the ConEmu, Hyper, and xterm on Windows.

Carefully implemented help and list commands, list command can print
supported commands by namespace.

Advanced make migration command offers great command interface for
creating migration classes, supports options for generating empty,
create, or update migration classes.

Unfinished make project command, will support creating qmake, qmake
subproject, and cmake, cmake subproject projects. Later will be
extracted to own executable tomproject.exe for rapidly generating a new
TinyORM projects.

Other implemented commands are env that prints current env. and inspire
that displays an inspiring quote 😁.

Verbose supports 5 levels quiet, normal, verbose, very verbose, and
debug.

Possibility to disable compilation of the tom command related code using
TINYORM_DISABLE_TOM c macro, for the qmake exists disable_tom CONFIG
option and for the CMake exist TOM configuration option.

Confirmable interface that ask Y/N confirmation during migrate when
env. == production, can be overridden by --force option.

Whole tom terminal application supports or is implemented with UTF-8
support, also implemented UTF-16 output methods but they are not needed.
Input also supports UTF-8, currently only Y/N input is needed by the
Confirmation concern.

All migrate commands also support the --pretend option and the --env
option, when env. is production then tom asks confirmation to migrate,
it can be overridden by the --force option.

Added the tom example project, it is a complete command-line migration
application, it uses migrations from the tests.

Implementing this was really fun 🙃😎.

 - added 14 functional tests to test migrations up/down, stepping,
   installing migration table, refresh, reset on MySQL database
 - added unit test to check version number in tom.exe executable
 - new tom exception classes
 - created dozens of a new todo tasks 😂🤪, currently 348 todos 😎
 - added some info messages to the qmake build about building features
 - in the debug build and with the -vvv option enable debugging of sql
   queries
 - enhanced RC and manifest file generation, allowed to pass a custom
   basename for a RC/manifest file as the 3. argument and a custom
   replace token for the CMake genex as the 4. argument
 - bugfix disabled #pragma code_page(65001) // UTF-8 in RC files, it
   messes up the © character

Output of tom exe without arguments and options:

Common options:
      --ansi            Force ANSI output
      --no-ansi         Disable ANSI output
      --env             The environment the command should run under
  -h, --help            Display help for the given command. When no
                        command is given display help for the list
                        command
  -n, --no-interaction  Do not ask any interactive question
  -q, --quiet           Do not output any message
  -V, --version         Display this application version
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal
                        output, 2 for more verbose output and
                        3 for debug

Available commands:
  env                   Display the current framework environment
  help                  Display help for a command
  inspire               Display an inspiring quote
  list                  List commands
  migrate               Run the database migrations
 db
  db:wipe               Drop all tables, views, and types
 make
  make:migration        Create a new migration file
  make:project          Create a new Tom application project
 migrate
  migrate:fresh         Drop all tables and re-run all migrations
  migrate:install       Create the migration repository
  migrate:refresh       Rollback and re-run all migrations
  migrate:reset         Rollback all database migrations
  migrate:rollback      Rollback the last database migration
  migrate:status        Show the status of each migration
This commit is contained in:
silverqx
2022-04-19 20:02:37 +02:00
parent 0290212d36
commit a6213a6a9d
113 changed files with 8276 additions and 290 deletions

View File

@@ -53,8 +53,10 @@ set(minReqMsvcVersion 19.29)
set(minRecGCCVersion 10.2)
set(minRecClangVersion 12)
# Packages
set(minQtVersion 5.15.2)
set(minRangeV3Version 0.11.0)
set(minQtVersion 5.15.2)
set(minRangeV3Version 0.11.0)
# tabulate doesn't provide Package Version File
#set(minTabulateVersion 1.4.0)
# Make minimum toolchain version a requirement
tiny_toolchain_requirement(
@@ -157,11 +159,25 @@ option is disabled, then only the query builder without ORM is compiled."
DISABLED TINYORM_DISABLE_ORM
)
target_optional_compile_definitions(${TinyOrm_target}
PUBLIC
FEATURE NAME TOM
DEFAULT ON
DESCRIPTION "Controls the compilation of all tom related source code (command \
line interface)."
DISABLED TINYORM_DISABLE_TOM
)
# Depends on the TOM option so defined here
feature_option_dependent(TOM_EXAMPLE
"Build the tom command-line application example" OFF
"TOM" TOM_EXAMPLE-NOTFOUND
)
# TinyORM library header and source files
# ---
include(TinySources)
tiny_sources(${TinyOrm_target}_headers ${TinyOrm_target}_sources)
tinyorm_sources(${TinyOrm_target}_headers ${TinyOrm_target}_sources)
target_sources(${TinyOrm_target} PRIVATE
${${TinyOrm_target}_headers}
@@ -212,7 +228,6 @@ target_compile_definitions(${TinyOrm_target}
$<$<NOT:$<CONFIG:Debug>>:TINYORM_NO_DEBUG>
# Do not log queries
$<$<NOT:$<CONFIG:Debug>>:TINYORM_NO_DEBUG_SQL>
PRIVATE
# Debug build
$<$<CONFIG:Debug>:TINYORM_DEBUG>
# Log queries with a time measurement
@@ -237,10 +252,49 @@ else()
endif()
# Enable code needed by tests, eg. connection overriding in the Model
if(BUILD_TESTS)
if(BUILD_TESTS AND ORM)
target_compile_definitions(${TinyOrm_target} PUBLIC TINYORM_TESTS_CODE)
endif()
# TinyTom related header and source files
# ---
if(TOM)
tinytom_sources(${TomExample_target}_headers ${TomExample_target}_sources)
target_sources(${TinyOrm_target} PRIVATE
${${TomExample_target}_headers}
${${TomExample_target}_sources}
)
endif()
# TinyTom related specific configuration
# ---
if(TOM)
target_include_directories(${TinyOrm_target} PUBLIC
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/tom/include>"
)
endif()
# TinyTom related defines
# ---
if(TOM)
target_compile_definitions(${TinyOrm_target}
PUBLIC
# Release build
$<$<NOT:$<CONFIG:Debug>>:TINYTOM_NO_DEBUG>
# Debug build
$<$<CONFIG:Debug>:TINYTOM_DEBUG>
)
# Enable code needed by tests (modify the migrate:status command for tests need)
if(BUILD_TESTS)
target_compile_definitions(${TinyOrm_target} PUBLIC TINYTOM_TESTS_CODE)
endif()
endif()
# Windows resource and manifest files
# ---
@@ -285,6 +339,12 @@ if(MYSQL_PING)
target_link_libraries(${TinyOrm_target} PRIVATE MySQL::MySQL)
endif()
if(TOM)
# tabulate doesn't provide Package Version File
tiny_find_package(tabulate CONFIG REQUIRED)
target_link_libraries(${TinyOrm_target} PUBLIC tabulate::tabulate)
endif()
# Build auto tests
# ---
@@ -295,6 +355,13 @@ if(BUILD_TESTS)
add_subdirectory(tests)
endif()
# Build examples
# ---
if(TOM_EXAMPLE)
add_subdirectory(examples)
endif()
# Deployment
# ---
@@ -355,10 +422,20 @@ set_package_properties(range-v3
if(MYSQL_PING)
set_package_properties(MySQL
PROPERTIES
# URL and DESCRIPTION are already set by Find-module Package (FindMySQL.cmake)
TYPE REQUIRED
PURPOSE "Provides MySQL ping, enables MySqlConnection::pingDatabase()"
)
endif()
if(TOM)
set_package_properties(tabulate
PROPERTIES
URL "https://github.com/p-ranav/tabulate/"
DESCRIPTION "Table Maker for Modern C++"
TYPE REQUIRED
PURPOSE "Used by the tom in the migrate:status command"
)
endif()
if(VERBOSE_CONFIGURE)
if(NOT TINY_IS_MULTI_CONFIG AND NOT CMAKE_BUILD_TYPE STREQUAL "Debug")

View File

@@ -2,13 +2,15 @@ TEMPLATE = subdirs
SUBDIRS = src
!disable_tom {
SUBDIRS += tom
tom.depends = src
tom_example {
SUBDIRS += examples
examples.depends = src
}
# Can be enabled by CONFIG += build_tests when the qmake.exe for the project is called
build_tests {
SUBDIRS += tests
tests.depends = src
!build_pass: message("Build TinyORM unit tests.")
}

View File

@@ -143,10 +143,12 @@ macro(tiny_init_tiny_variables_pre)
# a main package name
set(TinyOrm_ns TinyOrm)
set(TinyUtils_ns TinyUtils)
set(TomExample_ns tom)
# Target names
set(CommonConfig_target CommonConfig)
set(TinyOrm_target TinyOrm)
set(TinyUtils_target TinyUtils)
set(TomExample_target tom)
get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
set(TINY_IS_MULTI_CONFIG "${isMultiConfig}" CACHE INTERNAL

View File

@@ -1,7 +1,8 @@
# TinyORM library header and source files
# Create header and source files lists and return them
function(tiny_sources out_headers out_sources)
function(tinyorm_sources out_headers out_sources)
# ORM headers section
set(headers)
if(TINY_EXTERN_CONSTANTS)
@@ -131,10 +132,17 @@ function(tiny_sources out_headers out_sources)
tiny/types/connectionoverride.hpp
tiny/types/syncchanges.hpp
tiny/utils/attribute.hpp
)
endif()
# Headers used in both ORM and TOM
if(ORM OR TOM)
list(APPEND headers
tiny/utils/string.hpp
)
endif()
# ORM sources section
set(sources)
if(TINY_EXTERN_CONSTANTS)
@@ -203,6 +211,12 @@ function(tiny_sources out_headers out_sources)
tiny/tinytypes.cpp
tiny/types/syncchanges.cpp
tiny/utils/attribute.cpp
)
endif()
# Sources needed in both ORM and TOM
if(ORM OR TOM)
list(APPEND sources
tiny/utils/string.cpp
)
endif()
@@ -215,5 +229,91 @@ function(tiny_sources out_headers out_sources)
set(${out_headers} ${headers} PARENT_SCOPE)
set(${out_sources} ${sources} PARENT_SCOPE)
endfunction()
# TinyTom application header and source files
# Create header and source files lists and return them
function(tinytom_sources out_headers out_sources)
# Tom headers section
set(headers)
list(APPEND headers
application.hpp
commands/command.hpp
commands/database/wipecommand.hpp
commands/environmentcommand.hpp
commands/helpcommand.hpp
commands/inspirecommand.hpp
commands/listcommand.hpp
commands/make/migrationcommand.hpp
# commands/make/projectcommand.hpp
commands/make/stubs/migrationstubs.hpp
commands/make/stubs/projectstubs.hpp
commands/migrations/freshcommand.hpp
commands/migrations/installcommand.hpp
commands/migrations/migratecommand.hpp
commands/migrations/refreshcommand.hpp
commands/migrations/resetcommand.hpp
commands/migrations/rollbackcommand.hpp
commands/migrations/statuscommand.hpp
concerns/callscommands.hpp
concerns/confirmable.hpp
concerns/interactswithio.hpp
concerns/printsoptions.hpp
config.hpp
exceptions/invalidargumenterror.hpp
exceptions/invalidtemplateargumenterror.hpp
exceptions/logicerror.hpp
exceptions/runtimeerror.hpp
exceptions/tomerror.hpp
migration.hpp
migrationcreator.hpp
migrationrepository.hpp
migrator.hpp
terminal.hpp
tomtypes.hpp
version.hpp
)
# Tom sources section
set(sources)
list(APPEND sources
application.cpp
commands/command.cpp
commands/database/wipecommand.cpp
commands/environmentcommand.cpp
commands/helpcommand.cpp
commands/inspirecommand.cpp
commands/listcommand.cpp
commands/make/migrationcommand.cpp
# commands/make/projectcommand.cpp
commands/migrations/freshcommand.cpp
commands/migrations/installcommand.cpp
commands/migrations/migratecommand.cpp
commands/migrations/refreshcommand.cpp
commands/migrations/resetcommand.cpp
commands/migrations/rollbackcommand.cpp
commands/migrations/statuscommand.cpp
concerns/callscommands.cpp
concerns/confirmable.cpp
concerns/interactswithio.cpp
concerns/printsoptions.cpp
exceptions/tomlogicerror.cpp
exceptions/tomruntimeerror.cpp
migrationcreator.cpp
migrationrepository.cpp
migrator.cpp
terminal.cpp
)
list(SORT headers)
list(SORT sources)
list(TRANSFORM headers PREPEND "${CMAKE_SOURCE_DIR}/tom/include/tom/")
list(TRANSFORM sources PREPEND "${CMAKE_SOURCE_DIR}/tom/src/tom/")
set(${out_headers} ${headers} PARENT_SCOPE)
set(${out_sources} ${sources} PARENT_SCOPE)
endfunction()

View File

@@ -3,7 +3,7 @@ include(TinyResourceAndManifest)
# Configure passed auto test
function(tiny_configure_test name)
set(options INCLUDE_MODELS)
set(options INCLUDE_MIGRATIONS INCLUDE_MODELS)
cmake_parse_arguments(PARSE_ARGV 1 TINY "${options}" "" "")
if(DEFINED TINY_UNPARSED_ARGUMENTS)
@@ -54,6 +54,12 @@ ${TINY_UNPARSED_ARGUMENTS}")
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>"
)
if(TINY_INCLUDE_MIGRATIONS)
target_include_directories(${name} PRIVATE
"$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/tests/database>"
)
endif()
if(TINY_INCLUDE_MODELS)
target_include_directories(${name} PRIVATE
"$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/tests/models>"
@@ -62,8 +68,6 @@ ${TINY_UNPARSED_ARGUMENTS}")
target_link_libraries(${name}
PRIVATE
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Sql
Qt${QT_VERSION_MAJOR}::Test
${TinyOrm_ns}::${TinyUtils_target}
${TinyOrm_ns}::${TinyOrm_target}

3
examples/CMakeLists.txt Normal file
View File

@@ -0,0 +1,3 @@
if(TOM_EXAMPLE)
add_subdirectory(tom)
endif()

7
examples/examples.pro Normal file
View File

@@ -0,0 +1,7 @@
TEMPLATE = subdirs
tom_example:!disable_tom {
SUBDIRS += tom
!build_pass: message("Build the tom example.")
}

View File

@@ -0,0 +1,74 @@
# Tom command-line application example
# ---
# Initialize Project Version
# ---
include(TinyHelpers)
tiny_read_version(TINY_VERSION
TINY_VERSION_MAJOR TINY_VERSION_MINOR TINY_VERSION_PATCH TINY_VERSION_TWEAK
VERSION_HEADER "${CMAKE_SOURCE_DIR}/tom/include/tom/version.hpp"
PREFIX TINYTOM
HEADER_FOR "${TomExample_ns}"
)
# Basic project
# ---
project(${TomExample_ns}
DESCRIPTION "Tom console for TinyORM"
HOMEPAGE_URL "https://silverqx.github.io/TinyORM/"
LANGUAGES CXX
VERSION ${TINY_VERSION}
)
# Tom command-line application
# ---
add_executable(${TomExample_target}
main.cpp
)
add_executable(${TomExample_ns}::${TomExample_target} ALIAS ${TomExample_target})
# Tom command-line application specific configuration
# ---
set_target_properties(${TomExample_target}
PROPERTIES
C_VISIBILITY_PRESET "hidden"
CXX_VISIBILITY_PRESET "hidden"
VISIBILITY_INLINES_HIDDEN YES
VERSION ${PROJECT_VERSION}
)
target_include_directories(${TomExample_target} PRIVATE
"$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/tests/database>"
)
# Tom command-line application defines
# ---
target_compile_definitions(${TomExample_target}
PRIVATE
PROJECT_TOMEXAMPLE
)
# Windows resource and manifest files
# ---
# Find icons, tom/version.hpp, and Windows manifest file for MinGW
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
tiny_set_rc_flags("-I \"${CMAKE_SOURCE_DIR}/tom/resources\"")
endif()
include(TinyResourceAndManifest)
tiny_resource_and_manifest(${TomExample_target}
OUTPUT_DIR "${TINY_BUILD_GENDIR}/tmp/"
RESOURCES_DIR "${CMAKE_SOURCE_DIR}/tom/resources"
)
# Resolve and link dependencies
# ---
# Unconditional dependencies
target_link_libraries(${TomExample_target} PRIVATE ${TinyOrm_ns}::${TinyOrm_target})

View File

@@ -0,0 +1,39 @@
# Migrations header files
# ---
# Tests' migrations as example migrations
include($$TINYORM_SOURCE_TREE/tests/database/migrations.pri)
# Or include yours migrations
#include(/home/xyz/your_project/database/migrations.pri)
# Dependencies include and library paths
# ---
# MinGW
win32-g++|win32-clang-g++ {
# Enable ccache wrapper
CONFIG *= tiny_ccache
# Includes
# tabulate
INCLUDEPATH += $$quote(C:/msys64/home/xyz/vcpkg/installed/x64-mingw-dynamic/include/)
QMAKE_CXXFLAGS += -isystem $$shell_quote(C:/msys64/home/xyz/vcpkg/installed/x64-mingw-dynamic/include/)
# Use faster linker
# CONFIG *= use_lld_linker does not work on MinGW
QMAKE_LFLAGS *= -fuse-ld=lld
}
else:win32-msvc {
# Includes
# range-v3 and tabulate
INCLUDEPATH += $$quote(E:/xyz/vcpkg/installed/x64-windows/include/)
}
else:unix {
# Includes
# range-v3 and tabulate
QMAKE_CXXFLAGS += -isystem $$shell_quote(/home/xyz/vcpkg/installed/x64-linux/include/)
# Use faster linkers
clang: CONFIG *= use_lld_linker
else: CONFIG *= use_gold_linker
}

99
examples/tom/main.cpp Normal file
View File

@@ -0,0 +1,99 @@
#include <orm/db.hpp>
#include <tom/application.hpp>
#include "migrations/2014_10_12_000000_create_posts_table.hpp"
#include "migrations/2014_10_12_100000_add_factor_column_to_posts_table.hpp"
#include "migrations/2014_10_12_200000_create_properties_table.hpp"
#include "migrations/2014_10_12_300000_create_phones_table.hpp"
using Orm::Constants::H127001;
using Orm::Constants::P3306;
using Orm::Constants::QMYSQL;
using Orm::Constants::SYSTEM;
using Orm::Constants::UTF8MB4;
using Orm::Constants::charset_;
using Orm::Constants::collation_;
using Orm::Constants::database_;
using Orm::Constants::driver_;
using Orm::Constants::engine_;
using Orm::Constants::host_;
using Orm::Constants::InnoDB;
using Orm::Constants::isolation_level;
using Orm::Constants::options_;
using Orm::Constants::password_;
using Orm::Constants::port_;
using Orm::Constants::prefix_;
using Orm::Constants::prefix_indexes;
using Orm::Constants::strict_;
using Orm::Constants::timezone_;
using Orm::Constants::username_;
using Orm::DatabaseManager;
using Orm::DB;
using TomApplication = Tom::Application;
using namespace Migrations; // NOLINT(google-build-using-namespace)
/*! Build the database manager instance and add a database connection. */
std::shared_ptr<DatabaseManager> setupManager();
/*! c++ main function. */
int main(int argc, char *argv[])
{
try {
// Ownership of the shared_ptr()
auto db = setupManager();
return TomApplication(argc, argv, db, "TOM_EXAMPLE_ENV")
.migrations<
_2014_10_12_000000_create_posts_table,
_2014_10_12_100000_add_factor_column_to_posts_table,
_2014_10_12_200000_create_properties_table,
_2014_10_12_300000_create_phones_table>()
// Fire it up 🔥🚀✨
.run();
} catch (const std::exception &e) {
TomApplication::logException(e);
}
return EXIT_FAILURE;
}
std::shared_ptr<DatabaseManager> setupManager()
{
// Ownership of the shared_ptr()
return DB::create({
{driver_, QMYSQL},
{host_, qEnvironmentVariable("DB_MYSQL_HOST", H127001)},
{port_, qEnvironmentVariable("DB_MYSQL_PORT", P3306)},
{database_, qEnvironmentVariable("DB_MYSQL_DATABASE", "")},
{username_, qEnvironmentVariable("DB_MYSQL_USERNAME", "")},
{password_, qEnvironmentVariable("DB_MYSQL_PASSWORD", "")},
{charset_, qEnvironmentVariable("DB_MYSQL_CHARSET", UTF8MB4)},
{collation_, qEnvironmentVariable("DB_MYSQL_COLLATION",
QStringLiteral("utf8mb4_0900_ai_ci"))},
{timezone_, SYSTEM},
{prefix_, ""},
{prefix_indexes, true},
{strict_, true},
{isolation_level, QStringLiteral("REPEATABLE READ")},
{engine_, InnoDB},
{options_, QVariantHash()},
},
QLatin1String("tinyorm_tom"));
}
/* Alternative syntax to instantiate migration classes. */
// return TomApplication(argc, argv, db, "TOM_EXAMPLE_ENV",
// {
// std::make_shared<_2014_10_12_000000_create_posts_table>(),
// std::make_shared<_2014_10_12_100000_add_factor_column_to_posts_table>(),
// std::make_shared<_2014_10_12_200000_create_properties_table>(),
// std::make_shared<_2014_10_12_300000_create_phones_table>(),
// })
// // Fire it up 🔥🚀✨
// .run();

53
examples/tom/tom.pro Normal file
View File

@@ -0,0 +1,53 @@
QT *= core sql
QT -= gui
TEMPLATE = app
TARGET = tom
# TinyTom example application specific configuration
# ---
CONFIG *= console
include($$TINYORM_SOURCE_TREE/qmake/tom.pri)
# TinyTom example application defines
# ---
DEFINES += PROJECT_TOMEXAMPLE
# TinyTom defines
# ---
# this define is not provided in the qmake/tom.pri
# Enable code needed by tests (modify the migrate:status command for tests need)
build_tests: \
DEFINES *= TINYTOM_TESTS_CODE
# TinyTom example application header and source files
# ---
SOURCES += $$PWD/main.cpp
# Deployment
# ---
win32-msvc:CONFIG(debug, debug|release) {
win32-msvc: target.path = C:/optx64/$${TARGET}
# else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
}
# User Configuration
# ---
exists(conf.pri): \
include(conf.pri)
#else:is_vcpkg_build: \
# include(../qmake/vcpkgconf.pri)
else: \
error( "'conf.pri' for 'tom' example project does not exist. See an example\
configuration in 'examples/tom/conf.pri.example' or call 'vcpkg install'\
in the project's root." )

View File

@@ -126,6 +126,9 @@ headersList += \
$$PWD/orm/tiny/types/connectionoverride.hpp \
$$PWD/orm/tiny/types/syncchanges.hpp \
$$PWD/orm/tiny/utils/attribute.hpp \
!disable_orm|!disable_tom: \
headersList += \
$$PWD/orm/tiny/utils/string.hpp \
HEADERS += $$sorted(headersList)

View File

@@ -9,7 +9,7 @@ TINY_SYSTEM_HEADER
#include "orm/macros/commonnamespace.hpp"
#include "orm/macros/export.hpp"
// CUR check this on clean project silverqx
// CUR tom, check this on clean project silverqx
// Include the blueprint here so a user doesn't have to
#include "orm/schema/blueprint.hpp"

View File

@@ -1338,3 +1338,13 @@ TINYORM_END_COMMON_NAMESPACE
// CUR tom, add tabulate to comments where range-v3 is, all checked, only docs left silverqx
// CUR tom, verify -isystem $$shell_quote() updated in docs silverqx
// CUR tom, tom/conf.pri is used by who silverqx
// CUR cmake, add messages about Building tom example, tests and ORM silverqx
// CUR docs, remove target_link_libs() for transitive dependencies silverqx
// CUR tests, move version test outside of the orm/ folder silverqx
// CUR tom docs, write documentation silverqx
// CUR tom, build on mingw, linux, build without pch and all common tasks that should run from time to time silverqx
// CUR tom, update docs target_link_library() https://discourse.cmake.org/t/explicitly-link-against-public-interface-dependencies/5484/2 silverqx
// CUR tom, provide somehow a custom path to migrations for the tom example, so the tom example can be used like real migration app silverqx
// CUR tom, don't modify migrate:status command, rather extend it and add possibility to only call it through Application::runWithArguments() (this secure to not be able to call it from the cmd. line), do not show it in the list or help command output silverqx
// CUR tom, think about remove TINYTOM_NO/_DEBUG and TINYTOM_TESTS_CODE and use TINYORM_ defines instead silverqx
// BUG rc file © encoding silverqx

View File

@@ -5,6 +5,7 @@
#if defined __cplusplus
/* Add C++ includes here */
#include <QDateTime>
#include <QDebug>
#include <QHash>
#include <QMap>
#include <QSharedPointer>
@@ -38,6 +39,7 @@
#include <thread>
#include <tuple>
#include <type_traits>
#include <typeindex>
#include <typeinfo>
#include <unordered_map>
#include <unordered_set>

View File

@@ -30,3 +30,11 @@ CONFIG(release, debug|release): \
# Log queries with a time measurement in debug build
CONFIG(release, debug|release): \
DEFINES *= TINYORM_NO_DEBUG_SQL
# TinyTom related defines
# ---
# Release build
CONFIG(release, debug|release): DEFINES += TINYTOM_NO_DEBUG
# Debug build
CONFIG(debug, debug|release): DEFINES *= TINYTOM_DEBUG

View File

@@ -30,11 +30,25 @@ DEFINES *= QT_NO_CAST_FROM_BYTEARRAY
DEFINES *= QT_USE_QSTRINGBUILDER
DEFINES *= QT_STRICT_ITERATORS
# TinyORM configuration
# ---
# Use extern constants for shared build
CONFIG(shared, dll|shared|static|staticlib) | \
CONFIG(dll, dll|shared|static|staticlib): \
# Support override because inline_constants can be used in the shared build too
!inline_constants: \
CONFIG += extern_constants
# Archive library build (static build)
else: \
CONFIG += inline_constants
# TinyORM defines
# ---
# Release build
CONFIG(release, debug|release): DEFINES *= TINYORM_NO_DEBUG
CONFIG(release, debug|release): DEFINES += TINYORM_NO_DEBUG
# Debug build
CONFIG(debug, debug|release): DEFINES *= TINYORM_DEBUG
@@ -42,9 +56,27 @@ CONFIG(debug, debug|release): DEFINES *= TINYORM_DEBUG
mysql_ping: DEFINES *= TINYORM_MYSQL_PING
# Log queries with a time measurement
CONFIG(release, debug|release): DEFINES *= TINYORM_NO_DEBUG_SQL
CONFIG(release, debug|release): DEFINES += TINYORM_NO_DEBUG_SQL
CONFIG(debug, debug|release): DEFINES *= TINYORM_DEBUG_SQL
# Enable code needed by tests, eg. connection overriding in the Model
!disable_orm:build_tests: \
DEFINES *= TINYORM_TESTS_CODE
# TinyTom related defines
# ---
!disable_tom {
# Release build
CONFIG(release, debug|release): DEFINES += TINYTOM_NO_DEBUG
# Debug build
CONFIG(debug, debug|release): DEFINES *= TINYTOM_DEBUG
# Enable code needed by tests (modify the migrate:status command for tests need)
build_tests: \
DEFINES *= TINYTOM_TESTS_CODE
}
# Platform specific configuration
# ---
win32: include(winconf.pri)
@@ -62,17 +94,3 @@ debug_and_release: {
TINY_RELEASE_TYPE = $$quote(/debug)
}
else: TINY_RELEASE_TYPE =
# Other
# ---
# Use extern constants for shared build
CONFIG(shared, dll|shared|static|staticlib) | \
CONFIG(dll, dll|shared|static|staticlib): \
# Support override because inline_constants can be used in the shared build too
!inline_constants: \
CONFIG += extern_constants
# Archive library build (static build)
else: \
CONFIG += inline_constants

View File

@@ -14,16 +14,12 @@ defineTest(tiny_resource_and_manifest) {
isEmpty(2): resourcesFolder = $$absolute_path(resources, $$_PRO_FILE_PWD_)
else: resourcesFolder = $$absolute_path($$2)
# Processing of RC file and manifest file for the test?
defined(3, var):$$3: isTest = true
else: isTest = false
# Target's extension
contains(TEMPLATE, ".*app"): targetExt = ".exe"
else:contains(TEMPLATE, ".*lib"): targetExt = ".dll"
# Windows Resource file
rcFile = $$tiny_configure_cmake_rc($$resourcesFolder, $$targetExt, $$isTest)
rcFile = $$tiny_configure_cmake_rc($$resourcesFolder, $$targetExt, $$3, $$4)
# Needed in the RC file, MinGW does not define _DEBUG macro
mingw:CONFIG(debug, debug|release): DEFINES += _DEBUG
@@ -34,8 +30,8 @@ defineTest(tiny_resource_and_manifest) {
# Manifest file
CONFIG -= embed_manifest_dll
# Use the same manifest file for all tests
$$isTest: manifestBasename = TinyTest
# Allow to pass RC/manifest file basename as 3. argument (default is a $$TARGET value)
defined(3, var):!isEmpty(3): manifestBasename = $$3
else: manifestBasename = $$TARGET
# On MSVC use EMBED and on MinGW injected through the RC file
@@ -56,7 +52,7 @@ defineTest(tiny_resource_and_manifest) {
# RC file than manage two practically the same files.
defineReplace(tiny_configure_cmake_rc) {
# All tests use the same test.rc.in file
defined(3, var):$$3: rcBasename = TinyTest
defined(3, var):!isEmpty(3): rcBasename = $$3
else: rcBasename = $$TARGET
rcFile = $$absolute_path($$1/$${rcBasename}.rc.in)
@@ -75,8 +71,9 @@ defineReplace(tiny_configure_cmake_rc) {
tiny_manifest_basename = $$rcBasename
# The same logic for the substitution token, is 'test' for tests and $$TARGET instead
token = $$rcBasename
# Allow to pass a custom token as 4. argument (default is a $$TARGET value)
defined(4, var):!isEmpty(4): token = $$4
else: token = $$rcBasename
rcFileContent = $$cat($$rcFile, blob)

View File

@@ -1,11 +1,29 @@
# Find version numbers in the version header file and assign them to the
# <TARGET>_VERSION_<MAJOR,MINOR,PATCH,TWEAK> and also to the VERSION variable.
defineTest(tiny_version_numbers) {
versionHeader = $$find(HEADERS, "(?:.*\/)?version\.h(?:pp)?$")
exists(versionHeader)|isEmpty(versionHeader) {
# version.hpp for the TinyORM library project
contains(_PRO_FILE_, ".*\/src\/src\.pro$"): \
versionHeader = $$absolute_path("../include/orm/version.hpp")
# version.hpp for the tom example project
else:contains(_PRO_FILE_, ".*\/examples\/tom\/tom\.pro$"): \
versionHeader = $$absolute_path("../tom/include/tom/version.hpp")
else {
# Try to find in the HEADERS
versionHeader = $$find(HEADERS, "(?:.*\/)?version\.h(?:pp)?$")
# Try to find on the INCLUDEPATH
isEmpty(versionHeader) {
versionHeaders = $$files("$$clean_path($$first(INCLUDEPATH))/version.hpp", \
true)
versionHeader = $$first(versionHeaders)
}
}
exists(versionHeader)|isEmpty(versionHeader): \
error( "HEADERS does not contain a version header file version.hpp, needed\
in the tiny_version_numbers.prf." )
}
versionFileContent = $$cat($$quote($$absolute_path($$versionHeader)), lines)
versionTokens = MAJOR MINOR BUGFIX BUILD STATUS
@@ -38,9 +56,8 @@ defineTest(tiny_version_numbers) {
$$hasStatus: versionStatus = $$take_last(versionList)
# Use 3 numbers version on other platforms
!win32 {
!win32: \
$$take_last(versionList)
}
VERSION = $$join(versionList, '.')$$versionStatus
export(VERSION)

View File

@@ -0,0 +1,2 @@
CONFIG *= tom_example
DEFINES *= TINYORM_TOM_EXAMPLE

54
qmake/tom.pri Normal file
View File

@@ -0,0 +1,54 @@
TINYORM_SOURCE_TREE = $$clean_path($$quote($$PWD/..))
TINYTOM_SOURCE_TREE = $$quote($$TINYORM_SOURCE_TREE/tom)
# Qt Common Configuration
# ---
QT *= core sql
CONFIG *= link_prl
include($$TINYORM_SOURCE_TREE/qmake/common.pri)
# Configure TinyORM library
# ---
# everything other is defined in the qmake/common.pri
# Link with the shared library
CONFIG(shared, dll|shared|static|staticlib) | \
CONFIG(dll, dll|shared|static|staticlib): \
DEFINES *= TINYORM_LINKING_SHARED
# File version
# ---
# Find version numbers in the version header file and assign them to the
# <TARGET>_VERSION_<MAJOR,MINOR,PATCH,TWEAK> and also to the VERSION variable.
load(tiny_version_numbers)
tiny_version_numbers()
# Windows resource and manifest files
# ---
# Find version.hpp
tinyRcIncludepath = $$quote($$TINYTOM_SOURCE_TREE/include/)
# Find Windows manifest
mingw: tinyRcIncludepath += $$quote($$TINYTOM_SOURCE_TREE/resources/)
load(tiny_resource_and_manifest)
tiny_resource_and_manifest( \
$$tinyRcIncludepath, $$TINYTOM_SOURCE_TREE/resources, tom, TomExample \
)
# Link against TinyORM library
# ---
INCLUDEPATH *= \
$$quote($$TINYORM_SOURCE_TREE/include/) \
$$quote($$TINYTOM_SOURCE_TREE/include/) \
!isEmpty(TINYORM_BUILD_TREE): \
exists($$TINYORM_BUILD_TREE): {
LIBS += $$quote(-L$$TINYORM_BUILD_TREE/src$${TINY_RELEASE_TYPE}/)
LIBS += -lTinyOrm
}

View File

@@ -1,4 +1,4 @@
#pragma code_page(65001) // UTF-8
//#pragma code_page(65001) // UTF-8
IDI_ICON1 ICON "icons/@TinyOrm_target@.ico"

View File

@@ -61,6 +61,9 @@ sourcesList += \
$$PWD/orm/tiny/tinytypes.cpp \
$$PWD/orm/tiny/types/syncchanges.cpp \
$$PWD/orm/tiny/utils/attribute.cpp \
!disable_orm|!disable_tom: \
sourcesList += \
$$PWD/orm/tiny/utils/string.cpp \
SOURCES += $$sorted(sourcesList)

View File

@@ -39,10 +39,6 @@ CONFIG(shared, dll|shared|static|staticlib) | \
CONFIG(dll, dll|shared|static|staticlib): \
DEFINES *= TINYORM_BUILDING_SHARED
# Enable code needed by tests, eg. connection overriding in the Model
build_tests: \
DEFINES *= TINYORM_TESTS_CODE
# TinyORM library header and source files
# ---
@@ -50,6 +46,14 @@ build_tests: \
include(../include/include.pri)
include(src.pri)
# TinyTom header and source files
# ---
!disable_tom {
include(../tom/include/include.pri)
include(../tom/src/src.pri)
}
# File version
# ---
@@ -85,8 +89,16 @@ win32-msvc:CONFIG(debug, debug|release) {
# Some info output
# ---
CONFIG(debug, debug|release):!build_pass: message( "Project is built in DEBUG mode." )
CONFIG(release, debug|release):!build_pass: message( "Project is built in RELEASE mode." )
!build_pass {
CONFIG(debug, debug|release): message( "Project is built in DEBUG mode." )
CONFIG(release, debug|release): message( "Project is built in RELEASE mode." )
!disable_orm: message("Build ORM-related source code.")
else: message("Disable ORM-related source code (build the query builder \
only).")
mysql_ping: message("Enable MySQL ping on Orm::MySqlConnection.")
}
# User Configuration
# ---

View File

@@ -1,4 +1,4 @@
#pragma code_page(65001) // UTF-8
//#pragma code_page(65001) // UTF-8
//IDI_ICON1 ICON "icons/@TinyUtils_target@.ico"

View File

@@ -6,6 +6,7 @@
/* Add C++ includes here */
//#include <QCoreApplication>
#include <QDateTime>
//#include <QDebug>
#include <QHash>
#include <QMap>
#include <QSharedPointer>
@@ -38,7 +39,8 @@
//#include <thread>
#include <tuple>
//#include <type_traits>
#include <typeinfo>
//#include <typeindex>
//#include <typeinfo>
//#include <unordered_map>
//#include <vector>
#endif

View File

@@ -1 +1,5 @@
add_subdirectory(orm)
if(TOM)
add_subdirectory(tom)
endif()

View File

@@ -1,4 +1,12 @@
TEMPLATE = subdirs
SUBDIRS = \
subdirsList = \
orm \
!disable_tom: \
subdirsList += \
tom \
SUBDIRS = $$sorted(subdirsList)
unset(subdirsList)

View File

@@ -0,0 +1 @@
add_subdirectory(migrate)

View File

@@ -0,0 +1,18 @@
# migrate auto test
# ---
set(migrate_ns migrate)
set(migrate_target ${migrate_ns})
project(${migrate_ns}
LANGUAGES CXX
)
add_executable(${migrate_target}
tst_migrate.cpp
)
add_test(NAME ${migrate_target} COMMAND ${migrate_target})
include(TinyTestCommon)
tiny_configure_test(${migrate_target} INCLUDE_MIGRATIONS)

View File

@@ -0,0 +1,5 @@
include($$TINYORM_SOURCE_TREE/tests/qmake/common.pri)
include($$TINYORM_SOURCE_TREE/tests/qmake/TinyUtils.pri)
include($$TINYORM_SOURCE_TREE/tests/database/migrations.pri)
SOURCES += tst_migrate.cpp

View File

@@ -0,0 +1,585 @@
#include <QCoreApplication>
#include <QtTest>
#include "tom/application.hpp"
#include "tom/commands/migrations/statuscommand.hpp"
#include "databases.hpp"
#include "migrations/2014_10_12_000000_create_posts_table.hpp"
#include "migrations/2014_10_12_100000_add_factor_column_to_posts_table.hpp"
#include "migrations/2014_10_12_200000_create_properties_table.hpp"
#include "migrations/2014_10_12_300000_create_phones_table.hpp"
using namespace Migrations; // NOLINT(google-build-using-namespace)
using TomApplication = Tom::Application;
using Tom::Commands::Migrations::StatusCommand;
using TestUtils::Databases;
class tst_Migrate : public QObject
{
Q_OBJECT
public:
/*! Alias for the test output row. */
using StatusRow = StatusCommand::StatusRow;
/*! Type used for comparing results of the status command. */
using Status = std::vector<StatusRow>;
private slots:
void initTestCase();
void cleanup() const;
void reset() const;
void migrate() const;
void migrate_Step() const;
void rollback_OnMigrate() const;
void rollback_OnMigrateWithStep() const;
void rollback_Step_OnMigrate() const;
void rollback_Step_OnMigrateWithStep() const;
void refresh_OnMigrate() const;
void refresh_OnMigrateWithStep() const;
void refresh_Step() const;
void refresh_StepMigrate() const;
void refresh_Step_StepMigrate() const;
// NOLINTNEXTLINE(readability-redundant-access-specifiers)
private:
/*! Prepare arguments and invoke runCommand(). */
[[nodiscard]] int
invokeCommand(const QString &name, std::vector<const char *> &&arguments = {}) const;
/*! Create a tom application instance and invoke the given command. */
int runCommand(int &argc, const std::vector<const char *> &argv) const;
/*! Invoke the status command to obtain results. */
inline int invokeTestStatusCommand() const;
/*! Get result of the last status command. */
Status status() const;
/*! Create a status object for comparing with the result of the status(). */
Status createStatus(std::initializer_list<StatusRow> rows) const;
/*! Create a status object to be equal after complete rollback. */
Status createResetStatus() const;
/*! Connection name used in this test case. */
QString m_connection {};
};
/*! Alias for the test output row. */
using Status = tst_Migrate::Status;
/*! Type used for comparing results of the status command. */
using StatusRow = tst_Migrate::StatusRow;
/* Extracted common code to re-use. */
namespace
{
// Status
inline const auto *Yes = "Yes";
inline const auto *No = "No";
// Batches
inline const auto *s_1 = "1";
inline const auto *s_2 = "2";
inline const auto *s_3 = "3";
inline const auto *s_4 = "4";
// Migration names
inline const auto *
s_2014_10_12_000000_create_posts_table =
"2014_10_12_000000_create_posts_table";
inline const auto *
s_2014_10_12_100000_add_factor_column_to_posts_table =
"2014_10_12_100000_add_factor_column_to_posts_table";
inline const auto *
s_2014_10_12_200000_create_properties_table =
"2014_10_12_200000_create_properties_table";
inline const auto *
s_2014_10_12_300000_create_phones_table =
"2014_10_12_300000_create_phones_table";
// Fully migrated w/o --step
inline const std::initializer_list<StatusRow>
FullyMigrated = {
{Yes, s_2014_10_12_000000_create_posts_table, s_1},
{Yes, s_2014_10_12_100000_add_factor_column_to_posts_table, s_1},
{Yes, s_2014_10_12_200000_create_properties_table, s_1},
{Yes, s_2014_10_12_300000_create_phones_table, s_1},
};
// Fully migrated with --step
inline const std::initializer_list<StatusRow>
FullyStepMigrated = {
{Yes, s_2014_10_12_000000_create_posts_table, s_1},
{Yes, s_2014_10_12_100000_add_factor_column_to_posts_table, s_2},
{Yes, s_2014_10_12_200000_create_properties_table, s_3},
{Yes, s_2014_10_12_300000_create_phones_table, s_4},
};
} // namespace
/* private slots */
void tst_Migrate::initTestCase()
{
m_connection = Databases::createConnection(Databases::MYSQL);
if (m_connection.isEmpty())
QSKIP(QStringLiteral("%1 autotest skipped, environment variables "
"for '%2' connection have not been defined.")
.arg("tst_Migrate", Databases::MYSQL).toUtf8().constData(), );
/* Modify the migrate:status command to not output a status table to the console but
instead return a result as the vector, this vector is then used for comparing
results. */
TomApplication::enableInUnitTests();
}
void tst_Migrate::cleanup() const
{
/* All test methods need this except for two of them (reset and I don't remember
second), I will not implement special logic to skip this for these two methods. */
{
auto exitCode = invokeCommand("migrate:reset");
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
auto exitCode = invokeTestStatusCommand();
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createResetStatus(), status());
}
}
void tst_Migrate::reset() const
{
{
auto exitCode = invokeCommand("migrate:reset");
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
auto exitCode = invokeTestStatusCommand();
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createResetStatus(), status());
}
}
void tst_Migrate::migrate() const
{
{
auto exitCode = invokeCommand("migrate");
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
auto exitCode = invokeTestStatusCommand();
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyMigrated), status());
}
}
void tst_Migrate::migrate_Step() const
{
{
auto exitCode = invokeCommand("migrate", {"--step"});
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
auto exitCode = invokeTestStatusCommand();
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyStepMigrated), status());
}
}
void tst_Migrate::rollback_OnMigrate() const
{
{
auto exitCode = invokeCommand("migrate");
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
auto exitCode = invokeTestStatusCommand();
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyMigrated), status());
}
// rollback on previous migrate w/o --step
{
auto exitCode = invokeCommand("migrate:rollback");
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
auto exitCode = invokeTestStatusCommand();
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createResetStatus(), status());
}
}
void tst_Migrate::rollback_OnMigrateWithStep() const
{
{
auto exitCode = invokeCommand("migrate", {"--step"});
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
auto exitCode = invokeTestStatusCommand();
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyStepMigrated), status());
}
// rollback on previous migrate with --step
{
auto exitCode = invokeCommand("migrate:rollback");
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
auto exitCode = invokeTestStatusCommand();
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus({
{Yes, s_2014_10_12_000000_create_posts_table, s_1},
{Yes, s_2014_10_12_100000_add_factor_column_to_posts_table, s_2},
{Yes, s_2014_10_12_200000_create_properties_table, s_3},
{No, s_2014_10_12_300000_create_phones_table},
}), status());
}
}
void tst_Migrate::rollback_Step_OnMigrate() const
{
{
auto exitCode = invokeCommand("migrate");
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
auto exitCode = invokeTestStatusCommand();
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyMigrated), status());
}
// rollback on previous migrate w/o --step
{
auto exitCode = invokeCommand("migrate:rollback", {"--step=2"});
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
auto exitCode = invokeTestStatusCommand();
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus({
{Yes, s_2014_10_12_000000_create_posts_table, s_1},
{Yes, s_2014_10_12_100000_add_factor_column_to_posts_table, s_1},
{No, s_2014_10_12_200000_create_properties_table},
{No, s_2014_10_12_300000_create_phones_table},
}), status());
}
}
void tst_Migrate::rollback_Step_OnMigrateWithStep() const
{
{
auto exitCode = invokeCommand("migrate", {"--step"});
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
auto exitCode = invokeTestStatusCommand();
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyStepMigrated), status());
}
// rollback on previous migrate with --step
{
auto exitCode = invokeCommand("migrate:rollback", {"--step=2"});
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
auto exitCode = invokeTestStatusCommand();
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus({
{Yes, s_2014_10_12_000000_create_posts_table, s_1},
{Yes, s_2014_10_12_100000_add_factor_column_to_posts_table, s_2},
{No, s_2014_10_12_200000_create_properties_table},
{No, s_2014_10_12_300000_create_phones_table},
}), status());
}
}
void tst_Migrate::refresh_OnMigrate() const
{
{
auto exitCode = invokeCommand("migrate");
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
auto exitCode = invokeTestStatusCommand();
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyMigrated), status());
}
// refresh on previous migrate w/o --step
{
auto exitCode = invokeCommand("migrate:refresh");
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
auto exitCode = invokeTestStatusCommand();
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyMigrated), status());
}
}
void tst_Migrate::refresh_OnMigrateWithStep() const
{
{
auto exitCode = invokeCommand("migrate", {"--step"});
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
auto exitCode = invokeTestStatusCommand();
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyStepMigrated), status());
}
// refresh on previous migrate with --step
{
auto exitCode = invokeCommand("migrate:refresh");
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
auto exitCode = invokeTestStatusCommand();
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyMigrated), status());
}
}
void tst_Migrate::refresh_Step() const
{
{
auto exitCode = invokeCommand("migrate");
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
auto exitCode = invokeTestStatusCommand();
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyMigrated), status());
}
// refresh on previous migrate w/o --step
{
auto exitCode = invokeCommand("migrate:refresh", {"--step=2"});
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
auto exitCode = invokeTestStatusCommand();
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus({
{Yes, s_2014_10_12_000000_create_posts_table, s_1},
{Yes, s_2014_10_12_100000_add_factor_column_to_posts_table, s_1},
{Yes, s_2014_10_12_200000_create_properties_table, s_2},
{Yes, s_2014_10_12_300000_create_phones_table, s_2},
}), status());
}
}
void tst_Migrate::refresh_StepMigrate() const
{
{
auto exitCode = invokeCommand("migrate");
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
auto exitCode = invokeTestStatusCommand();
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyMigrated), status());
}
// refresh on previous migrate w/o --step
{
auto exitCode = invokeCommand("migrate:refresh", {"--step-migrate"});
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
auto exitCode = invokeTestStatusCommand();
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyStepMigrated), status());
}
}
void tst_Migrate::refresh_Step_StepMigrate() const
{
{
auto exitCode = invokeCommand("migrate");
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
auto exitCode = invokeTestStatusCommand();
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyMigrated), status());
}
// refresh on previous migrate w/o --step
{
auto exitCode = invokeCommand("migrate:refresh", {"--step=2", "--step-migrate"});
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
auto exitCode = invokeTestStatusCommand();
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus({
{Yes, s_2014_10_12_000000_create_posts_table, s_1},
{Yes, s_2014_10_12_100000_add_factor_column_to_posts_table, s_1},
{Yes, s_2014_10_12_200000_create_properties_table, s_2},
{Yes, s_2014_10_12_300000_create_phones_table, s_3},
}), status());
}
}
/* private */
int tst_Migrate::invokeCommand(const QString &name,
std::vector<const char *> &&arguments) const
{
static const auto connectionTmpl = QStringLiteral("--database=%1");
// Prepare fake argc and argv
const auto nameArr = name.toUtf8();
// FUTURE tests tom, when the schema builder will support more db drivers, I can run it on all supported connections, code will look like in the tst_querybuilder.cpp, then I will fetch connection name in every test method using QFETCH_GLOBAL() and I will pass this connection name to the invokeCommand(), so I will discard m_connection and will use method parameter connection here silverqx
/* Schema builder is implemented only for the MySQL driver, so I can use m_connection
here as the default connection. */
// DB connection to use
const auto connectionArr = connectionTmpl.arg(m_connection).toUtf8();
std::vector<const char *> argv {
#ifdef _WIN32
"tom.exe",
#else
"tom",
#endif
nameArr.constData(),
connectionArr.constData(),
};
std::ranges::move(arguments, std::back_inserter(argv));
int argc = static_cast<int>(argv.size());
return runCommand(argc, argv);
}
int tst_Migrate::runCommand(int &argc, const std::vector<const char *> &argv) const
{
try {
// env. should be always development so passed {} for env. name
return TomApplication(argc, const_cast<char **>(argv.data()),
Databases::manager(), {})
.migrations<
_2014_10_12_000000_create_posts_table,
_2014_10_12_100000_add_factor_column_to_posts_table,
_2014_10_12_200000_create_properties_table,
_2014_10_12_300000_create_phones_table>()
// Fire it up 🔥🚀✨
.runWithArguments({argv.cbegin(), argv.cend()});
} catch (const std::exception &e) {
TomApplication::logException(e, true);
}
return EXIT_FAILURE;
}
int tst_Migrate::invokeTestStatusCommand() const
{
return invokeCommand("migrate:status");
}
Status tst_Migrate::status() const
{
return TomApplication::status();
}
Status tst_Migrate::createStatus(std::initializer_list<StatusRow> rows) const
{
return Status({rows});
}
Status tst_Migrate::createResetStatus() const
{
return Status({
{No, s_2014_10_12_000000_create_posts_table},
{No, s_2014_10_12_100000_add_factor_column_to_posts_table},
{No, s_2014_10_12_200000_create_properties_table},
{No, s_2014_10_12_300000_create_phones_table},
});
}
QTEST_MAIN(tst_Migrate)
#include "tst_migrate.moc"

View File

@@ -0,0 +1,4 @@
TEMPLATE = subdirs
SUBDIRS = \
migrate \

View File

@@ -21,6 +21,15 @@ if(BUILD_SHARED_LIBS)
target_compile_definitions(version PRIVATE TINYTEST_VERSION_IS_SHARED_BUILD)
endif()
if(TOM_EXAMPLE)
target_compile_definitions(version PRIVATE TINYTOM_EXAMPLE)
# To find tom/include/version.hpp
target_include_directories(version PRIVATE
"$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/tom/include>"
)
endif()
target_include_directories(version PRIVATE
"$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/${TINY_BUILD_GENDIR}/include>"
)

View File

@@ -4,5 +4,6 @@
#define TINYTEST_VERSION_TINYORM_PATH "$<TARGET_FILE:@TinyOrm_target@>"
#define TINYTEST_VERSION_TINYUTILS_PATH "$<TARGET_FILE:@TinyUtils_target@>"
#define TINYTEST_VERSION_TOMEXAMPLE_PATH "$<$<TARGET_EXISTS:@TomExample_target@>:$<TARGET_FILE:@TomExample_target@>>"
#endif // TINYTESTS_VERSIONDEBUG_CMAKE_HPP

View File

@@ -4,5 +4,6 @@
#define TINYTEST_VERSION_TINYORM_PATH \"$${TINYTEST_VERSION_TINYORM_PATH}\"
#define TINYTEST_VERSION_TINYUTILS_PATH \"$${TINYTEST_VERSION_TINYUTILS_PATH}\"
#define TINYTEST_VERSION_TOMEXAMPLE_PATH \"$${TINYTEST_VERSION_TOMEXAMPLE_PATH}\"
#endif // TINYTEST_VERSIONDEBUG_QMAKE_HPP

View File

@@ -11,6 +11,10 @@
#include "orm/version.hpp"
// TinyUtils
#include "version.hpp"
// Tom example
#ifdef TINYTOM_EXAMPLE
# include "tom/version.hpp"
#endif
// Used by checkFileVersion_*() tests
#if defined(_WIN32) && defined(TINYTEST_VERSION_IS_SHARED_BUILD)
@@ -22,6 +26,7 @@
#else
# define TINYTEST_VERSION_TINYORM_PATH
# define TINYTEST_VERSION_TINYUTILS_PATH
# define TINYTEST_VERSION_TOMEXAMPLE_PATH
#endif
#if defined(_WIN32)
@@ -35,9 +40,15 @@ class tst_Version : public QObject
private slots:
void versions_TinyOrm() const;
void versions_TinyUtils() const;
#ifdef TINYTOM_EXAMPLE
void versions_TomExample() const;
#endif
void checkFileVersion_TinyOrm() const;
void checkFileVersion_TinyUtils() const;
#ifdef TINYTOM_EXAMPLE
void checkFileVersion_TomExample() const;
#endif
#if defined(_WIN32) && defined(TINYTEST_VERSION_IS_SHARED_BUILD)
// NOLINTNEXTLINE(readability-redundant-access-specifiers)
@@ -125,6 +136,43 @@ void tst_Version::versions_TinyUtils() const
QCOMPARE(TINYUTILS_VERSION, version);
}
#ifdef TINYTOM_EXAMPLE
void tst_Version::versions_TomExample() const
{
// Test types
QCOMPARE(typeid (TINYTOM_VERSION_MAJOR), typeid (int));
QCOMPARE(typeid (TINYTOM_VERSION_MINOR), typeid (int));
QCOMPARE(typeid (TINYTOM_VERSION_BUGFIX), typeid (int));
QCOMPARE(typeid (TINYTOM_VERSION_BUILD), typeid (int));
// Individual version numbers have to be greater than zero
QVERIFY(TINYTOM_VERSION_MAJOR >= 0);
QVERIFY(TINYTOM_VERSION_MINOR >= 0);
QVERIFY(TINYTOM_VERSION_BUGFIX >= 0);
QVERIFY(TINYTOM_VERSION_BUILD >= 0);
// Project and File Version strings
QString versionStr = QString::number(TINYTOM_VERSION_MAJOR) + QChar('.') +
QString::number(TINYTOM_VERSION_MINOR) + QChar('.') +
QString::number(TINYTOM_VERSION_BUGFIX);
QString fileVersionStr = versionStr + QChar('.') +
QString::number(TINYTOM_VERSION_BUILD);
if constexpr (TINYTOM_VERSION_BUILD > 0)
versionStr += QChar('.') + QString::number(TINYTOM_VERSION_BUILD);
versionStr += TINYTOM_VERSION_STATUS;
QCOMPARE(TINYTOM_FILEVERSION_STR, fileVersionStr);
QCOMPARE(TINYTOM_VERSION_STR, versionStr);
QCOMPARE(TINYTOM_VERSION_STR_2, QChar('v') + versionStr);
// Project Version number, to check API compatibility
const auto version = TINYTOM_VERSION_MAJOR * 10000 +
TINYTOM_VERSION_MINOR * 100 +
TINYTOM_VERSION_BUGFIX;
QCOMPARE(TINYTOM_VERSION, version);
}
#endif
void tst_Version::checkFileVersion_TinyOrm() const
{
#if !defined(_WIN32)
@@ -167,6 +215,29 @@ void tst_Version::checkFileVersion_TinyUtils() const
#endif
}
#ifdef TINYTOM_EXAMPLE
void tst_Version::checkFileVersion_TomExample() const
{
#if !defined(_WIN32)
QSKIP("checkFileVersion_*() related tests are supported on MSVC only.", );
#elif !defined(TINYTEST_VERSION_IS_SHARED_BUILD)
QSKIP("checkFileVersion_*() related tests are enabled for shared builds only.", );
#else
const auto fileVersions =
getExeVersionString(Fs::absolutePath(TINYTEST_VERSION_TOMEXAMPLE_PATH));
// Project and File Version strings
const QString versionStr = QString::number(TINYTOM_VERSION_MAJOR) + QChar('.') +
QString::number(TINYTOM_VERSION_MINOR) + QChar('.') +
QString::number(TINYTOM_VERSION_BUGFIX) + QChar('.') +
QString::number(TINYTOM_VERSION_BUILD);
QCOMPARE(fileVersions.productVersion, versionStr);
QCOMPARE(fileVersions.fileVersion, fileVersions.productVersion);
#endif
}
#endif
#if defined(_WIN32) && defined(TINYTEST_VERSION_IS_SHARED_BUILD)
tst_Version::FileVersions
tst_Version::getExeVersionString(const QString &fileName) const

View File

@@ -7,6 +7,9 @@ SOURCES = tst_version.cpp
win32 {
DEFINES += TINYTEST_VERSION_IS_QMAKE
tom_example:!disable_tom: \
DEFINES += TINYTOM_EXAMPLE
CONFIG(shared, dll|shared|static|staticlib) | \
CONFIG(dll, dll|shared|static|staticlib): \
DEFINES += TINYTEST_VERSION_IS_SHARED_BUILD
@@ -15,6 +18,8 @@ win32 {
$$quote($${TINYORM_BUILD_TREE}/src$${TINY_RELEASE_TYPE}/TinyOrm0.dll)
TINYTEST_VERSION_TINYUTILS_PATH = \
$$quote($${TINYORM_BUILD_TREE}/tests/TinyUtils$${TINY_RELEASE_TYPE}/TinyUtils0.dll)
TINYTEST_VERSION_TOMEXAMPLE_PATH = \
$$quote($${TINYORM_BUILD_TREE}/examples/tom$${TINY_RELEASE_TYPE}/tom.exe)
QMAKE_SUBSTITUTES += $$quote(include/versiondebug_qmake.hpp.in)
@@ -22,5 +27,9 @@ win32 {
INCLUDEPATH += $$quote($$OUT_PWD/include/)
# To find tom/include/version.hpp (don't need to include whole qmake/tom.pri)
tom_example:!disable_tom: \
INCLUDEPATH += $$quote($$TINYORM_SOURCE_TREE/tom/include/)
LIBS += -lVersion
}

View File

@@ -0,0 +1,7 @@
INCLUDEPATH += $$PWD
HEADERS += \
$$PWD/migrations/2014_10_12_000000_create_posts_table.hpp \
$$PWD/migrations/2014_10_12_100000_add_factor_column_to_posts_table.hpp \
$$PWD/migrations/2014_10_12_200000_create_properties_table.hpp \
$$PWD/migrations/2014_10_12_300000_create_phones_table.hpp \

View File

@@ -0,0 +1,29 @@
#pragma once
#include <tom/migration.hpp>
namespace Migrations
{
struct _2014_10_12_000000_create_posts_table : Migration
{
/*! Run the migrations. */
void up() const override
{
Schema::create("posts", [](Blueprint &table)
{
table.id();
table.string(NAME);
table.timestamps();
});
}
/*! Reverse the migrations. */
void down() const override
{
Schema::dropIfExists("posts");
}
};
} // namespace Migrations

View File

@@ -0,0 +1,29 @@
#pragma once
#include <tom/migration.hpp>
namespace Migrations
{
struct _2014_10_12_100000_add_factor_column_to_posts_table : Migration
{
/*! Run the migrations. */
void up() const override
{
Schema::table("posts", [](Blueprint &table)
{
table.integer("factor");
});
}
/*! Reverse the migrations. */
void down() const override
{
Schema::table("posts", [](Blueprint &table)
{
table.dropColumn("factor");
});
}
};
} // namespace Migrations

View File

@@ -0,0 +1,29 @@
#pragma once
#include <tom/migration.hpp>
namespace Migrations
{
struct _2014_10_12_200000_create_properties_table : Migration
{
/*! Run the migrations. */
void up() const override
{
Schema::create("properties", [](Blueprint &table)
{
table.id();
table.string(NAME);
table.timestamps();
});
}
/*! Reverse the migrations. */
void down() const override
{
Schema::dropIfExists("properties");
}
};
} // namespace Migrations

View File

@@ -0,0 +1,29 @@
#pragma once
#include <tom/migration.hpp>
namespace Migrations
{
struct _2014_10_12_300000_create_phones_table : Migration
{
/*! Run the migrations. */
void up() const override
{
Schema::create("phones", [](Blueprint &table)
{
table.id();
table.string(NAME);
table.timestamps();
});
}
/*! Reverse the migrations. */
void down() const override
{
Schema::dropIfExists("phones");
}
};
} // namespace Migrations

View File

@@ -5,14 +5,17 @@ CONFIG(shared, dll|shared|static|staticlib) | \
CONFIG(dll, dll|shared|static|staticlib): \
DEFINES += TINYORM_LINKING_SHARED
# Enable code needed by tests, eg connection overriding in the Model
DEFINES *= TINYORM_TESTS_CODE
# TinyORM library headers include path
# ---
INCLUDEPATH += $$quote($$TINYORM_SOURCE_TREE/include/)
# TinyTom include path
# ---
!disable_tom: \
INCLUDEPATH += $$quote($$TINYORM_SOURCE_TREE/tom/include/)
# Link against TinyORM library
# ---

View File

@@ -45,7 +45,7 @@ mingw: tinyRcIncludepath += $$quote($$TINYORM_SOURCE_TREE/tests/resources/)
load(tiny_resource_and_manifest)
tiny_resource_and_manifest($$tinyRcIncludepath, \
$$quote($$TINYORM_SOURCE_TREE/tests/resources), \
true \
TinyTest \
)
unset(tinyRcIncludepath)

View File

@@ -1,4 +1,4 @@
#pragma code_page(65001) // UTF-8
//#pragma code_page(65001) // UTF-8
//IDI_ICON1 ICON "icons/@TinyTest_icon@.ico"

View File

@@ -1,5 +1,38 @@
INCLUDEPATH += $$PWD
HEADERS += \
$$PWD/tom/application.hpp \
$$PWD/tom/commands/command.hpp \
$$PWD/tom/commands/database/wipecommand.hpp \
$$PWD/tom/commands/environmentcommand.hpp \
$$PWD/tom/commands/helpcommand.hpp \
$$PWD/tom/commands/inspirecommand.hpp \
$$PWD/tom/commands/listcommand.hpp \
$$PWD/tom/commands/make/migrationcommand.hpp \
# $$PWD/tom/commands/make/projectcommand.hpp \
$$PWD/tom/commands/make/stubs/migrationstubs.hpp \
$$PWD/tom/commands/make/stubs/projectstubs.hpp \
$$PWD/tom/commands/migrations/freshcommand.hpp \
$$PWD/tom/commands/migrations/installcommand.hpp \
$$PWD/tom/commands/migrations/migratecommand.hpp \
$$PWD/tom/commands/migrations/refreshcommand.hpp \
$$PWD/tom/commands/migrations/resetcommand.hpp \
$$PWD/tom/commands/migrations/rollbackcommand.hpp \
$$PWD/tom/commands/migrations/statuscommand.hpp \
$$PWD/tom/concerns/callscommands.hpp \
$$PWD/tom/concerns/confirmable.hpp \
$$PWD/tom/concerns/interactswithio.hpp \
$$PWD/tom/concerns/printsoptions.hpp \
$$PWD/tom/config.hpp \
$$PWD/tom/exceptions/invalidargumenterror.hpp \
$$PWD/tom/exceptions/invalidtemplateargumenterror.hpp \
$$PWD/tom/exceptions/logicerror.hpp \
$$PWD/tom/exceptions/runtimeerror.hpp \
$$PWD/tom/exceptions/tomerror.hpp \
$$PWD/tom/migration.hpp \
$$PWD/tom/migrationcreator.hpp \
$$PWD/tom/migrationrepository.hpp \
$$PWD/tom/migrator.hpp \
$$PWD/tom/terminal.hpp \
$$PWD/tom/tomtypes.hpp \
$$PWD/tom/version.hpp \

View File

@@ -1,45 +0,0 @@
/* This file can't be included in the project, it's for a precompiled header. */
/* Add C includes here */
#if defined __cplusplus
/* Add C++ includes here */
//#include <QDateTime>
//#include <QHash>
//#include <QMap>
//#include <QSharedPointer>
#include <QStringList>
//#include <QTimer>
//#include <QVariant>
#include <QVector>
#include <algorithm>
#include <array>
#include <bitset>
#include <cassert>
#include <cfloat>
#include <cmath>
#include <complex>
#include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <initializer_list>
#include <iterator>
#include <map>
#include <memory>
#include <mutex>
#include <numeric>
#include <optional>
#include <set>
#include <span>
#include <string>
//#include <thread>
#include <tuple>
#include <type_traits>
#include <typeinfo>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#endif

View File

@@ -1,8 +0,0 @@
# Use Precompiled headers (PCH)
# ---
PRECOMPILED_HEADER = $$quote($$PWD/pch.h)
HEADERS += $$PRECOMPILED_HEADER
precompile_header: \
DEFINES *= USING_PCH

View File

@@ -0,0 +1,264 @@
#pragma once
#ifndef TOM_APPLICATION_HPP
#define TOM_APPLICATION_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include <QCommandLineParser>
#include "tom/config.hpp"
#include "tom/concerns/interactswithio.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Orm
{
class DatabaseManager;
}
namespace Tom {
namespace Commands
{
class Command;
class HelpCommand;
class ListCommand;
} // namespace Commands
namespace Concerns
{
class CallsCommands;
class PrintsOptions;
} // namespace Concerns
class Migration;
class MigrationRepository;
class Migrator;
/*! Tom application. */
class SHAREDLIB_EXPORT Application : public Concerns::InteractsWithIO
{
Q_DISABLE_COPY(Application)
// To access saveOptions()
friend Commands::Command;
// To access createCommand()
friend Commands::HelpCommand;
// To access showVersion()
friend Commands::ListCommand;
// To access m_options
friend Concerns::PrintsOptions;
// To access initializeParser() and createCommand()
friend Concerns::CallsCommands;
/*! Alias for the DatabaseManager. */
using DatabaseManager = Orm::DatabaseManager;
public:
/*! Constructor. */
Application(int &argc, char **argv, std::shared_ptr<DatabaseManager> db,
const char *environmentEnvName = "TOM_ENV",
QString migrationTable = QLatin1String("migrations"),
std::vector<std::shared_ptr<Migration>> migrations = {});
/*! Default destructor. */
inline ~Application() = default;
/*! Instantiate/initialize all migration classes. */
template<typename ...M>
Application &migrations();
/*! Run the tom application. */
int run();
/*! Log exception caught in the main exception handler in a current thread. */
static void logException(const std::exception &e, bool noAnsi = false);
/* Getters / Setters */
/*! Get a current application environment. */
inline const QString &environment() const noexcept;
/*! Get database manager. */
inline DatabaseManager &db() const noexcept;
/*! Get command-line parser. */
inline const QCommandLineParser &parser() const noexcept;
/*! Is the application running in an interactive mode? */
inline bool isInteractive() const noexcept;
/*! Obtain current command-line arguments. */
QStringList arguments() const;
/*! Set the migration repository table name. */
inline Application &migrationTable(QString table);
#ifdef TINYTOM_TESTS_CODE
/*! Alias for the test output row from the status command. */
using StatusRow = std::vector<std::string>;
/*! Get result of the status command (used in auto tests). */
static std::vector<StatusRow> status() noexcept;
/*! Enable logic for unit testing? */
static void enableInUnitTests() noexcept;
#endif
protected:
/*! Alias for the commands' base class. */
using Command = Commands::Command;
/* Application initialization */
#ifdef _WIN32
/*! Prepare console input/output character encoding. */
void initializeConsoleEncoding() const;
#endif
/*! Fix m_argc/m_argv data members if the argv is empty. */
void fixEmptyArgv();
/*! Processes the specified function at application's normal exit. */
void initializeAtExit() const;
/*! Initialize the command-line parser. */
void initializeParser(QCommandLineParser &parser);
/*! Save a copy of application options passed to the Qt's parser. */
const QList<QCommandLineOption> &
saveOptions(QList<QCommandLineOption> &&options);
/*! Prepend command options before common options (used by the help command). */
QList<QCommandLineOption>
prependOptions(QList<QCommandLineOption> &&options);
/* Run command */
/*! Parse current application's command line. */
void parseCommandLine();
/*! Initialize environment value, order:
development -> value from env. variable -> --env command-line argument. */
void initializeEnvironment();
/*! Obtain command name to run. */
QString getCommandName();
/* Early exit during parse command-line */
/*! Display the version information and exits. */
Q_NORETURN void showVersion() const;
/*! Display the version information. */
void printVersion() const;
/*! Invoke the list command. */
Q_NORETURN void showCommandsList(int exitCode);
/*! Exit the application with post routines. */
Q_NORETURN void exitApplication(int exitCode) const;
/* Commands factory */
/*! Alias for an optional command-line parser reference. */
using OptionalParserRef =
std::optional<std::reference_wrapper<QCommandLineParser>>;
/*! Create command by the given name. */
std::unique_ptr<Command>
createCommand(const QString &command, OptionalParserRef parser = std::nullopt,
bool showHelp = true);
/*! Migration repository instance. */
std::shared_ptr<MigrationRepository> createMigrationRepository();
/*! Migrator instance. */
std::shared_ptr<Migrator> createMigrator();
/* Others */
/*! Get all supported commands list (used by the list command). */
const std::vector<std::shared_ptr<Command>> &createCommandsVector();
/*! Get all supported commands' names. */
const std::vector<const char *> &commandNames() const;
/*! Get arguments list from the m_argv array. */
QStringList prepareArguments() const;
/*! Current application argc. */
int &m_argc;
/*! Current application argv. */
char **m_argv;
/*! DatabaseManager instance. */
std::shared_ptr<DatabaseManager> m_db;
/*! The migration repository instance. */
std::shared_ptr<MigrationRepository> m_repository = nullptr;
/*! The migrator service instance. */
std::shared_ptr<Migrator> m_migrator = nullptr;
/* Only one instance can exist in the whole application, auto tests create their
own QCoreApplication instance so this has to be excluded. */
#ifndef TINYTOM_TESTS_CODE
/*! Qt's application instance. */
QCoreApplication m_qtApplication;
/*! Determine whether the TomApplication has its own QCoreApplication instance. */
bool hasQtApplication = true;
#else
/*! Determine whether the TomApplication has its own QCoreApplication instance. */
bool hasQtApplication = false;
#endif
/*! Command line parser. */
QCommandLineParser m_parser {};
/*! Current environment. */
QString m_environment = QStringLiteral("development");
/*! Environment variable name that holds a current environment value. */
const char *m_environmentEnvName;
/*! Migration repository table name. */
QString m_migrationTable;
/*! Migrations vector to process. */
std::vector<std::shared_ptr<Migration>> m_migrations;
/*! Is this input means interactive? */
bool m_interactive = true;
/*! Application options. */
QList<QCommandLineOption> m_options {};
/* Auto tests helpers */
#ifdef TINYTOM_TESTS_CODE
public:
/*! Run the tom application with the given arguments (used in auto tests). */
int runWithArguments(QStringList &&arguments);
#endif
};
/* public */
template<typename ...Migrations>
Application &Application::migrations()
{
m_migrations = {std::make_shared<Migrations>()...};
// Correct sort order is checked in the Migrator::createMigrationNamesMap()
return *this;
}
/* Getters / Setters */
const QString &Application::environment() const noexcept
{
return m_environment;
}
Application::DatabaseManager &Application::db() const noexcept
{
return *m_db;
}
const QCommandLineParser &Application::parser() const noexcept
{
return m_parser;
}
bool Application::isInteractive() const noexcept
{
return m_interactive;
}
Application &Application::migrationTable(QString table)
{
m_migrationTable = std::move(table);
return *this;
}
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_APPLICATION_HPP

View File

@@ -0,0 +1,153 @@
#pragma once
#ifndef TOM_COMMANDS_COMMAND_HPP
#define TOM_COMMANDS_COMMAND_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include "tom/concerns/callscommands.hpp"
#include "tom/concerns/interactswithio.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
class QCommandLineOption;
namespace Orm
{
class DatabaseConnection;
}
namespace Tom::Commands
{
/*! Positional argument item for a console command. */
struct PositionalArgument
{
/*! Argument name. */
QString name;
/*! Argument description. */
QString description;
/*! Appended to the Usage line, if empty the name is used. */
QString syntax {};
/*! Is argument optional? */
bool optional = false;
/*! Argument's default value (optional argument only). */
QString defaultValue {};
};
/*! Abstract base class for the console command. */
class Command : public Concerns::CallsCommands,
public Concerns::InteractsWithIO
{
Q_DISABLE_COPY(Command)
public:
/*! Constructor. */
Command(Application &application, QCommandLineParser &parser);
/*! Pure virtual destructor. */
inline virtual ~Command() = 0;
/*! The console command name. */
virtual QString name() const = 0;
/*! The console command description. */
virtual QString description() const = 0;
/*! The console command positional arguments signature. */
inline virtual const std::vector<PositionalArgument> &
positionalArguments() const;
/*! The signature of the console command. */
virtual QList<QCommandLineOption> optionsSignature() const;
/*! The console command help. */
inline virtual QString help() const;
/*! Execute the console command. */
virtual int run();
/*! Execute the console command with the given arguments. */
int runWithArguments(QStringList &&arguments);
/* Getters */
/*! Get the tom application. */
inline Application &application() const noexcept;
/*! Determine whether a command has own positional arguments. */
bool hasPositionalArguments() const;
/*! Determine whether a command has own options. */
bool hasOptions() const;
protected:
/* Getters */
/*! Obtain passed arguments to parse (can come from three sources). */
QStringList passedArguments() const;
/* Parser helpers */
/*! Check whether the option name was set in the parser. */
bool isSet(const QString &name) const;
/*! Returns the option value found for the given option name or empty string. */
QString value(const QString &name) const;
/*! Get a full command-line value option if value is set in the parser. */
QString valueCmd(const QString &name, const QString &key = "") const;
/*! Get a full command-line boolean option if it's set in the parser. */
QString boolCmd(const QString &name, const QString &key = "") const;
/*! Check whether a positional argument at the given index was set. */
bool hasArgument(QList<QString>::size_type index) const;
/*! Get a list of positional arguments. */
QStringList arguments() const;
/*! Get a positional argument at the given index position. */
QString argument(QList<QString>::size_type index) const;
/*! Get a positional argument by the given name. */
QString argument(const QString &name) const;
/*! Get a database connection. */
Orm::DatabaseConnection &connection(const QString &name) const;
/*! Get a command-line parser. */
QCommandLineParser &parser() const noexcept;
/*! Reference to the tom application. */
std::reference_wrapper<Application> m_application;
/*! Command line parser. */
std::reference_wrapper<QCommandLineParser> m_parser;
/*! Passed command's arguments. */
QStringList m_arguments {};
/*! Alias for the QList command-line option size type. */
using OptionsSizeType = QList<QCommandLineOption>::size_type;
/*! Map positional argument names to the index for obtaining values. */
std::unordered_map<QString, OptionsSizeType> m_positionalArguments {};
private:
/*! Initialize positional arguments map. */
void initializePositionalArguments();
/*! Show help if --help argument was passed. */
void checkHelpArgument() const;
/*! Show the error wall and exit the application if the parser fails. */
void showParserError(const QCommandLineParser &parser) const;
};
/* public */
Command::~Command() = default;
const std::vector<PositionalArgument> &Command::positionalArguments() const
{
static const std::vector<PositionalArgument> cached;
return cached;
}
QString Command::help() const
{
return {};
}
Application &Command::application() const noexcept
{
return m_application;
}
} // namespace Tom::Commands
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_COMMANDS_COMMAND_HPP

View File

@@ -0,0 +1,67 @@
#pragma once
#ifndef TOM_COMMANDS_DATABASE_WIPECOMMAND_HPP
#define TOM_COMMANDS_DATABASE_WIPECOMMAND_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include "tom/commands/command.hpp"
#include "tom/concerns/confirmable.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Commands::Database
{
/*! Drop all tables, views, and types. */
class WipeCommand : public Command,
public Concerns::Confirmable
{
Q_DISABLE_COPY(WipeCommand)
/*! Alias for the Command. */
using Command = Commands::Command;
public:
/*! Constructor. */
WipeCommand(Application &application, QCommandLineParser &parser);
/*! Virtual destructor. */
inline ~WipeCommand() override = default;
/*! The console command name. */
inline QString name() const override;
/*! The console command description. */
inline QString description() const override;
/*! The signature of the console command. */
QList<QCommandLineOption> optionsSignature() const override;
/*! Execute the console command. */
int run() override;
protected:
/*! Drop all of the database tables. */
void dropAllTables(const QString &database) const;
/*! Drop all of the database views. */
void dropAllViews(const QString &database) const;
/*! Drop all of the database types. */
void dropAllTypes(const QString &database) const;
};
/* public */
QString WipeCommand::name() const
{
return QStringLiteral("db:wipe");
}
QString WipeCommand::description() const
{
return QLatin1String("Drop all tables, views, and types");
}
} // namespace Tom::Commands::Database
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_COMMANDS_DATABASE_WIPECOMMAND_HPP

View File

@@ -0,0 +1,51 @@
#pragma once
#ifndef TOM_COMMANDS_ENVIRONMENTCOMMAND_HPP
#define TOM_COMMANDS_ENVIRONMENTCOMMAND_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include "tom/commands/command.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Commands
{
/*! Display the current environment. */
class EnvironmentCommand : public Command
{
Q_DISABLE_COPY(EnvironmentCommand)
public:
/*! Constructor. */
EnvironmentCommand(Application &application, QCommandLineParser &parser);
/*! Virtual destructor. */
inline ~EnvironmentCommand() override = default;
/*! The console command name. */
inline QString name() const override;
/*! The console command description. */
inline QString description() const override;
/*! Execute the console command. */
int run() override;
};
/* public */
QString EnvironmentCommand::name() const
{
return QStringLiteral("env");
}
QString EnvironmentCommand::description() const
{
return QLatin1String("Display the current framework environment");
}
} // namespace Tom::Commands
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_COMMANDS_ENVIRONMENTCOMMAND_HPP

View File

@@ -0,0 +1,85 @@
#pragma once
#ifndef TOM_COMMANDS_HELPCOMMAND_HPP
#define TOM_COMMANDS_HELPCOMMAND_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include "tom/commands/command.hpp"
#include "tom/concerns/printsoptions.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Commands
{
/*! Display help for a command. */
class HelpCommand : public Command,
public Concerns::PrintsOptions
{
Q_DISABLE_COPY(HelpCommand)
public:
/*! Constructor. */
HelpCommand(Application &application, QCommandLineParser &parser);
/*! Virtual destructor. */
inline ~HelpCommand() 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 console command help. */
QString help() const override;
/*! Execute the console command. */
int run() override;
protected:
/*! Create command by the given name. */
std::unique_ptr<Command> createCommand(const QString &name) const;
/*! Validate if all required positional arguments are after optional arguments. */
bool validateRequiredArguments(
const std::vector<PositionalArgument> &arguments) const;
/*! Print description section. */
void printDescriptionSection(const Command &command) const;
/*! Print usage section. */
void printUsageSection(
const QString &commandNameArg, const Command &command,
const std::vector<PositionalArgument> &arguments) const;
/*! Print positional arguments section. */
void printArgumentsSection(
const std::vector<PositionalArgument> &arguments) const;
/*! Get max. positional argument size in all command arguments. */
int argumentsMaxSize(const std::vector<PositionalArgument> &arguments) const;
/*! Print a positional's argument default value part. */
void printArgumentDefaultValue(const PositionalArgument &argument) const;
/*! Print options section. */
int printOptionsSection(const Command &command) const;
/*! Print help section. */
void printHelpSection(const Command &command) const;
};
/* public */
QString HelpCommand::name() const
{
return QStringLiteral("help");
}
QString HelpCommand::description() const
{
return QLatin1String("Display help for a command");
}
} // namespace Tom::Commands
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_COMMANDS_HELPCOMMAND_HPP

View File

@@ -0,0 +1,51 @@
#pragma once
#ifndef TOM_COMMANDS_INSPIRECOMMAND_HPP
#define TOM_COMMANDS_INSPIRECOMMAND_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include "tom/commands/command.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Commands
{
/*! Display an inspiring quote. */
class InspireCommand : public Command
{
Q_DISABLE_COPY(InspireCommand)
public:
/*! Constructor. */
InspireCommand(Application &application, QCommandLineParser &parser);
/*! Virtual destructor. */
inline ~InspireCommand() override = default;
/*! The console command name. */
inline QString name() const override;
/*! The console command description. */
inline QString description() const override;
/*! Execute the console command. */
int run() override;
};
/* public */
QString InspireCommand::name() const
{
return QStringLiteral("inspire");
}
QString InspireCommand::description() const
{
return QLatin1String("Display an inspiring quote");
}
} // namespace Tom::Commands
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_COMMANDS_INSPIRECOMMAND_HPP

View File

@@ -0,0 +1,89 @@
#pragma once
#ifndef TOM_COMMANDS_LISTCOMMAND_HPP
#define TOM_COMMANDS_LISTCOMMAND_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include "tom/commands/command.hpp"
#include "tom/concerns/printsoptions.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Commands
{
/*! List all available commands. */
class ListCommand : public Command,
public Concerns::PrintsOptions
{
Q_DISABLE_COPY(ListCommand)
public:
/*! Constructor. */
ListCommand(Application &application, QCommandLineParser &parser);
/*! Virtual destructor. */
inline ~ListCommand() 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;
/*! The console command help. */
QString help() const override;
/*! Execute the console command. */
int run() override;
protected:
/*! Output full commands list. */
int full(const QString &namespaceArg);
/*! Output raw commands list and nothing else (can be consumed by scripts). */
int raw(const QString &namespaceArg);
/* Commands section */
/*! Print commands section. */
void printCommandsSection(const QString &namespaceArg, int optionsMaxSize) const;
/*! Get max. command size in all command names. */
int commandsMaxSize(const std::vector<std::shared_ptr<Command>> &commands,
int optionsMaxSize) const;
/*! Print commands to the console. */
void printCommands(const std::vector<std::shared_ptr<Command>> &commands,
int commandsMaxSize, bool hasNamespaceArg) const;
/*! Print a new namespace section. */
void tryBeginNsSection(QString &renderingNamespace,
const QString &commandName, bool hasNamespaceArg) const;
/*! Get command's namespace from a command name. */
QString commandNamespace(const QString &commandName) const;
/*! Wrapper for the two methods below, helps to avoid one copy. */
const std::vector<std::shared_ptr<Command>> &
getCommandsByNamespace(const QString &name) const;
/*! Obtain all commands in the given namespace. */
std::vector<std::shared_ptr<Command>>
getCommandsInNamespace(const QString &name) const;
};
/* public */
QString ListCommand::name() const
{
return QStringLiteral("list");
}
QString ListCommand::description() const
{
return QLatin1String("List commands");
}
} // namespace Tom::Commands
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_COMMANDS_LISTCOMMAND_HPP

View File

@@ -0,0 +1,80 @@
#pragma once
#ifndef TOM_COMMANDS_MAKE_MIGRATIONCOMMAND_HPP
#define TOM_COMMANDS_MAKE_MIGRATIONCOMMAND_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include "tom/commands/command.hpp"
#include "tom/migrationcreator.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Commands::Make
{
/*! Create a new migration file. */
class MigrationCommand : public Command
{
Q_DISABLE_COPY(MigrationCommand)
/*! Alias for the filesystem path. */
using path = std::filesystem::path;
public:
/*! Constructor. */
MigrationCommand(Application &application, QCommandLineParser &parser);
/*! Virtual destructor. */
inline ~MigrationCommand() 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:
/*! Write the migration file to disk. */
void writeMigration(const QString &name, const QString &table,
bool create) const;
/*! Get migration path (either specified by '--path' option or default
location). */
inline path getMigrationPath() const;
/*! The migration creator instance. */
MigrationCreator m_creator {};
};
/* public */
QString MigrationCommand::name() const
{
return QStringLiteral("make:migration");
}
QString MigrationCommand::description() const
{
return QLatin1String("Create a new migration file");
}
/* protected */
// CUR tom, finish --path/--realpath silverqx
std::filesystem::path MigrationCommand::getMigrationPath() const
{
return path(__FILE__).parent_path().parent_path().parent_path() / "migrations";
}
} // namespace Tom::Commands::Make
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_COMMANDS_MAKE_MIGRATIONCOMMAND_HPP

View File

@@ -0,0 +1,78 @@
#pragma once
#ifndef TOM_COMMANDS_MAKE_PROJECTCOMMAND_HPP
#define TOM_COMMANDS_MAKE_PROJECTCOMMAND_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include "tom/commands/command.hpp"
//#include "tom/migrationcreator.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Commands::Make
{
/*! Create a new Tom application project. */
class ProjectCommand : public Command
{
Q_DISABLE_COPY(ProjectCommand)
/*! Alias for the filesystem path. */
// using path = std::filesystem::path;
public:
/*! Constructor. */
ProjectCommand(Application &application, QCommandLineParser &parser);
/*! Virtual destructor. */
inline ~ProjectCommand() 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> signature() const override;
/*! Execute the console command. */
int run() override;
protected:
/*! Write the migration file to disk. */
// void writeMigration(const QString &name, const QString &table,
// bool create) const;
/*! Get migration path (either specified by '--path' option or default
location). */
// inline path getMigrationPath() const;
/*! The migration creator instance. */
// MigrationCreator m_creator {};
};
/* public */
QString ProjectCommand::name() const
{
return QStringLiteral("make:project");
}
QString ProjectCommand::description() const
{
return QLatin1String("Create a new Tom application project");
}
/* protected */
// std::filesystem::path ProjectCommand::getMigrationPath() const
// {
// return path(__FILE__).parent_path().parent_path().parent_path() / "migrations";
// }
} // namespace Tom::Commands::Make
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_COMMANDS_MAKE_PROJECTCOMMAND_HPP

View File

@@ -0,0 +1,110 @@
#pragma once
#ifndef TOM_COMMANDS_MAKE_STUBS_MIGRATIONSTUBS_HPP
#define TOM_COMMANDS_MAKE_STUBS_MIGRATIONSTUBS_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include <orm/macros/commonnamespace.hpp>
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Commands::Make::Stubs
{
/*! Empty migration stub. */
inline const auto *const MigrationStub = R"T(#pragma once
#include <tom/migration.hpp>
namespace Migrations
{
struct {{ class }} : Migration
{
/*! Run the migrations. */
void up() const override
{
//
}
/*! Reverse the migrations. */
void down() const override
{
//
}
};
} // namespace Migrations
)T";
/*! Migration stub for creating a new table. */
inline const auto *const MigrationCreateStub = R"T(#pragma once
#include <tom/migration.hpp>
namespace Migrations
{
struct {{ class }} : Migration
{
/*! Run the migrations. */
void up() const override
{
Schema::create("{{ table }}", [](Blueprint &table)
{
table.id();
table.timestamps();
});
}
/*! Reverse the migrations. */
void down() const override
{
Schema::dropIfExists("{{ table }}");
}
};
} // namespace Migrations
)T";
/*! Migration stub for updating an existing table. */
inline const auto *const MigrationUpdateStub = R"T(#pragma once
#include <tom/migration.hpp>
namespace Migrations
{
struct {{ class }} : Migration
{
/*! Run the migrations. */
void up() const override
{
Schema::table("{{ table }}", [](Blueprint &table)
{
//
});
}
/*! Reverse the migrations. */
void down() const override
{
Schema::table("{{ table }}", [](Blueprint &table)
{
//
});
}
};
} // namespace Migrations
)T";
} // namespace Tom::Commands::Make::Stubs
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_COMMANDS_MAKE_STUBS_MIGRATIONSTUBS_HPP

View File

@@ -0,0 +1,22 @@
#pragma once
#ifndef TOM_COMMANDS_MAKE_STUBS_PROJECTSTUBS_HPP
#define TOM_COMMANDS_MAKE_STUBS_PROJECTSTUBS_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include <orm/macros/commonnamespace.hpp>
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Commands::Make::Stubs
{
inline const auto *const XyzStub = R"T(#pragma once
)T";
} // namespace Tom::Commands::Make::Stubs
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_COMMANDS_MAKE_STUBS_PROJECTSTUBS_HPP

View File

@@ -0,0 +1,74 @@
#pragma once
#ifndef TOM_COMMANDS_MIGRATIONS_FRESHCOMMAND_HPP
#define TOM_COMMANDS_MIGRATIONS_FRESHCOMMAND_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include "tom/commands/command.hpp"
#include "tom/concerns/confirmable.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom
{
class Migrator;
namespace Commands::Migrations
{
/*! Rollback the last database migration/s. */
class FreshCommand : public Command,
public Concerns::Confirmable
{
Q_DISABLE_COPY(FreshCommand)
/*! Alias for the Command. */
using Command = Commands::Command;
public:
/*! Constructor. */
FreshCommand(Application &application, QCommandLineParser &parser,
std::shared_ptr<Migrator> migrator);
/*! Virtual destructor. */
inline ~FreshCommand() override = default;
/*! The console command name. */
inline QString name() const override;
/*! The console command description. */
inline QString description() const override;
/*! The signature of the console command. */
QList<QCommandLineOption> optionsSignature() const override;
/*! Execute the console command. */
int run() override;
protected:
/*! Determine if the developer has requested database seeding. */
bool needsSeeding() const;
/*! Run the database seeder command. */
void runSeeder(QString &&databaseCmd) const;
/*! The migrator service instance. */
std::shared_ptr<Migrator> m_migrator;
};
/* public */
QString FreshCommand::name() const
{
return QStringLiteral("migrate:fresh");
}
QString FreshCommand::description() const
{
return QLatin1String("Drop all tables and re-run all migrations");
}
} // namespace Commands::Migrations
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_COMMANDS_MIGRATIONS_FRESHCOMMAND_HPP

View File

@@ -0,0 +1,64 @@
#pragma once
#ifndef TOM_COMMANDS_MIGRATIONS_INSTALLCOMMAND_HPP
#define TOM_COMMANDS_MIGRATIONS_INSTALLCOMMAND_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include "tom/commands/command.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom
{
class MigrationRepository;
namespace Commands::Migrations
{
/*! Create the migration database repository. */
class InstallCommand : public Command
{
Q_DISABLE_COPY(InstallCommand)
public:
/*! Constructor. */
InstallCommand(Application &application, QCommandLineParser &parser,
std::shared_ptr<MigrationRepository> repository);
/*! Virtual destructor. */
inline ~InstallCommand() override = default;
/*! The console command name. */
inline QString name() const override;
/*! The console command description. */
inline QString description() const override;
/*! The signature of the console command. */
QList<QCommandLineOption> optionsSignature() const override;
/*! Execute the console command. */
int run() override;
protected:
/*! The repository instance. */
std::shared_ptr<MigrationRepository> m_repository;
};
/* public */
QString InstallCommand::name() const
{
return QStringLiteral("migrate:install");
}
QString InstallCommand::description() const
{
return QLatin1String("Create the migration repository");
}
} // namespace Commands::Migrations
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_COMMANDS_MIGRATIONS_INSTALLCOMMAND_HPP

View File

@@ -0,0 +1,79 @@
#pragma once
#ifndef TOM_COMMANDS_MIGRATIONS_MIGRATECOMMAND_HPP
#define TOM_COMMANDS_MIGRATIONS_MIGRATECOMMAND_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include "tom/commands/command.hpp"
#include "tom/concerns/confirmable.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom
{
class Migrator;
namespace Commands::Migrations
{
/*! Run the database migrations up/down. */
class MigrateCommand : public Command,
public Concerns::Confirmable
{
Q_DISABLE_COPY(MigrateCommand)
/*! Alias for the Command. */
using Command = Commands::Command;
public:
/*! Constructor. */
MigrateCommand(Application &application, QCommandLineParser &parser,
std::shared_ptr<Migrator> migrator);
/*! Virtual destructor. */
inline ~MigrateCommand() override = default;
/*! The console command name. */
inline QString name() const override;
/*! The console command description. */
inline QString description() const override;
/*! The signature of the console command. */
QList<QCommandLineOption> optionsSignature() const override;
/*! Execute the console command. */
int run() override;
protected:
/*! Prepare the migration database for running. */
void prepareDatabase() const;
/*! Load the schema state to seed the initial database schema structure. */
void loadSchemaState() const;
/*! Determine if the developer has requested database seeding. */
bool needsSeeding() const;
/*! Run the database seeder command. */
void runSeeder() const;
/*! The migrator service instance. */
std::shared_ptr<Migrator> m_migrator;
};
/* public */
QString MigrateCommand::name() const
{
return QStringLiteral("migrate");
}
QString MigrateCommand::description() const
{
return QLatin1String("Run the database migrations");
}
} // namespace Commands::Migrations
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_COMMANDS_MIGRATIONS_MIGRATECOMMAND_HPP

View File

@@ -0,0 +1,74 @@
#pragma once
#ifndef TOM_COMMANDS_MIGRATIONS_REFRESHCOMMAND_HPP
#define TOM_COMMANDS_MIGRATIONS_REFRESHCOMMAND_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include "tom/commands/command.hpp"
#include "tom/concerns/confirmable.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom
{
class Migrator;
namespace Commands::Migrations
{
/*! Rollback the last database migration. */
class RefreshCommand : public Command,
public Concerns::Confirmable
{
Q_DISABLE_COPY(RefreshCommand)
/*! Alias for the Command. */
using Command = Commands::Command;
public:
/*! Constructor. */
RefreshCommand(Application &application, QCommandLineParser &parser,
std::shared_ptr<Migrator> migrator);
/*! Virtual destructor. */
inline ~RefreshCommand() override = default;
/*! The console command name. */
inline QString name() const override;
/*! The console command description. */
inline QString description() const override;
/*! The signature of the console command. */
QList<QCommandLineOption> optionsSignature() const override;
/*! Execute the console command. */
int run() override;
protected:
/*! Determine if the developer has requested database seeding. */
bool needsSeeding() const;
/*! Run the database seeder command. */
void runSeeder(QString &&databaseCmd) const;
/*! The migrator service instance. */
std::shared_ptr<Migrator> m_migrator;
};
/* public */
QString RefreshCommand::name() const
{
return QStringLiteral("migrate:refresh");
}
QString RefreshCommand::description() const
{
return QLatin1String("Rollback and re-run all migrations");
}
} // namespace Commands::Migrations
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_COMMANDS_MIGRATIONS_REFRESHCOMMAND_HPP

View File

@@ -0,0 +1,69 @@
#pragma once
#ifndef TOM_COMMANDS_MIGRATIONS_RESETCOMMAND_HPP
#define TOM_COMMANDS_MIGRATIONS_RESETCOMMAND_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include "tom/commands/command.hpp"
#include "tom/concerns/confirmable.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom
{
class Migrator;
namespace Commands::Migrations
{
/*! Rollback the last database migration. */
class ResetCommand : public Command,
public Concerns::Confirmable
{
Q_DISABLE_COPY(ResetCommand)
/*! Alias for the Command. */
using Command = Commands::Command;
public:
/*! Constructor. */
ResetCommand(Application &application, QCommandLineParser &parser,
std::shared_ptr<Migrator> migrator);
/*! Virtual destructor. */
inline ~ResetCommand() override = default;
/*! The console command name. */
inline QString name() const override;
/*! The console command description. */
inline QString description() const override;
/*! The signature of the console command. */
QList<QCommandLineOption> optionsSignature() const override;
/*! Execute the console command. */
int run() override;
protected:
/*! The migrator service instance. */
std::shared_ptr<Migrator> m_migrator;
};
/* public */
QString ResetCommand::name() const
{
return QStringLiteral("migrate:reset");
}
QString ResetCommand::description() const
{
return QLatin1String("Rollback all database migrations");
}
} // namespace Commands::Migrations
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_COMMANDS_MIGRATIONS_RESETCOMMAND_HPP

View File

@@ -0,0 +1,69 @@
#pragma once
#ifndef TOM_COMMANDS_MIGRATIONS_ROLLBACKCOMMAND_HPP
#define TOM_COMMANDS_MIGRATIONS_ROLLBACKCOMMAND_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include "tom/commands/command.hpp"
#include "tom/concerns/confirmable.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom
{
class Migrator;
namespace Commands::Migrations
{
/*! Rollback the last database migration. */
class RollbackCommand : public Command,
public Concerns::Confirmable
{
Q_DISABLE_COPY(RollbackCommand)
/*! Alias for the Command. */
using Command = Commands::Command;
public:
/*! Constructor. */
RollbackCommand(Application &application, QCommandLineParser &parser,
std::shared_ptr<Migrator> migrator);
/*! Virtual destructor. */
inline ~RollbackCommand() override = default;
/*! The console command name. */
inline QString name() const override;
/*! The console command description. */
inline QString description() const override;
/*! The signature of the console command. */
QList<QCommandLineOption> optionsSignature() const override;
/*! Execute the console command. */
int run() override;
protected:
/*! The migrator service instance. */
std::shared_ptr<Migrator> m_migrator;
};
/* public */
QString RollbackCommand::name() const
{
return QStringLiteral("migrate:rollback");
}
QString RollbackCommand::description() const
{
return QLatin1String("Rollback the last database migration");
}
} // namespace Commands::Migrations
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_COMMANDS_MIGRATIONS_ROLLBACKCOMMAND_HPP

View File

@@ -0,0 +1,118 @@
#pragma once
#ifndef TOM_COMMANDS_MIGRATIONS_STATUSCOMMAND_HPP
#define TOM_COMMANDS_MIGRATIONS_STATUSCOMMAND_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include <QVariant>
#include <tabulate/table.hpp>
#ifdef TINYTOM_TESTS_CODE
#include <orm/macros/threadlocal.hpp>
#endif
#include "tom/commands/command.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom
{
class Migrator;
namespace Commands::Migrations
{
/*! Show the status of each migration. */
class StatusCommand : public Command
{
Q_DISABLE_COPY(StatusCommand)
/*! Alias for the tabulate cell. */
using TableCell = InteractsWithIO::TableCell;
/*! Alias for the tabulate row. */
using TableRow = InteractsWithIO::TableRow;
public:
/*! Constructor. */
StatusCommand(Application &application, QCommandLineParser &parser,
std::shared_ptr<Migrator> migrator);
/*! Virtual destructor. */
inline ~StatusCommand() override = default;
/*! The console command name. */
inline QString name() const override;
/*! The console command description. */
inline QString description() const override;
/*! The signature of the console command. */
QList<QCommandLineOption> optionsSignature() const override;
/*! Execute the console command. */
int run() override;
#ifdef TINYTOM_TESTS_CODE
/*! Alias for the test output row. */
using StatusRow = std::vector<std::string>;
/*! Get result of the status command (used in auto tests). */
inline static std::vector<StatusRow> status() noexcept;
/*! Enable logic for unit testing? */
inline static void setInUnitTests() noexcept;
#endif
protected:
/*! Get the status for the given ran migrations. */
std::vector<TableRow>
getStatusFor(QVector<QVariant> &&ran,
std::map<QString, QVariant> &&batches) const;
#ifdef TINYTOM_TESTS_CODE
/*! Transform migrations status for comparing in auto tests. */
std::vector<StatusRow>
statusForUnitTest(std::vector<TableRow> &&migrations) const;
#endif
/*! The migrator service instance. */
std::shared_ptr<Migrator> m_migrator;
#ifdef TINYTOM_TESTS_CODE
/*! Result of the status command (used in auto tests). */
T_THREAD_LOCAL
inline static std::vector<StatusRow> m_status;
/*! Is enabled logic for unit testing? */
T_THREAD_LOCAL
inline static auto m_inUnitTests = false;
#endif
};
/* public */
QString StatusCommand::name() const
{
return QStringLiteral("migrate:status");
}
QString StatusCommand::description() const
{
return QLatin1String("Show the status of each migration");
}
#ifdef TINYTOM_TESTS_CODE
std::vector<StatusCommand::StatusRow> StatusCommand::status() noexcept
{
return m_status;
}
void StatusCommand::setInUnitTests() noexcept
{
m_inUnitTests = true;
}
#endif
} // namespace Commands::Migrations
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_COMMANDS_MIGRATIONS_STATUSCOMMAND_HPP

View File

@@ -0,0 +1,67 @@
#pragma once
#ifndef TOM_CONCERNS_CALLSCOMMANDS_HPP
#define TOM_CONCERNS_CALLSCOMMANDS_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include <QStringList>
#include <orm/macros/commonnamespace.hpp>
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom
{
namespace Commands
{
class Command;
}
namespace Concerns
{
/*! Invoke another command by name and passed arguments. */
class CallsCommands
{
Q_DISABLE_COPY(CallsCommands)
public:
/*! Default constructor. */
inline CallsCommands() = default;
/*! Default destructor. */
inline ~CallsCommands() = default;
/*! Call another console command. */
inline int call(const QString &command, QStringList &&arguments = {}) const;
protected:
/*! Run the given console command. */
int runCommand(const QString &command, QStringList &&arguments) const;
/*! Create command-line arguments from the given arguments. */
QStringList
createCommandLineArguments(const QString &command, QStringList &&arguments,
QStringList &&currentArguments) const;
/*! Get common command-line arguments from current command-line arguments. */
QStringList getCommonArguments(QStringList &&arguments) const;
private:
/*! Static cast *this to the Command & derived type, const version. */
const Commands::Command &command() const;
};
/* public */
int CallsCommands::call(const QString &command, QStringList &&arguments) const
{
return runCommand(std::move(command), std::move(arguments));
}
} // namespace Concerns
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_CONCERNS_CALLSCOMMANDS_HPP

View File

@@ -0,0 +1,58 @@
#pragma once
#ifndef TOM_CONCERNS_CONFIRMABLE_HPP
#define TOM_CONCERNS_CONFIRMABLE_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include <QString>
#include <functional>
#include <orm/macros/commonnamespace.hpp>
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom
{
namespace Commands
{
class Command;
}
namespace Concerns
{
/*! Prints alert and asks for the confirmation (Y/N). */
class Confirmable
{
Q_DISABLE_COPY(Confirmable)
/*! Alias for the Command. */
using Command = Commands::Command;
public:
/*! Constructor (int param. to avoid interpret it as copy ctor). */
Confirmable(Command &command, int);
/*! Default destructor. */
inline ~Confirmable() = default;
/*! Confirm before proceeding with the action (only in production environment). */
bool confirmToProceed(
const QString &warning = QLatin1String("Application In Production!"),
const std::function<bool()> &callback = nullptr) const;
protected:
/*! Get the default confirmation callback. */
std::function<bool()> defaultConfirmCallback() const;
/*! Reference to a command that should be confimable. */
std::reference_wrapper<Command> m_command;
};
} // namespace Concerns
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_CONCERNS_CONFIRMABLE_HPP

View File

@@ -0,0 +1,238 @@
#pragma once
#ifndef TOM_CONCERNS_INTERACTSWITHIO_HPP
#define TOM_CONCERNS_INTERACTSWITHIO_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include <QStringList>
#include <tabulate/table.hpp>
#include <orm/macros/commonnamespace.hpp>
#include <orm/macros/export.hpp>
TINYORM_BEGIN_COMMON_NAMESPACE
class QCommandLineParser;
namespace Tom
{
class Application;
class Terminal;
namespace Concerns
{
/*! Set of methods for the console output/input. */
class SHAREDLIB_EXPORT InteractsWithIO
{
Q_DISABLE_COPY(InteractsWithIO)
// To access private ctor and errorWallInternal() (used by logException())
friend Tom::Application;
public:
/*! Alias for the tabulate cell. */
using TableCell = std::variant<std::string, tabulate::Table>;
/*! Alias for the tabulate row. */
using TableRow = std::vector<TableCell>;
/*! Constructor. */
explicit InteractsWithIO(const QCommandLineParser &parser);
/*! Default destructor. */
~InteractsWithIO();
/*! Base enum for the verbosity levels. */
enum struct Verbosity {
Quiet = 0x0001,
Normal = 0x0002,
Verbose = 0x0004,
VeryVerbose = 0x0008,
Debug = 0x0010,
};
/*! Quiet verbosity. */
static constexpr Verbosity Quiet = Verbosity::Quiet;
/*! Normal verbosity (default). */
static constexpr Verbosity Normal = Verbosity::Normal;
/*! Verbose verbosity. */
static constexpr Verbosity Verbose = Verbosity::Verbose;
/*! Very verbose verbosity. */
static constexpr Verbosity VeryVerbose = Verbosity::VeryVerbose;
/*! Debug verbosity. */
static constexpr Verbosity Debug = Verbosity::Debug;
/*! Write a string as standard output. */
const InteractsWithIO &line(const QString &string, bool newline = true,
Verbosity verbosity = Normal,
QString &&style = "",
std::ostream &cout = std::cout) const;
/*! Write a string as note output. */
const InteractsWithIO &note(const QString &string, bool newline = true,
Verbosity verbosity = Normal) const;
/*! Write a string as information output. */
const InteractsWithIO &info(const QString &string, bool newline = true,
Verbosity verbosity = Normal) const;
/*! Write a string as error output. */
const InteractsWithIO &error(const QString &string, bool newline = true,
Verbosity verbosity = Normal) const;
/*! Write a string as comment output. */
const InteractsWithIO &comment(const QString &string, bool newline = true,
Verbosity verbosity = Normal) const;
/*! Write a string in an alert box. */
const InteractsWithIO &alert(const QString &string,
Verbosity verbosity = Normal) const;
/*! Write a string as error output (red box with a white text). */
const InteractsWithIO &errorWall(const QString &string,
Verbosity verbosity = Normal) const;
/*! Write a string as standard output, wide version. */
const InteractsWithIO &wline(const QString &string, bool newline = true,
Verbosity verbosity = Normal,
QString &&style = "",
std::wostream &wcout = std::wcout) const;
/*! Write a string as note output, wide version. */
const InteractsWithIO &wnote(const QString &string, bool newline = true,
Verbosity verbosity = Normal) const;
/*! Write a string as information output, wide version. */
const InteractsWithIO &winfo(const QString &string, bool newline = true,
Verbosity verbosity = Normal) const;
/*! Write a string as error output, wide version. */
const InteractsWithIO &werror(const QString &string, bool newline = true,
Verbosity verbosity = Normal) const;
/*! Write a string as comment output, wide version. */
const InteractsWithIO &wcomment(const QString &string, bool newline = true,
Verbosity verbosity = Normal) const;
/*! Write a string in an alert box, wide version. */
const InteractsWithIO &walert(const QString &string,
Verbosity verbosity = Normal) const;
/*! Write a string as error output (red box with a white text). */
const InteractsWithIO &werrorWall(const QString &string,
Verbosity verbosity = Normal) const;
/*! Write a blank line. */
const InteractsWithIO &newLine(int count = 1,
Verbosity verbosity = Normal) const;
/*! Write a blank line, wide version. */
const InteractsWithIO &newLineErr(int count = 1,
Verbosity verbosity = Normal) const;
/*! Format input to textual table. */
const InteractsWithIO &
table(const TableRow &headers, const std::vector<TableRow> &rows,
Verbosity verbosity = Normal) const;
/*! Confirm a question with the user. */
bool confirm(const QString &question, bool defaultAnswer = false) const;
protected:
/*! Default constructor (used by the TomApplication, instance is initialized
later in the TomApplication::parseCommandLine()). */
InteractsWithIO();
/*! Initialize instance like the second constructor do, allows to create
an instance in two steps. */
void initialize(const QCommandLineParser &parser);
/*! Get a current verbosity level. */
inline Verbosity verbosity() const noexcept;
/*! Is quiet verbosity level? */
inline bool isQuietVerbosity() const noexcept;
/*! Is normal verbosity level? */
inline bool isNormalVerbosity() const noexcept;
/*! Is verbose verbosity level? */
inline bool isVerboseVerbosity() const noexcept;
/*! Is very verbose verbosity level? */
inline bool isVeryVerboseVerbosity() const noexcept;
/*! Is debug verbosity level? */
inline bool isDebugVerbosity() const noexcept;
private:
/*! Constructor (used by TomApplication::logException()). */
explicit InteractsWithIO(bool noAnsi);
/*! Repalce text tags with ANSI sequences. */
QString parseOutput(QString string, bool isAnsi = true) const;
/*! Remove tom ansi tags from the given string. */
QString stripTags(QString string) const;
/*! Initialize verbosity by set options in the command-line parser. */
Verbosity initializeVerbosity(const QCommandLineParser &parser) const;
/*! Initialize ansi support by set options in the command-line parser. */
std::optional<bool> initializeAnsi(const QCommandLineParser &parser) const;
/*! Initialize ansi support by noAnsi passed to the Application::logException. */
std::optional<bool> initializeNoAnsi(bool noAnsi) const;
/*! Number of the option name set on the command line (used by eg. -vvv). */
QStringList::size_type
countSetOption(const QString &optionName, const QCommandLineParser &parser) const;
/*! Determine whether discard output by the current and the given verbosity. */
bool dontOutput(Verbosity verbosity) const;
/*! Should the given output use ansi? (ansi is disabled for non-tty). */
bool isAnsiOutput(std::ostream &cout = std::cout) const;
/*! Should the given output use ansi? (ansi is disabled for non-tty),
wide version. */
bool isAnsiWOutput(std::wostream &cout = std::wcout) const;
/*! Write a string as error output (red box with a white text). */
QString errorWallInternal(const QString &string) const;
/*! Alias for the tabulate color. */
using Color = tabulate::Color;
/*! Default tabulate table colors. */
struct TableColors
{
Color green = Color::green;
Color red = Color::red;
};
/*! Initialize tabulate table colors by supported ansi. */
TableColors initializeTableColors() const;
/*! Is this input means interactive? */
bool m_interactive = true;
/*! Current application verbosity (defined by passed command-line options). */
Verbosity m_verbosity = Normal;
/*! Current application ansi passed by command-line option. */
std::optional<bool> m_ansi = std::nullopt;
/*! Describes current terminal features. */
std::unique_ptr<Terminal> m_terminal;
};
/* protected */
InteractsWithIO::Verbosity InteractsWithIO::verbosity() const noexcept
{
return m_verbosity;
}
bool InteractsWithIO::isQuietVerbosity() const noexcept
{
return m_verbosity == Quiet;
}
bool InteractsWithIO::isNormalVerbosity() const noexcept
{
return m_verbosity == Normal;
}
bool InteractsWithIO::isVerboseVerbosity() const noexcept
{
return m_verbosity == Verbose;
}
bool InteractsWithIO::isVeryVerboseVerbosity() const noexcept
{
return m_verbosity == VeryVerbose;
}
bool InteractsWithIO::isDebugVerbosity() const noexcept
{
return m_verbosity == Debug;
}
} // namespace Concerns
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_CONCERNS_INTERACTSWITHIO_HPP

View File

@@ -0,0 +1,53 @@
#pragma once
#ifndef TOM_CONCERNS_PRINTSOPTIONS_HPP
#define TOM_CONCERNS_PRINTSOPTIONS_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include <QtGlobal>
#include <orm/macros/commonnamespace.hpp>
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom
{
namespace Commands
{
class Command;
}
namespace Concerns
{
/*! Print options section. */
class PrintsOptions
{
Q_DISABLE_COPY(PrintsOptions)
public:
/*! Constructor (int param. to avoid interpret it as copy ctor). */
PrintsOptions(const Commands::Command &command, int);
/*! Default destructor. */
inline ~PrintsOptions() = default;
/*! Print options section. */
int printOptionsSection(bool commonOptions) const;
private:
/*! Get max. option size in all options. */
int optionsMaxSize() const;
/*! Print options to the console. */
void printOptions(int optionsMaxSize) const;
/*! Reference to the command. */
std::reference_wrapper<const Commands::Command> m_command;
};
} // namespace Concerns
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_CONCERNS_PRINTSOPTIONS_HPP

View File

@@ -0,0 +1,26 @@
#pragma once
#ifndef TOM_EXCEPTIONS_INVALIDARGUMENTERROR_HPP
#define TOM_EXCEPTIONS_INVALIDARGUMENTERROR_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include "tom/exceptions/logicerror.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Exceptions
{
/*! Tom Invalid argument exception. */
class InvalidArgumentError : public LogicError
{
/*! Inherit constructors. */
using LogicError::LogicError;
};
} // namespace Tom::Exceptions
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_EXCEPTIONS_INVALIDARGUMENTERROR_HPP

View File

@@ -0,0 +1,26 @@
#pragma once
#ifndef TOM_EXCEPTIONS_INVALIDTEMPLATEARGUMENTERROR_HPP
#define TOM_EXCEPTIONS_INVALIDTEMPLATEARGUMENTERROR_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include "tom/exceptions/invalidargumenterror.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Exceptions
{
/*! Tom invalid template argument exception. */
class InvalidTemplateArgumentError : public InvalidArgumentError
{
/*! Inherit constructors. */
using InvalidArgumentError::InvalidArgumentError;
};
} // namespace Tom::Exceptions
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_EXCEPTIONS_INVALIDTEMPLATEARGUMENTERROR_HPP

View File

@@ -0,0 +1,48 @@
#pragma once
#ifndef TOM_EXCEPTIONS_LOGICERROR_HPP
#define TOM_EXCEPTIONS_LOGICERROR_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include <QString>
#include <stdexcept>
#include "tom/exceptions/tomerror.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Exceptions
{
/*! Tom Logic exception. */
class LogicError : public std::logic_error,
public TomError
{
public:
/*! const char * constructor. */
explicit LogicError(const char *message);
/*! QString constructor. */
explicit LogicError(const QString &message);
/*! std::string constructor. */
explicit LogicError(const std::string &message);
/*! Return exception message as a QString. */
inline const QString &message() const noexcept;
protected:
/*! Exception message. */
QString m_message = what();
};
const QString &LogicError::message() const noexcept
{
return m_message;
}
} // namespace Tom::Exceptions
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_EXCEPTIONS_LOGICERROR_HPP

View File

@@ -0,0 +1,48 @@
#pragma once
#ifndef TOM_EXCEPTIONS_RUNTIMEERROR_HPP
#define TOM_EXCEPTIONS_RUNTIMEERROR_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include <QString>
#include <stdexcept>
#include "tom/exceptions/tomerror.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Exceptions
{
/*! Tom Runtime exception. */
class RuntimeError : public std::runtime_error,
public TomError
{
public:
/*! const char * constructor. */
explicit RuntimeError(const char *message);
/*! QString constructor. */
explicit RuntimeError(const QString &message);
/*! std::string constructor. */
explicit RuntimeError(const std::string &message);
/*! Return exception message as a QString. */
inline const QString &message() const noexcept;
protected:
/*! Exception message. */
QString m_message = what();
};
const QString &RuntimeError::message() const noexcept
{
return m_message;
}
} // namespace Tom::Exceptions
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_EXCEPTIONS_RUNTIMEERROR_HPP

View File

@@ -0,0 +1,27 @@
#pragma once
#ifndef TOM_EXCEPTIONS_TOMERROR_HPP
#define TOM_EXCEPTIONS_TOMERROR_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include <orm/macros/commonnamespace.hpp>
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Exceptions
{
/*! Tom exceptions tag, all Tom exceptions are derived from this class. */
class TomError
{
public:
/*! Virtual destructor. */
inline virtual ~TomError() = default;
};
} // namespace Tom::Exceptions
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_EXCEPTIONS_TOMERROR_HPP

View File

@@ -0,0 +1,131 @@
#pragma once
#ifndef TOM_MIGRATION_HPP
#define TOM_MIGRATION_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include <orm/schema.hpp>
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom
{
/*! Migrations base class. */
class Migration
{
Q_DISABLE_COPY(Migration)
public:
/*! Default constructor. */
inline Migration() = default;
/*! Pure virtual destructor. */
inline virtual ~Migration() = 0;
/*! Run the migrations. */
virtual void up() const = 0;
/*! Reverse the migrations. */
virtual void down() const = 0;
/*! The name of the database connection to use. */
QString connection;
/*! Wrapping the migration within a transaction, if supported. */
bool withinTransaction = true;
};
Migration::~Migration() = default;
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE
// Predefine some aliases so the user doesn't have to
namespace Migrations
{
/*! Alias for the Schema Blueprint. */
using TINYORM_COMMON_NAMESPACE::Orm::SchemaNs::Blueprint; // NOLINT(misc-unused-using-decls)
/*! Alias for the Tom Migration. */
using TINYORM_COMMON_NAMESPACE::Tom::Migration; // NOLINT(misc-unused-using-decls)
/*! Alias for the Orm Schema. */
using TINYORM_COMMON_NAMESPACE::Orm::Schema; // 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 Migrations
#endif // TOM_MIGRATION_HPP

View File

@@ -0,0 +1,65 @@
#pragma once
#ifndef TOM_MIGRATIONCREATOR_HPP
#define TOM_MIGRATIONCREATOR_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include <QString>
#include <filesystem>
#include <orm/macros/commonnamespace.hpp>
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom
{
/*! Migration file generator (used by the make:migration command). */
class MigrationCreator
{
Q_DISABLE_COPY(MigrationCreator)
/*! Alias for the filesystem path. */
using fspath = std::filesystem::path;
public:
/*! Default constructor. */
inline MigrationCreator() = default;
/*! Default destructor. */
inline ~MigrationCreator() = default;
/*! Create a new migration at the given path. */
fspath create(const QString &name, fspath &&migrationsPath,
const QString &table = "", bool create = false) const;
protected:
/*! Ensure that a migration with the given name doesn't already exist. */
void throwIfMigrationAlreadyExists(const QString &name,
const fspath &migrationsPath) const;
/*! Get the migration stub file. */
QString getStub(const QString &table, bool create) const;
/*! Get the path to the stubs. */
fspath stubPath() const;
/*! Get the full path to the migration. */
fspath getPath(const QString &name, const fspath &path) const;
/*! Get the date prefix for the migration. */
std::string getDatePrefix() const;
/*! Populate the place-holders in the migration stub. */
std::string populateStub(const QString &name, QString &&stub,
const QString &table) const;
/*! Get the class name of a migration name. */
QString getClassName(const QString &name) const;
/*! Ensure a directory exists. */
void ensureDirectoryExists(fspath &&path) const;
};
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_MIGRATIONCREATOR_HPP

View File

@@ -0,0 +1,94 @@
#pragma once
#ifndef TOM_MIGRATIONREPOSITORY_HPP
#define TOM_MIGRATIONREPOSITORY_HPP
#include <QSharedPointer>
#include <orm/connectionresolverinterface.hpp>
#include "tom/tomtypes.hpp"
class QSqlQuery;
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Orm::Query
{
class Builder;
}
namespace Tom
{
/*! Migrations database repository. */
class MigrationRepository
{
Q_DISABLE_COPY(MigrationRepository)
/*! Alias for the ConnectionResolverInterface. */
using ConnectionResolverInterface = Orm::ConnectionResolverInterface;
/*! Alias for the DatabaseConnection. */
using DatabaseConnection = Orm::DatabaseConnection;
/*! Alias for the QueryBuilder. */
using QueryBuilder = Orm::Query::Builder;
public:
/*! Constructor. */
MigrationRepository(std::shared_ptr<ConnectionResolverInterface> &&resolver,
QString table);
/*! Default destructor. */
inline ~MigrationRepository() = default;
/*! Get the completed migrations (only migration names using pluck). */
QVector<QVariant> getRanSimple() const;
/*! Get the completed migrations. */
std::vector<MigrationItem> getRan(const QString &order) const;
/*! Get list of migrations. */
std::vector<MigrationItem> getMigrations(int steps) const;
/*! Get the last migration batch. */
std::vector<MigrationItem> getLast() const;
/*! Get the completed migrations with their batch numbers. */
std::map<QString, QVariant> getMigrationBatches() const;
/*! Log that a migration was run. */
void log(const QString &file, int batch) const;
/*! Remove a migration from the log. */
void deleteMigration(quint64 id) const;
/*! Get the next migration batch number. */
int getNextBatchNumber() const;
/*! Get the last migration batch number. */
int getLastBatchNumber() const;
/*! Create the migration repository data store. */
void createRepository() const;
/*! Determine if the migration repository exists. */
bool repositoryExists() const;
/*! Delete the migration repository data store. */
void deleteRepository() const;
/*! Resolve the database connection instance. */
DatabaseConnection &getConnection() const;
/*! Set the connection name to use in the repository. */
void setConnection(const QString &name, std::optional<bool> &&debugSql);
protected:
/*! Get a query builder for the migration table. */
QSharedPointer<QueryBuilder> table() const;
/*! 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. */
QString m_table;
/*! The name of the database connection to use. */
QString m_connection {};
};
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_MIGRATIONREPOSITORY_HPP

View File

@@ -0,0 +1,178 @@
#pragma once
#ifndef TOM_MIGRATOR_HPP
#define TOM_MIGRATOR_HPP
#include <set>
#include <typeindex>
#include <orm/connectionresolverinterface.hpp>
#include <orm/types/log.hpp>
#include "tom/concerns/interactswithio.hpp"
#include "tom/tomtypes.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom
{
class MigrationRepository;
/*! Migration service class. */
class Migrator : public Concerns::InteractsWithIO
{
Q_DISABLE_COPY(Migrator)
/*! Alias for the ConnectionResolverInterface. */
using ConnectionResolverInterface = Orm::ConnectionResolverInterface;
/*! Alias for the DatabaseConnection. */
using DatabaseConnection = Orm::DatabaseConnection;
/*! Alias for the pretend Log. */
using Log = Orm::Types::Log;
public:
/*! Constructor. */
Migrator(std::shared_ptr<MigrationRepository> &&repository,
std::shared_ptr<ConnectionResolverInterface> &&resolver,
const std::vector<std::shared_ptr<Migration>> &migrations,
const QCommandLineParser &parser);
/*! Default destructor. */
inline ~Migrator() = default;
/* Main migrate operations */
/*! Migrate options. */
struct MigrateOptions
{
/*! Dump the SQL queries that would be run. */
bool pretend = false;
/*! Force the migrations to be run so they can be rolled back individually. */
bool step = false;
/*! The number of migrations to be reverted. */
int stepValue = 0;
};
/*! Run the pending migrations. */
std::vector<std::shared_ptr<Migration>> run(MigrateOptions options) const;
/*! Rollback the last migration operation. */
std::vector<RollbackItem> rollback(MigrateOptions options) const;
/*! 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;
/*! Determine if any migrations have been run. */
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. */
inline const std::set<QString> &migrationNames() const noexcept;
protected:
/* 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). */
void createMigrationNamesMap();
/*! Get a migration name by a migration type-id. */
QString getMigrationName(const Migration &migration) const;
/* Migrate */
/*! Get the migration instances that have not yet run. */
std::vector<std::shared_ptr<Migration>>
pendingMigrations(const QVector<QVariant> &ran) const;
/*! Run "up" a migration instance. */
void runUp(const Migration &migration, int batch, bool pretend) const;
/* Rollback */
/*! Get the migrations for a rollback operation (used by rollback). */
std::vector<RollbackItem>
getMigrationsForRollback(MigrateOptions options) const;
/*! Get the migrations for a rollback operation (used by reset). */
std::vector<RollbackItem>
getMigrationsForRollback(std::vector<MigrationItem> &&ran) const;
/*! Rollback the given migrations. */
std::vector<RollbackItem>
rollbackMigrations(std::vector<RollbackItem> &&migrations, bool pretend) const;
/*! Run "down" a migration instance. */
void runDown(const RollbackItem &migrationToRollback, bool pretend) const;
/* Pretend */
/*! Migrate type (up/down). */
enum struct MigrateMethod { Up, Down };
/*! Pretend to run the migrations. */
void pretendToRun(const Migration &migration, MigrateMethod method) const;
/*! Get all of the queries that would be run for a migration. */
QVector<Log> getQueries(const Migration &migration, MigrateMethod method) const;
/* Migrate up/down common */
/*! Run a migration inside a transaction if the database supports it. */
void runMigration(const Migration &migration, MigrateMethod method) const;
/*! Migrate by the given method (up/down). */
void migrateByMethod(const Migration &migration, MigrateMethod method) const;
/*! Throw if migrations passed to the TomApplication are not sorted
alphabetically. */
void throwIfMigrationsNotSorted(const QString &previousMigrationName,
const QString &migrationName) const;
/*! The migration repository instance. */
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. */
const std::vector<std::shared_ptr<Migration>> &m_migrations;
/*! Map a migration names by migrations type-id (type_index)
(used migrate, rollback, pretend). */
std::unordered_map<std::type_index, QString> m_migrationNamesMap {};
/*! Migration names list (used by status). */
std::set<QString> m_migrationNames {};
/*! Map a migration instances by migration names (used by reset). */
std::unordered_map<QString,
std::shared_ptr<Migration>> m_migrationInstancesMap {};
};
/* public */
const QString &Migrator::getConnection() const noexcept
{
return m_connection;
}
MigrationRepository &Migrator::repository() const noexcept
{
return *m_repository;
}
const std::set<QString> &Migrator::migrationNames() const noexcept
{
return m_migrationNames;
}
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_MIGRATOR_HPP

View File

@@ -0,0 +1,129 @@
#pragma once
#ifndef TOM_TERMINAL_HPP
#define TOM_TERMINAL_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include <QString>
#include <iostream>
#include <orm/macros/commonnamespace.hpp>
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom
{
/*! Concept for the ostream and wostream. */
template<typename T>
concept OStreamConcept = std::convertible_to<T, const std::ostream &> ||
std::convertible_to<T, const std::wostream &>;
/*! Describes current terminal features. */
class Terminal
{
Q_DISABLE_COPY(Terminal)
public:
/*! Default constructor. */
inline Terminal() = default;
/*! Default destructor. */
inline ~Terminal() = default;
/*! Supports the given output ansi colors? (ansi is disabled for non-tty). */
bool hasColorSupport(std::ostream &cout = std::cout) const;
/*! Supports the given output ansi colors? (ansi is disabled for non-tty),
wide version. */
bool hasWColorSupport(std::wostream &wcout = std::wcout) const;
/*! Determines whether a file descriptor is associated with a character device. */
bool isatty(FILE *stream) const;
/*! Obtain the current terminal width. */
int width();
/*! Obtain the current terminal height. */
int height();
/*! Get the cached terminal width. */
inline int lastWidth() const noexcept;
/*! Get the cached terminal height. */
inline int lastHeight() const noexcept;
/*! Terminal width and height. */
struct TerminalSize
{
/*! Visible columns. */
int columns;
/*! Visible lines. */
int lines;
};
/*! Get terminal size of the visible area. */
TerminalSize terminalSize() const;
private:
/*! Supports the given output ansi colors? (common logic). */
template<OStreamConcept O>
bool hasColorSupportInternal(O &&cout, FILE *stream) const;
#ifdef _WIN32
/*! Detect if c++ ostream has enabled virtual terminal processing. */
bool hasVt100Support(std::ostream &cout) const;
/*! Detect if c++ wostream has enabled virtual terminal processing,
wide version. */
bool hasVt100Support(std::wostream &wcout) const;
#endif
/*! Cache for detected ansi output. */
mutable std::unordered_map<std::ostream *, bool> m_isAnsiOutput;
/*! Cache for detected ansi output, wide version. */
mutable std::unordered_map<std::wostream *, bool> m_isAnsiWOutput;
/*! Current terminal width. */
int m_lastWidth = 80;
/*! Current terminal height. */
int m_lastHeight = 50;
};
/* public */
int Terminal::lastWidth() const noexcept
{
return m_lastWidth;
}
int Terminal::lastHeight() const noexcept
{
return m_lastHeight;
}
/* private */
template<OStreamConcept O>
bool Terminal::hasColorSupportInternal(O &&cout, FILE *stream) const
{
// Follow https://no-color.org/
if (qEnvironmentVariableIsSet("NO_COLOR"))
return false;
if (qEnvironmentVariable("TERM_PROGRAM") == QLatin1String("Hyper"))
return isatty(stream);
#ifdef _WIN32
return isatty(stream) &&
(hasVt100Support(std::forward<O>(cout)) ||
qEnvironmentVariableIsSet("ANSICON") ||
qEnvironmentVariable("ConEmuANSI") == QLatin1String("ON") ||
qEnvironmentVariable("TERM") == QLatin1String("xterm"));
#endif
// Detect character device, in most cases false when the output is redirected
return isatty(stream);
}
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_TERMINAL_HPP

View File

@@ -0,0 +1,46 @@
#pragma once
#ifndef TOM_TOMTYPES_HPP
#define TOM_TOMTYPES_HPP
#include <orm/macros/systemheader.hpp>
TINY_SYSTEM_HEADER
#include <QString>
#include <memory>
#include <orm/macros/commonnamespace.hpp>
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom
{
/*! Hydrated migration item from the database. */
struct MigrationItem
{
/*! Database ID. */
quint64 id;
/*! Migration name. */
QString migration;
int batch;
};
class Migration;
/*! Migration item used and returned from the rollback. */
struct RollbackItem
{
/*! Database ID. */
quint64 id;
/*! Migration name. */
QString migrationName;
/*! Migration instance. */
std::shared_ptr<Migration> migration;
};
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE
#endif // TOM_TOMTYPES_HPP

View File

@@ -1,6 +1,6 @@
//#pragma code_page(65001) // UTF-8
//IDI_ICON1 ICON "icons/@tom_target@.ico"
//IDI_ICON1 ICON "icons/@TomExample_target@.ico"
#include <windows.h>
#include "tom/version.hpp"
@@ -11,7 +11,7 @@
#define VER_PRODUCTVERSION TINYTOM_VERSION_MAJOR,TINYTOM_VERSION_MINOR,TINYTOM_VERSION_BUGFIX,TINYTOM_VERSION_BUILD
#define VER_PRODUCTVERSION_STR TINYTOM_VERSION_STR "\0"
#define VER_ORIGINALFILENAME_STR "$<TARGET_FILE_NAME:@tom_target@>\0"
#define VER_ORIGINALFILENAME_STR "$<TARGET_FILE_NAME:@TomExample_target@>\0"
#ifdef TINYTOM_NO_DEBUG
# define VER_DEBUG 0
@@ -33,7 +33,7 @@ VS_VERSION_INFO VERSIONINFO
BLOCK "040904B0"
BEGIN
VALUE "CompanyName", "Crystal Studio\0"
VALUE "FileDescription", "tom console for TinyORM\0"
VALUE "FileDescription", "Tom console for TinyORM\0"
VALUE "FileVersion", VER_FILEVERSION_STR
VALUE "InternalName", "tom console\0"
VALUE "LegalCopyright", "Copyright (©) 2022 Crystal Studio\0"
@@ -51,5 +51,5 @@ VS_VERSION_INFO VERSIONINFO
/* End of Version info */
#ifdef __MINGW32__
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "@tom_target@$<TARGET_FILE_SUFFIX:@tom_target@>.manifest"
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "@TomExample_target@$<TARGET_FILE_SUFFIX:@TomExample_target@>.manifest"
#endif

View File

@@ -1,2 +1,27 @@
SOURCES += \
$$PWD/tom/main.cpp \
$$PWD/tom/application.cpp \
$$PWD/tom/commands/command.cpp \
$$PWD/tom/commands/database/wipecommand.cpp \
$$PWD/tom/commands/environmentcommand.cpp \
$$PWD/tom/commands/helpcommand.cpp \
$$PWD/tom/commands/inspirecommand.cpp \
$$PWD/tom/commands/listcommand.cpp \
$$PWD/tom/commands/make/migrationcommand.cpp \
# $$PWD/tom/commands/make/projectcommand.cpp \
$$PWD/tom/commands/migrations/freshcommand.cpp \
$$PWD/tom/commands/migrations/installcommand.cpp \
$$PWD/tom/commands/migrations/migratecommand.cpp \
$$PWD/tom/commands/migrations/refreshcommand.cpp \
$$PWD/tom/commands/migrations/resetcommand.cpp \
$$PWD/tom/commands/migrations/rollbackcommand.cpp \
$$PWD/tom/commands/migrations/statuscommand.cpp \
$$PWD/tom/concerns/callscommands.cpp \
$$PWD/tom/concerns/confirmable.cpp \
$$PWD/tom/concerns/interactswithio.cpp \
$$PWD/tom/concerns/printsoptions.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/terminal.cpp \

View File

@@ -1,95 +0,0 @@
QT *= core sql
QT -= gui
TEMPLATE = app
TARGET = tom
# TinyTom application specific configuration
# ---
CONFIG *= console
# Common Configuration
# ---
include($$TINYORM_SOURCE_TREE/qmake/common.pri)
# TinyTom defines
# ---
DEFINES += PROJECT_TINYTOM
# Release build
CONFIG(release, debug|release): DEFINES += TINYTOM_NO_DEBUG
# Debug build
CONFIG(debug, debug|release): DEFINES *= TINYTOM_DEBUG
# Enable code needed by tests
#build_tests: \
# DEFINES *= TINYTOM_TESTS_CODE
# TinyTom library header and source files
# ---
# tiny_version_numbers() depends on HEADERS (version.hpp)
include(../include/include.pri)
include(src.pri)
# File version
# ---
# Find version numbers in the version header file and assign them to the
# <TARGET>_VERSION_<MAJOR,MINOR,PATCH,TWEAK> and also to the VERSION variable.
load(tiny_version_numbers)
tiny_version_numbers()
# Windows resource and manifest files
# ---
# Find version.hpp
tinyRcIncludepath = $$quote($$PWD/../include/)
# Find Windows manifest
mingw: tinyRcIncludepath += $$quote($$PWD/../resources/)
load(tiny_resource_and_manifest)
tiny_resource_and_manifest($$tinyRcIncludepath, ../resources)
# Use Precompiled headers (PCH)
# ---
include(../include/pch.pri)
# Deployment
# ---
win32-msvc:CONFIG(debug, debug|release) {
win32-msvc: target.path = C:/optx64/$${TARGET}
# else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
}
# Configure TinyORM library
# ---
include($$TINYORM_SOURCE_TREE/qmake/TinyOrm.pri)
# User Configuration
# ---
exists(../conf.pri): \
include(../conf.pri)
#else:is_vcpkg_build: \
# include(../qmake/vcpkgconf.pri)
else: \
error( "'conf.pri' for 'tom' project does not exist. See an example configuration\
in 'tom/conf.pri.example' or call 'vcpkg install' in the project's root." )
# Link against TinyORM library
# ---
INCLUDEPATH += $$quote($$TINYORM_SOURCE_TREE/include/)
LIBS += $$quote(-L$$TINYORM_BUILD_TREE/src$${TINY_RELEASE_TYPE}/)
LIBS += -lTinyOrm

474
tom/src/tom/application.cpp Normal file
View File

@@ -0,0 +1,474 @@
#include "tom/application.hpp"
#include <QCoreApplication>
#include <QDebug>
#ifdef _WIN32
# include <qt_windows.h>
# include <fcntl.h>
# include <io.h>
#endif
#include <orm/databasemanager.hpp>
#include <orm/utils/type.hpp>
#include <orm/version.hpp>
#include "tom/commands/database/wipecommand.hpp"
#include "tom/commands/environmentcommand.hpp"
#include "tom/commands/helpcommand.hpp"
#include "tom/commands/inspirecommand.hpp"
#include "tom/commands/listcommand.hpp"
#include "tom/commands/make/migrationcommand.hpp"
//#include "tom/commands/make/projectcommand.hpp"
#include "tom/commands/migrations/freshcommand.hpp"
#include "tom/commands/migrations/installcommand.hpp"
#include "tom/commands/migrations/migratecommand.hpp"
#include "tom/commands/migrations/refreshcommand.hpp"
#include "tom/commands/migrations/resetcommand.hpp"
#include "tom/commands/migrations/rollbackcommand.hpp"
#include "tom/commands/migrations/statuscommand.hpp"
#include "tom/migrationrepository.hpp"
#include "tom/migrator.hpp"
#ifndef TINYTOM_TESTS_CODE
# include "tom/version.hpp"
#endif
using Orm::ConnectionResolverInterface;
using Orm::Constants::NEWLINE;
using TypeUtils = Orm::Utils::Type;
using Tom::Commands::Command;
using Tom::Commands::Database::WipeCommand;
using Tom::Commands::EnvironmentCommand;
using Tom::Commands::HelpCommand;
using Tom::Commands::InspireCommand;
using Tom::Commands::ListCommand;
using Tom::Commands::Make::MigrationCommand;
//using Tom::Commands::Make::ProjectCommand;
using Tom::Commands::Migrations::FreshCommand;
using Tom::Commands::Migrations::InstallCommand;
using Tom::Commands::Migrations::MigrateCommand;
using Tom::Commands::Migrations::RefreshCommand;
using Tom::Commands::Migrations::ResetCommand;
using Tom::Commands::Migrations::RollbackCommand;
using Tom::Commands::Migrations::StatusCommand;
/*! Invoke Qt's global post routines. */
extern void Q_DECL_IMPORT qt_call_post_routines();
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom {
/* 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)
: m_argc(argc)
, m_argv(argv)
, m_db(std::move(db))
#ifndef TINYTOM_TESTS_CODE
, m_qtApplication(argc, argv)
#endif
, m_environmentEnvName(environmentEnvName)
, m_migrationTable(std::move(migrationTable))
, m_migrations(std::move(migrations))
{
#ifdef _WIN32
// Prepare console input/output character encoding
initializeConsoleEncoding();
#endif
// Following is not relevant in the auto test executables
#ifndef TINYTOM_TESTS_CODE
QCoreApplication::setOrganizationName("TinyORM");
QCoreApplication::setOrganizationDomain("tinyorm.org");
QCoreApplication::setApplicationName("tom");
QCoreApplication::setApplicationVersion(TINYTOM_VERSION_STR);
#endif
// Print a newline at application's normal exit
// initializeAtExit();
// Fix m_argc/m_argv data members if the argv is empty
fixEmptyArgv();
// Initialize the command-line parser
initializeParser(m_parser);
}
int Application::run()
{
// Process the actual command-line arguments given by the user
parseCommandLine();
// Ownership of a unique_ptr()
return createCommand(getCommandName())->run();
}
void Application::logException(const std::exception &e, const bool noAnsi)
{
// TODO future decide how qCritical()/qFatal() really works, also regarding to the Qt Creator's settings 'Ignore first chance access violations' and similar silverqx
// TODO future alse how to correctly setup this in prod/dev envs. silverqx
auto message = QStringLiteral("Caught '%1' Exception:\n%2")
.arg(TypeUtils::classPureBasename(e, true), e.what());
/* Want to have this method static, downside is that the InteractsWithIO has to be
instantiated again. */
Concerns::InteractsWithIO io(noAnsi);
static const auto tmpl = QStringLiteral("%1%2%1").arg(NEWLINE, "%1");
// No-ansi output
if (noAnsi || !io.isAnsiOutput(std::cerr)) {
qCritical().nospace().noquote() << tmpl.arg(std::move(message));
return;
}
/* Print error wall (red box with a white text) */
qCritical().nospace().noquote() << tmpl.arg(io.errorWallInternal(message));
}
QStringList Application::arguments() const
{
// Never obtain arguments from the QCoreApplication instance in unit tests
return hasQtApplication ? QCoreApplication::arguments()
: prepareArguments();
}
#ifdef TINYTOM_TESTS_CODE
std::vector<Application::StatusRow> Application::status() noexcept
{
return StatusCommand::status();
}
void Application::enableInUnitTests() noexcept
{
StatusCommand::setInUnitTests();
}
#endif
/* protected */
/* Application initialization */
#ifdef _WIN32
void Application::initializeConsoleEncoding() const
{
// Set it here so the user doesn't have to deal with this
SetConsoleOutputCP(CP_UTF8);
/* UTF-8 encoding is corrupted for narrow input functions, needed to use wcin/wstring
for an input, input will be in the unicode encoding then needed to translate
unicode to utf8, eg. by QString::fromStdWString(), WideCharToMultiByte(), or
std::codecvt(). It also works with msys2 ucrt64 gcc/clang. */
SetConsoleCP(CP_UTF8);
_setmode(_fileno(stdin), _O_WTEXT);
}
#endif
void Application::fixEmptyArgv()
{
constexpr const auto *const empty = "";
if (m_argc == 0 || m_argv == nullptr) {
m_argc = 0;
m_argv = const_cast<char **>(&empty);
}
}
// CUR tom, remove? silverqx
void Application::initializeAtExit() const
{
std::atexit([]
{
std::cout << std::endl;
});
}
void Application::initializeParser(QCommandLineParser &parser)
{
parser.setApplicationDescription(
QStringLiteral("TinyORM %1").arg(TINYORM_VERSION_STR));
// Common options used in all commands
parser.addOptions(saveOptions({
{ "ansi", "Force ANSI output"},
{ "no-ansi", "Disable ANSI output"},
{ "env", "The environment the command should run under",
"env"}, // Value
{{"h", "help"}, "Display help for the given command. When no command "
"is given display help for the <info>list</info> "
"command"},
{{"n", "no-interaction"}, "Do not ask any interactive question"},
{{"q", "quiet"}, "Do not output any message"},
{{"V", "version"}, "Display this application version"},
{{"v", "verbose"}, "Increase the verbosity of messages: 1 for normal "
"output, 2 for more verbose output and 3 for debug"},
}));
}
const QList<QCommandLineOption> &
Application::saveOptions(QList<QCommandLineOption> &&options)
{
return m_options = std::move(options);
}
QList<QCommandLineOption>
Application::prependOptions(QList<QCommandLineOption> &&options)
{
auto commonOptions = m_options;
m_options = options;
m_options << std::move(commonOptions);
// Return only a new options because they are passed to the addOptions() method
return std::move(options);
}
/* Run command */
void Application::parseCommandLine()
{
/* Can not use QCommandLineParser::ParseAsPositionalArguments because I still need
to check eg. --version and --no-interaction below, it doesn't matter as everything
works just fine. */
/* Arguments have to be prepared manually in unit tests because is not possible
to use the QCoreApplication::arguments() method. Also no error handling needed
here as it is only some kind of pre-parse, whole command is parsed inside
the command itself. */
m_parser.parse(arguments());
initializeEnvironment();
/* Command line arguments are parsed now, the InteractsWithIO() base class can be
initialized. Nothing bad to divide it into two steps as output to the console
is not needed until here. */
Concerns::InteractsWithIO::initialize(m_parser);
if (m_parser.isSet("no-interaction"))
m_interactive = false;
if (m_parser.isSet(QStringLiteral("version")))
showVersion();
}
void Application::initializeEnvironment()
{
/*! Order is as follow, the default value is development, can be overriden by
a env. variable which name is in the m_environmentEnvName data member, highest
priority has --env command-line argument. */
if (auto environmentCmd = m_parser.value("env");
!environmentCmd.isEmpty()
)
m_environment = std::move(environmentCmd);
else if (auto environmentEnv = QString::fromUtf8(m_environmentEnvName).isEmpty()
? ""
: qEnvironmentVariable(m_environmentEnvName);
!environmentEnv.isEmpty()
)
m_environment = std::move(environmentEnv);
}
QString Application::getCommandName()
{
const auto arguments = m_parser.positionalArguments();
if (arguments.isEmpty())
return {};
return arguments.at(0);
}
/* Early exit during parse command-line */
Q_NORETURN void Application::showVersion() const
{
printVersion();
exitApplication(EXIT_SUCCESS);
}
/* Has to be divided into two methods because the printVersion() is called from
the list command. */
void Application::printVersion() const
{
note(QStringLiteral("TinyORM "), false);
info(TINYORM_VERSION_STR);
}
Q_NORETURN void Application::showCommandsList(const int exitCode)
{
ListCommand(*this, m_parser).run();
exitApplication(exitCode);
}
Q_NORETURN void Application::exitApplication(const int exitCode) const
{
/* Invoke post routines manually, it's safe as they will not be called twice even if
the QCoreApplication's desctructor calls also this function. */
qt_call_post_routines();
::exit(exitCode); // NOLINT(concurrency-mt-unsafe)
}
/* Commands factory */
std::unique_ptr<Command>
Application::createCommand(const QString &command, const OptionalParserRef parser,
const bool showHelp)
{
// Use a custom parser if passed as the argument, needed by CallsCommands::call()
auto parserRef = parser ? *parser : std::ref(m_parser);
if (command == QStringLiteral("db:wipe"))
return std::make_unique<WipeCommand>(*this, parserRef);
if (command == QStringLiteral("env"))
return std::make_unique<EnvironmentCommand>(*this, parserRef);
if (command == QStringLiteral("help"))
return std::make_unique<HelpCommand>(*this, parserRef);
if (command == QStringLiteral("inspire"))
return std::make_unique<InspireCommand>(*this, parserRef);
if (command == QStringLiteral("list"))
return std::make_unique<ListCommand>(*this, parserRef);
if (command == QStringLiteral("make:migration"))
return std::make_unique<MigrationCommand>(*this, parserRef);
// if (command == QStringLiteral("make:project"))
// return std::make_unique<ProjectCommand>(*this, parserRef);
if (command == QStringLiteral("migrate"))
return std::make_unique<MigrateCommand>(*this, parserRef, createMigrator());
if (command == QStringLiteral("migrate:fresh"))
return std::make_unique<FreshCommand>(*this, parserRef, createMigrator());
if (command == QStringLiteral("migrate:install"))
return std::make_unique<InstallCommand>(*this, parserRef,
createMigrationRepository());
if (command == QStringLiteral("migrate:rollback"))
return std::make_unique<RollbackCommand>(*this, parserRef, createMigrator());
if (command == QStringLiteral("migrate:refresh"))
return std::make_unique<RefreshCommand>(*this, parserRef, createMigrator());
if (command == QStringLiteral("migrate:reset"))
return std::make_unique<ResetCommand>(*this, parserRef, createMigrator());
if (command == QStringLiteral("migrate:status"))
return std::make_unique<StatusCommand>(*this, parserRef, createMigrator());
// Used by the help command
if (!showHelp)
return nullptr;
// If passed non-existent command then show all commands list
this->showCommandsList(EXIT_FAILURE);
}
std::shared_ptr<MigrationRepository> Application::createMigrationRepository()
{
if (m_repository)
return m_repository;
return m_repository =
std::make_shared<MigrationRepository>(
std::dynamic_pointer_cast<ConnectionResolverInterface>(m_db),
m_migrationTable);
}
std::shared_ptr<Migrator> Application::createMigrator()
{
if (m_migrator)
return m_migrator;
return m_migrator =
std::make_shared<Migrator>(
createMigrationRepository(),
std::dynamic_pointer_cast<ConnectionResolverInterface>(m_db),
m_migrations, m_parser);
}
/* Others */
const std::vector<std::shared_ptr<Application::Command>> &
Application::createCommandsVector()
{
static const std::vector<std::shared_ptr<Command>> cached = [this]
{
return commandNames()
| ranges::views::transform([this](const char *const commandName)
-> std::shared_ptr<Command>
{
return createCommand(commandName);
})
| ranges::to<std::vector<std::shared_ptr<Command>>>();
}();
return cached;
}
const std::vector<const char *> &Application::commandNames() const
{
// Order is important here
static const std::vector<const char *> cached {
// global namespace
"env", "help", "inspire", "list", "migrate",
// db
"db:wipe",
// make
"make:migration", "make:project",
// migrate
"migrate:fresh", "migrate:install", "migrate:refresh", "migrate:reset",
"migrate:rollback", "migrate:status",
};
return cached;
}
QStringList Application::prepareArguments() const
{
QStringList arguments;
arguments.reserve(m_argc);
for (QStringList::size_type i = 0; i < m_argc; ++i)
arguments << QString::fromUtf8(m_argv[i]);
return arguments;
}
/* Auto tests helpers */
#ifdef TINYTOM_TESTS_CODE
/* public */
int Application::runWithArguments(QStringList &&arguments)
{
// Process the actual command-line arguments given by the user
parseCommandLine();
// Ownership of a unique_ptr()
return createCommand(getCommandName())->runWithArguments(std::move(arguments));
}
#endif
} // namespace Tom
TINYORM_END_COMMON_NAMESPACE
// CUR tom, commands I want to implement; completion, test, db:seed, schema:dump silverqx
// CUR tom, implement support for short command names, eg mig:st silverqx

View File

@@ -0,0 +1,198 @@
#include "tom/commands/command.hpp"
#include <QCommandLineParser>
#include <range/v3/range/conversion.hpp>
#include <range/v3/view/iota.hpp>
#include <range/v3/view/zip_with.hpp>
#include <orm/databasemanager.hpp>
#include "tom/application.hpp"
#include "tom/version.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Commands
{
/* public */
Command::Command(Application &application, QCommandLineParser &parser)
: Concerns::InteractsWithIO(parser)
, m_application(application)
, m_parser(parser)
{}
QList<QCommandLineOption> Command::optionsSignature() const
{
return {};
}
int Command::run()
{
initializePositionalArguments();
auto &parser = this->parser();
parser.clearPositionalArguments();
parser.addOptions(optionsSignature());
if (!parser.parse(passedArguments()))
showParserError(parser);
// Show help if --help argument was passed
checkHelpArgument();
return EXIT_SUCCESS;
}
int Command::runWithArguments(QStringList &&arguments)
{
m_arguments = std::move(arguments);
return run();
}
/* Getters */
bool Command::hasPositionalArguments() const
{
return !positionalArguments().empty();
}
bool Command::hasOptions() const
{
return !optionsSignature().isEmpty();
}
/* protected */
/* Getters */
QStringList Command::passedArguments() const
{
if (!m_arguments.isEmpty())
return m_arguments;
// Never obtain arguments from the QCoreApplication instance in tests
return application().hasQtApplication ? QCoreApplication::arguments()
: application().prepareArguments();
}
/* Parser helpers */
bool Command::isSet(const QString &name) const
{
return parser().isSet(name);
}
QString Command::value(const QString &name) const
{
return parser().value(name);
}
QString Command::valueCmd(const QString &name, const QString &key) const
{
if (auto value = parser().value(name);
!value.isEmpty()
)
return QStringLiteral("--%1=%2").arg(key.isEmpty() ? name : key,
std::move(value));
return {};
}
QString Command::boolCmd(const QString &name, const QString &key) const
{
if (!parser().isSet(name))
return {};
return QStringLiteral("--%1").arg(key.isEmpty() ? name : key);
}
bool Command::hasArgument(const QList<QString>::size_type 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();
}
QStringList Command::arguments() const
{
return parser().positionalArguments();
}
QString Command::argument(const QList<QString>::size_type index) const
{
// Default value supported
return parser().positionalArguments()
.value(index,
positionalArguments().at(
static_cast<std::size_t>(index) - 1).defaultValue);
}
QString Command::argument(const QString &name) const
{
// Default value supported
return argument(m_positionalArguments.at(name));
}
Orm::DatabaseConnection &Command::connection(const QString &name) const
{
return application().db().connection(name);
}
QCommandLineParser &Command::parser() const noexcept
{
return m_parser;
}
/* private */
void Command::initializePositionalArguments()
{
const auto &arguments = positionalArguments();
if (arguments.empty())
return;
using ArgumentsSizeType = std::vector<PositionalArgument>::size_type;
m_positionalArguments =
ranges::views::zip_with([](const auto &argument, auto &&index)
-> std::pair<QString, OptionsSizeType>
{
return {argument.name, index};
},
arguments, ranges::views::closed_iota(static_cast<ArgumentsSizeType>(1),
arguments.size())
)
| ranges::to<decltype (m_positionalArguments)>();
// The same as above, I leave above as I want to have one example with zip_with()
// for (OptionsSizeType index = 0; const auto &argument : positionalArguments())
// m_positionalArguments.emplace(argument.name, ++index);
}
void Command::checkHelpArgument() const
{
if (!isSet("help"))
return;
call("help", {name()});
application().exitApplication(EXIT_SUCCESS);
}
void Command::showParserError(const QCommandLineParser &parser) const
{
errorWall(parser.errorText());
application().exitApplication(EXIT_FAILURE);
}
} // namespace Tom::Commands
TINYORM_END_COMMON_NAMESPACE

View File

@@ -0,0 +1,80 @@
#include "tom/commands/database/wipecommand.hpp"
#include <QCommandLineParser>
#include <orm/databaseconnection.hpp>
using Orm::Constants::database_;
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Commands::Database
{
/* public */
WipeCommand::WipeCommand(Application &application, QCommandLineParser &parser)
: Command(application, parser)
, Concerns::Confirmable(*this, 0)
{}
QList<QCommandLineOption> WipeCommand::optionsSignature() const
{
return {
{database_, "The database connection to use", database_}, // Value
{"drop-views", "Drop all tables and views"},
{"drop-types", "Drop all tables and types (Postgres only)"},
{"force", "Force the operation to run when in production"},
};
}
int WipeCommand::run()
{
Command::run();
// Ask for confirmation in the production environment
if (!confirmToProceed())
return EXIT_FAILURE;
// Database connection to use
const auto database = value(database_);
if (isSet("drop-views")) {
dropAllViews(database);
info(QLatin1String("Dropped all views successfully."));
}
dropAllTables(database);
info(QLatin1String("Dropped all tables successfully."));
if (isSet("drop-types")) {
dropAllTypes(database);
info(QLatin1String("Dropped all types successfully."));
}
return EXIT_SUCCESS;
}
/* protected */
void WipeCommand::dropAllTables(const QString &database) const
{
connection(database).getSchemaBuilder()->dropAllTables();
}
void WipeCommand::dropAllViews(const QString &database) const
{
connection(database).getSchemaBuilder()->dropAllViews();
}
void WipeCommand::dropAllTypes(const QString &database) const
{
connection(database).getSchemaBuilder()->dropAllTypes();
}
} // namespace Tom::Commands::Database
TINYORM_END_COMMON_NAMESPACE

View File

@@ -0,0 +1,30 @@
#include "tom/commands/environmentcommand.hpp"
#include "tom/application.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Commands
{
/* public */
EnvironmentCommand::EnvironmentCommand(Application &application,
QCommandLineParser &parser)
: Command(application, parser)
{}
int EnvironmentCommand::run()
{
Command::run();
info(QLatin1String("Current application environment: "), false);
comment(application().environment());
return EXIT_SUCCESS;
}
} // namespace Tom::Commands
TINYORM_END_COMMON_NAMESPACE

View File

@@ -0,0 +1,240 @@
#include "tom/commands/helpcommand.hpp"
#include <QCommandLineOption>
#include <orm/constants.hpp>
#include <orm/tiny/utils/string.hpp>
#include "tom/application.hpp"
using Orm::Constants::SPACE;
using StringUtils = Orm::Tiny::Utils::String;
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Commands
{
/* public */
HelpCommand::HelpCommand(Application &application, QCommandLineParser &parser)
: Command(application, parser)
, Concerns::PrintsOptions(*this, 0)
{}
const std::vector<PositionalArgument> &HelpCommand::positionalArguments() const
{
static const std::vector<PositionalArgument> cached {
{"command_name", "The command name", {}, true, "help"},
};
return cached;
}
QString HelpCommand::help() const
{
return QLatin1String(
" The <info>help</info> command displays help for a given command:\n\n"
" <info>tom</info> help list\n\n"
" To display the list of available commands, please use the <info>list</info> "
"command.");
}
int HelpCommand::run()
{
Command::run();
const auto commandNameArg = argument("command_name");
const auto command = createCommand(commandNameArg);
const auto &arguments = command->positionalArguments();
if (!validateRequiredArguments(arguments))
return EXIT_FAILURE;
printDescriptionSection(*command);
printUsageSection(commandNameArg, *command, arguments);
printArgumentsSection(arguments);
printOptionsSection(*command);
printHelpSection(*command);
return EXIT_SUCCESS;
}
/* protected */
std::unique_ptr<Command> HelpCommand::createCommand(const QString &name) const
{
auto command = application().createCommand(name, std::nullopt, false);
if (command)
return command;
errorWall(QLatin1String("Command '%1' is not defined.").arg(name));
application().exitApplication(EXIT_FAILURE);
}
bool HelpCommand::validateRequiredArguments(
const std::vector<PositionalArgument> &arguments) const
{
// Fail on required argument after optional argument
for (std::vector<PositionalArgument>::size_type i = 1;
i < arguments.size() ; ++i
) {
const auto &left = arguments.at(i - 1);
const auto &right = arguments.at(i);
if (left.optional && !right.optional) {
errorWall(QLatin1String("Cannot add a required argument '%1' after "
"an optional one '%2'.")
.arg(right.name, left.name));
return false;
}
}
// Fail when required argument has a default value
return std::ranges::none_of(arguments, [this](const auto &argument)
{
const auto requiredWithDefault = !argument.optional &&
!argument.defaultValue.isEmpty();
if (requiredWithDefault)
errorWall(QLatin1String("The required argument '%1' has a default value "
"'%2'.")
.arg(argument.name, argument.defaultValue));
return requiredWithDefault;
});
}
void HelpCommand::printDescriptionSection(const Command &command) const
{
comment(QLatin1String("Description:"));
note(QStringLiteral(" %1").arg(command.description()));
}
void HelpCommand::printUsageSection(
const QString &commandNameArg, const Command &command,
const std::vector<PositionalArgument> &arguments) const
{
/* Everything after the option -- (double dash) is treated as positional arguments.
[] means optional, <> means positional argument. If an argument is optional,
all arguments after have to be optional. */
newLine();
comment(QLatin1String("Usage:"));
QString usage(2, SPACE);
usage += commandNameArg;
if (command.hasOptions())
usage += QLatin1String(" [options]");
if (command.hasPositionalArguments()) {
usage += QLatin1String(" [--]");
auto optionalCounter = 0;
for (const auto &argument : arguments) {
auto syntax = argument.syntax.isEmpty() ? argument.name : argument.syntax;
if (argument.optional) {
usage += QStringLiteral(" [<%1>").arg(std::move(syntax));
++optionalCounter;
}
else
usage += QStringLiteral(" <%1>").arg(std::move(syntax));
}
usage += QString(optionalCounter, QChar(']'));
}
note(usage);
}
void HelpCommand::printArgumentsSection(
const std::vector<PositionalArgument> &arguments) const
{
if (arguments.empty())
return;
newLine();
comment(QLatin1String("Arguments:"));
for (const auto argumentsMaxSize = this->argumentsMaxSize(arguments);
const auto &argument : arguments
) {
// Compute indent
auto indent = QString(argumentsMaxSize - argument.name.size(), SPACE);
info(QStringLiteral(" %1%2 ").arg(argument.name, std::move(indent)), false);
note(argument.description, false);
printArgumentDefaultValue(argument);
}
}
int HelpCommand::argumentsMaxSize(const std::vector<PositionalArgument> &arguments) const
{
const auto it = std::ranges::max_element(arguments, std::less {},
[](const auto &argument)
{
return argument.name.size();
});
return static_cast<int>((*it).name.size());
}
void HelpCommand::printArgumentDefaultValue(const PositionalArgument &argument) const
{
// Empty default value, don't render
const auto &defaultValueRef = argument.defaultValue;
if (defaultValueRef.isEmpty()) {
newLine();
return;
}
// Quote string type
auto defaultValue = StringUtils::isNumber(defaultValueRef, true)
? defaultValueRef
: QStringLiteral("\"%1\"").arg(defaultValueRef);
comment(QStringLiteral(" [default: %1]").arg(std::move(defaultValue)));
}
int HelpCommand::printOptionsSection(const Command &command) const
{
// Prepare the command parser to show command options
parser().addOptions(application().prependOptions(command.optionsSignature()));
return PrintsOptions::printOptionsSection(false);
}
void HelpCommand::printHelpSection(const Command &command) const
{
const auto help = command.help();
if (help.isEmpty())
return;
newLine();
comment(QLatin1String("Help:"));
note(help);
}
} // namespace Tom::Commands
TINYORM_END_COMMON_NAMESPACE

View File

@@ -0,0 +1,80 @@
#include "tom/commands/inspirecommand.hpp"
#include <random>
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Commands
{
/* public */
InspireCommand::InspireCommand(Application &application, QCommandLineParser &parser)
: Command(application, parser)
{}
int InspireCommand::run()
{
Command::run();
static const std::vector inspires {
"Act only according to that maxim whereby you can, at the same time, will that it should become a universal law. - Immanuel Kant",
"An unexamined life is not worth living. - Socrates",
"Be present above all else. - Naval Ravikant",
"Do what you can, with what you have, where you are. - Theodore Roosevelt",
"Happiness is not something readymade. It comes from your own actions. - Dalai Lama",
"He who is contented is rich. - Laozi",
"I begin to speak only when I am certain what I will say is not better left unsaid. - Cato the Younger",
"I have not failed. I've just found 10,000 ways that won't work. - Thomas Edison",
"If you do not have a consistent goal in life, you can not live it in a consistent way. - Marcus Aurelius",
"It is never too late to be what you might have been. - George Eliot",
"It is not the man who has too little, but the man who craves more, that is poor. - Seneca",
"It is quality rather than quantity that matters. - Lucius Annaeus Seneca",
"Knowing is not enough; we must apply. Being willing is not enough; we must do. - Leonardo da Vinci",
"Let all your things have their places; let each part of your business have its time. - Benjamin Franklin",
"Live as if you were to die tomorrow. Learn as if you were to live forever. - Mahatma Gandhi",
"No surplus words or unnecessary actions. - Marcus Aurelius",
"Nothing worth having comes easy. - Theodore Roosevelt",
"Order your soul. Reduce your wants. - Augustine",
"People find pleasure in different ways. I find it in keeping my mind clear. - Marcus Aurelius",
"Simplicity is an acquired taste. - Katharine Gerould",
"Simplicity is the consequence of refined emotions. - Jean D'Alembert",
"Simplicity is the essence of happiness. - Cedric Bledsoe",
"Simplicity is the ultimate sophistication. - Leonardo da Vinci",
"Smile, breathe, and go slowly. - Thich Nhat Hanh",
"The only way to do great work is to love what you do. - Steve Jobs",
"The whole future lies in uncertainty: live immediately. - Seneca",
"Very little is needed to make a happy life. - Marcus Aurelius",
"Waste no more time arguing what a good man should be, be one. - Marcus Aurelius",
"Well begun is half done. - Aristotle",
"When there is no desire, all things are at peace. - Laozi",
"Walk as if you are kissing the Earth with your feet. - Thich Nhat Hanh",
"Because you are alive, everything is possible. - Thich Nhat Hanh",
"Breathing in, I calm body and mind. Breathing out, I smile. - Thich Nhat Hanh",
"Life is available only in the present moment. - Thich Nhat Hanh",
"The best way to take care of the future is to take care of the present moment. - Thich Nhat Hanh",
"Nothing in life is to be feared, it is only to be understood. Now is the time to understand more, so that we may fear less. - Marie Curie",
};
static const auto size = inspires.size();
// Obtain a random number from hardware
std::random_device rd;
// Seed the generator
std::default_random_engine generator(rd());
// Define the range
std::uniform_int_distribution<std::size_t>
distribute(0, static_cast<decltype (inspires)::size_type>(size) - 1);
comment(inspires.at(distribute(generator)));
// Alternative implementation
// std::srand(std::time(nullptr));
// comment(inspires.at(std::rand() % size));
return EXIT_SUCCESS;
}
} // namespace Tom::Commands
TINYORM_END_COMMON_NAMESPACE

View File

@@ -0,0 +1,234 @@
#include "tom/commands/listcommand.hpp"
#include <QCommandLineOption>
#include <range/v3/range/conversion.hpp>
#include <range/v3/view/slice.hpp>
#include <orm/constants.hpp>
#include "tom/application.hpp"
using Orm::Constants::COLON;
using Orm::Constants::SPACE;
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Commands
{
/* public */
ListCommand::ListCommand(Application &application, QCommandLineParser &parser)
: Command(application, parser)
, Concerns::PrintsOptions(*this, 0)
{}
const std::vector<PositionalArgument> &ListCommand::positionalArguments() const
{
static const std::vector<PositionalArgument> cached {
{"namespace", "The namespace name", {}, true},
};
return cached;
}
QList<QCommandLineOption> ListCommand::optionsSignature() const
{
return {
{"raw", "To output raw command list"},
};
}
QString ListCommand::help() const
{
return QLatin1String(
" The <info>list</info> command lists all commands:\n\n"
" <info>tom list</info>\n\n"
" You can also display the commands for a specific namespace:\n\n"
" <info>tom list test</info>\n\n"
" It's also possible to get raw list of commands (useful for embedding command "
"runner):\n\n"
" <info>tom list --raw</info>");
}
int ListCommand::run()
{
Command::run();
const auto namespaceArg = argument("namespace");
return isSet("raw") ? raw(namespaceArg) : full(namespaceArg);
}
/* protected */
int ListCommand::full(const QString &namespaceArg)
{
application().printVersion();
newLine();
comment(QLatin1String("Usage:"));
note(QLatin1String(" command [options] [--] [arguments]"));
// Options section
const auto optionsMaxSize = printOptionsSection(true);
// Commands section
printCommandsSection(namespaceArg, optionsMaxSize);
return EXIT_SUCCESS;
}
int ListCommand::raw(const QString &namespaceArg)
{
const auto &commands = getCommandsByNamespace(namespaceArg);
const auto it = std::ranges::max_element(commands, std::less {},
[](const auto &command)
{
return command->name().size();
});
const auto commandMaxSize = static_cast<int>((*it)->name().size());
for (const auto &command : commands) {
auto commandName = command->name();
auto indent = QString(commandMaxSize - commandName.size(), SPACE);
note(QStringLiteral("%1%2 %3").arg(std::move(commandName), std::move(indent),
command->description()));
}
return EXIT_SUCCESS;
}
/* Commands section */
void ListCommand::printCommandsSection(const QString &namespaceArg,
const int optionsMaxSize) const
{
const auto hasNamespaceArg = !namespaceArg.isNull();
newLine();
if (hasNamespaceArg)
comment(QLatin1String("Available commands for the '%1' namespace:")
.arg(namespaceArg.isEmpty() ? QLatin1String("global") : namespaceArg));
else
comment(QLatin1String("Available commands:"));
const auto &commands = getCommandsByNamespace(namespaceArg);
// Get max. command size in all command names
const auto commandsMaxSize = this->commandsMaxSize(commands, optionsMaxSize);
// Print commands to the console
printCommands(commands, commandsMaxSize, hasNamespaceArg);
}
int ListCommand::commandsMaxSize(
const std::vector<std::shared_ptr<Command>> &commands,
const int optionsMaxSize) const
{
const auto it = std::ranges::max_element(commands, std::less {},
[](const auto &command)
{
return command->name().size();
});
auto commandsMaxSize = static_cast<int>((*it)->name().size());
// Align commands' description to the same level as options' description
if (commandsMaxSize < optionsMaxSize)
commandsMaxSize = optionsMaxSize;
return commandsMaxSize;
}
void ListCommand::printCommands(
const std::vector<std::shared_ptr<Command>> &commands,
const int commandsMaxSize, const bool hasNamespaceArg) const
{
// Currently rendering NS
QString renderingNamespace;
for (const auto &command : commands) {
auto commandName = command->name();
// Begin a new namespace section
tryBeginNsSection(renderingNamespace, commandName, hasNamespaceArg);
auto indent = QString(commandsMaxSize - commandName.size(), SPACE);
info(QStringLiteral(" %1%2").arg(std::move(commandName), std::move(indent)),
false);
note(QStringLiteral(" %1").arg(command->description()));
}
}
void ListCommand::tryBeginNsSection(
QString &renderingNamespace, const QString &commandName,
const bool hasNamespaceArg) const
{
const auto commandNamespace = this->commandNamespace(commandName);
if (hasNamespaceArg || commandNamespace == renderingNamespace)
return;
// Update currently rendering NS section
renderingNamespace = commandNamespace;
comment(QStringLiteral(" %1").arg(renderingNamespace));
}
QString ListCommand::commandNamespace(const QString &commandName) const
{
if (!commandName.contains(COLON))
return {};
return commandName.split(COLON).at(0);
}
const std::vector<std::shared_ptr<Command>> &
ListCommand::getCommandsByNamespace(const QString &name) const
{
// isNull() needed because still able to return global namespace for an empty string
if (name.isNull())
return application().createCommandsVector();
/* This avoids one copy that would be done if commands would be returned by a value,
key thing is that it can be returned as a const reference, believe it it works. */
static std::vector<std::shared_ptr<Command>> cached;
return cached = getCommandsInNamespace(name);
}
std::vector<std::shared_ptr<Command>>
ListCommand::getCommandsInNamespace(const QString &name) const
{
/* First number is index where it starts (0-based), second the number where it ends.
tuple is forwarded as args to the ranges::views::slice(). */
static const std::unordered_map<QString, std::tuple<int, int>> cached {
{"", std::make_tuple(0, 5)},
{"global", std::make_tuple(0, 5)},
{"db", std::make_tuple(5, 6)},
{"make", std::make_tuple(6, 8)},
{"migrate", std::make_tuple(8, 14)},
};
if (!cached.contains(name)) {
errorWall(QLatin1String("There are no commands defined in the '%1' namespace.")
.arg(name));
application().exitApplication(EXIT_FAILURE);
}
return application().createCommandsVector()
| std::apply(ranges::views::slice, cached.at(name))
| ranges::to<std::vector<std::shared_ptr<Command>>>();
}
} // namespace Tom::Commands
TINYORM_END_COMMON_NAMESPACE

View File

@@ -0,0 +1,99 @@
#include "tom/commands/make/migrationcommand.hpp"
#include <QCommandLineOption>
#include <orm/constants.hpp>
#include <orm/tiny/utils/string.hpp>
using fspath = std::filesystem::path;
using Orm::Constants::NAME;
using StringUtils = Orm::Tiny::Utils::String;
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Commands::Make
{
/* public */
MigrationCommand::MigrationCommand(Application &application, QCommandLineParser &parser)
: Command(application, parser)
{}
const std::vector<PositionalArgument> &MigrationCommand::positionalArguments() const
{
static const std::vector<PositionalArgument> cached {
{NAME, "The name of the migration"},
};
return cached;
}
QList<QCommandLineOption> MigrationCommand::optionsSignature() const
{
return {
{"create", "The table to be created", "create"},
{"table", "The table to migrate", "table"},
{"path", "The location where the migration file should be created", "path"},
{"realpath", "Indicate any provided migration file paths are pre-resolved "
"absolute paths"},
{"fullpath", "Output the full path of the migration"},
};
}
int MigrationCommand::run()
{
Command::run();
/* It's possible for the developer to specify the tables to modify in this
schema operation. The developer may also specify if this table needs
to be freshly created so we can create the appropriate migrations. */
const auto name = StringUtils::snake(argument(NAME).trimmed());
auto table = value(QStringLiteral("table"));
auto createArg = value(QStringLiteral("create"));
auto create = isSet(QStringLiteral("create"));
/* If no table was given as an option but a create option is given then we
will use the "create" option as the table name. This allows the devs
to pass a table name into this option as a short-cut for creating. */
if (table.isEmpty() && !createArg.isEmpty()) {
table = createArg;
create = true;
}
// CUR tom, finish TableGuesser, when --create/--table params are not passed silverqx
/* Next, we will attempt to guess the table name if the migration name has
"create" in the name. This will allow us to provide a convenient way
of creating migrations that create new tables for the application. */
// if (!table.isEmpty())
// auto [table, create] = TableGuesser::guess(name);
/* Now we are ready to write the migration out to disk. Once we've written
the migration out. */
writeMigration(name, table, create);
return EXIT_SUCCESS;
}
void MigrationCommand::writeMigration(const QString &name, const QString &table,
const bool create) const
{
auto migrationFilePath = m_creator.create(name, getMigrationPath(), table, create);
// make_preferred() returns reference and filename() creates a new fs::path instance
const auto migrationFile = isSet("fullpath") ? migrationFilePath.make_preferred()
: migrationFilePath.filename();
info(QLatin1String("Created Migration: "), false);
note(QString::fromWCharArray(migrationFile.c_str()));
}
} // namespace Tom::Commands::Make
TINYORM_END_COMMON_NAMESPACE

View File

@@ -0,0 +1,95 @@
#include "tom/commands/make/projectcommand.hpp"
#include <QCommandLineOption>
#include <orm/constants.hpp>
//using fspath = std::filesystem::path;
using Orm::Constants::NAME;
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Commands::Make
{
/* public */
ProjectCommand::ProjectCommand(Application &application, QCommandLineParser &parser)
: Command(application, parser)
{}
const std::vector<PositionalArgument> &ProjectCommand::positionalArguments() const
{
static const std::vector<PositionalArgument> cached {
{NAME, "The name of the project"},
};
return cached;
}
QList<QCommandLineOption> ProjectCommand::signature() const
{
return {
{"qmake", "Create qmake project"},
{"cmake", "Create CMake project"},
{"tinyorm", "Crete TinyORM project"},
{"tom", "Crete tom project"},
{"path", "The location where the project should be created", "path"},
{"realpath", "Indicate the <info>path</info> argument is an absolute path"},
};
}
int ProjectCommand::run()
{
Command::run();
/* It's possible for the developer to specify the tables to modify in this
schema operation. The developer may also specify if this table needs
to be freshly created so we can create the appropriate migrations. */
// const auto name = StringUtils::snake(argument(NAME).trimmed());
// auto table = value(QStringLiteral("table"));
// auto createArg = value(QStringLiteral("create"));
// auto create = isSet(QStringLiteral("create"));
// /* If no table was given as an option but a create option is given then we
// will use the "create" option as the table name. This allows the devs
// to pass a table name into this option as a short-cut for creating. */
// if (table.isEmpty() && !createArg.isEmpty()) {
// table = createArg;
// create = true;
// }
// // CUR tom, finish, when --create/--table params are not passed silverqx
// /* Next, we will attempt to guess the table name if the migration name has
// "create" in the name. This will allow us to provide a convenient way
// of creating migrations that create new tables for the application. */
//// if (!table.isEmpty())
//// auto [table, create] = TableGuesser::guess(name);
// /* Now we are ready to write the migration out to disk. Once we've written
// the migration out. */
// writeMigration(name, table, create);
return EXIT_SUCCESS;
}
//void ProjectCommand::writeMigration(const QString &name, const QString &table,
// const bool create) const
//{
// auto migrationFilePath = m_creator.create(name, getMigrationPath(), table, create);
// // make_preferred() returns reference and filename() creates a new fs::path instance
// const auto migrationFile = isSet("fullpath") ? migrationFilePath.make_preferred()
// : migrationFilePath.filename();
// info(QLatin1String("Created Migration: "), false)
// .note(QString::fromWCharArray(migrationFile.c_str()));
//}
} // namespace Tom::Commands::Make
TINYORM_END_COMMON_NAMESPACE

View File

@@ -0,0 +1,85 @@
#include "tom/commands/migrations/freshcommand.hpp"
#include <QCommandLineParser>
#include <orm/constants.hpp>
#include "tom/migrator.hpp"
using Orm::Constants::database_;
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Commands::Migrations
{
/* public */
FreshCommand::FreshCommand(
Application &application, QCommandLineParser &parser,
std::shared_ptr<Migrator> migrator
)
: Command(application, parser)
, Concerns::Confirmable(*this, 0)
, m_migrator(std::move(migrator))
{}
QList<QCommandLineOption> FreshCommand::optionsSignature() const
{
return {
{database_, "The database connection to use", database_}, // Value
{"drop-views", "Drop all tables and views"},
{"drop-types", "Drop all tables and types (Postgres only)"},
{"force", "Force the operation to run when in production"},
// {"schema-path", "The path to a schema dump file"}, // Value
// {"seed", "Indicates if the seed task should be re-run"},
// {"seeder", "The class name of the root seeder", "seeded"}, // Value
{"step", "Force the migrations to be run so they can be rolled back "
"individually"},
};
}
int FreshCommand::run()
{
Command::run();
// Ask for confirmation in the production environment
if (!confirmToProceed())
return EXIT_FAILURE;
// Database connection to use
auto databaseCmd = valueCmd(database_);
call("db:wipe", {databaseCmd,
QStringLiteral("--force"),
boolCmd("drop-views"),
boolCmd("drop-types")});
call("migrate", {databaseCmd,
QStringLiteral("--force"),
boolCmd("step"),
valueCmd("schema-path")});
// if (needsSeeding())
// runSeeder(std::move(databaseCmd));
return EXIT_SUCCESS;
}
/* protected */
bool FreshCommand::needsSeeding() const
{
return isSet("seed") || !value("seeder").isEmpty();
}
void FreshCommand::runSeeder(QString &&databaseCmd) const
{
call("db:seed", {std::move(databaseCmd),
QStringLiteral("--force"),
valueCmd("seeder", "class")});
}
} // namespace Tom::Commands::Migrations
TINYORM_END_COMMON_NAMESPACE

View File

@@ -0,0 +1,51 @@
#include "tom/commands/migrations/installcommand.hpp"
#include <QCommandLineParser>
#include <orm/constants.hpp>
#include "tom/migrationrepository.hpp"
using Orm::Constants::database_;
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Commands::Migrations
{
/* public */
InstallCommand::InstallCommand(
Application &application, QCommandLineParser &parser,
std::shared_ptr<MigrationRepository> repository
)
: Command(application, parser)
, m_repository(std::move(repository))
{}
QList<QCommandLineOption> InstallCommand::optionsSignature() const
{
return {
{database_, "The database connection to use", database_}, // Value
};
}
int InstallCommand::run()
{
Command::run();
// Database connection to use
m_repository->setConnection(value(database_), isDebugVerbosity());
m_repository->createRepository();
info(QLatin1String("Migration table created successfully."));
return EXIT_SUCCESS;
}
} // namespace Tom::Commands::Migrations
TINYORM_END_COMMON_NAMESPACE
// FUTURE tom, add migrate:uninstall or migrate:install --uninstall silverqx

View File

@@ -0,0 +1,100 @@
#include "tom/commands/migrations/migratecommand.hpp"
#include <QCommandLineParser>
#include <orm/constants.hpp>
#include "tom/migrator.hpp"
using Orm::Constants::database_;
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Commands::Migrations
{
/* public */
MigrateCommand::MigrateCommand(
Application &application, QCommandLineParser &parser,
std::shared_ptr<Migrator> migrator
)
: Command(application, parser)
, Concerns::Confirmable(*this, 0)
, m_migrator(std::move(migrator))
{}
QList<QCommandLineOption> MigrateCommand::optionsSignature() const
{
return {
{database_, "The database connection to use", database_}, // Value
{"force", "Force the operation to run when in production"},
{"pretend", "Dump the SQL queries that would be run"},
{"schema-path", "The path to a schema dump file"}, // Value
// {"seed", "Indicates if the seed task should be re-run"},
// {"seeder", "The class name of the root seeder", "seeded"}, // Value
{"step", "Force the migrations to be run so they can be rolled back "
"individually"},
};
}
int MigrateCommand::run()
{
Command::run();
// Ask for confirmation in the production environment
if (!confirmToProceed())
return EXIT_FAILURE;
// Database connection to use
return m_migrator->usingConnection(value(database_), isDebugVerbosity(), [this]
{
// Install db repository and load schema state
prepareDatabase();
/* Next, we will check to see if a path option has been defined. If it has
we will use the path relative to the root of this installation folder
so that migrations may be run for any path within the applications. */
m_migrator->run({isSet("pretend"), isSet("step")});
/* 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();
return EXIT_SUCCESS;
});
}
/* protected */
void MigrateCommand::prepareDatabase() const
{
if (!m_migrator->repositoryExists())
call(QStringLiteral("migrate:install"), {value(database_)});
if (!m_migrator->hasRunAnyMigrations() && !isSet("pretend"))
loadSchemaState();
}
void MigrateCommand::loadSchemaState() const
{
// CUR tom, finish load schema silverqx
}
bool MigrateCommand::needsSeeding() const
{
return !isSet("pretend") && (isSet("seed") || !value("seeder").isEmpty());
}
void MigrateCommand::runSeeder() const
{
call("db:seed", {valueCmd(database_),
QStringLiteral("--force"),
valueCmd("seeder", "class")});
}
} // namespace Tom::Commands::Migrations
TINYORM_END_COMMON_NAMESPACE

View File

@@ -0,0 +1,87 @@
#include "tom/commands/migrations/refreshcommand.hpp"
#include <QCommandLineParser>
#include <orm/constants.hpp>
#include "tom/migrator.hpp"
using Orm::Constants::database_;
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Commands::Migrations
{
/* public */
RefreshCommand::RefreshCommand(
Application &application, QCommandLineParser &parser,
std::shared_ptr<Migrator> migrator
)
: Command(application, parser)
, Concerns::Confirmable(*this, 0)
, m_migrator(std::move(migrator))
{}
QList<QCommandLineOption> RefreshCommand::optionsSignature() const
{
return {
{database_, "The database connection to use", database_}, // Value
{"force", "Force the operation to run when in production"},
// {"seed", "Indicates if the seed task should be re-run"},
// {"seeder", "The class name of the root seeder", "seeded"}, // Value
{"step", "The number of migrations to be reverted & re-run", "step"}, // Value
{"step-migrate", "Force the migrations to be run so they can be rolled back "
"individually"},
};
}
int RefreshCommand::run()
{
Command::run();
// Ask for confirmation in the production environment
if (!confirmToProceed())
return EXIT_FAILURE;
// Database connection to use
auto databaseCmd = valueCmd(database_);
/* If the "step" option is specified it means we only want to rollback a small
number of migrations before migrating again. For example, the user might
only rollback and remigrate the latest four migrations instead of all. */
if (const auto step = value("step").toInt(); step > 0)
call("migrate:rollback", {databaseCmd,
QStringLiteral("--force"),
valueCmd("step")});
else
call("migrate:reset", {databaseCmd, QStringLiteral("--force")});
call("migrate", {databaseCmd,
QStringLiteral("--force"),
boolCmd("step-migrate", "step")});
// if (needsSeeding())
// runSeeder(std::move(databaseCmd));
return EXIT_SUCCESS;
}
/* protected */
bool RefreshCommand::needsSeeding() const
{
return isSet("seed") || !value("seeder").isEmpty();
}
void RefreshCommand::runSeeder(QString &&databaseCmd) const
{
call("db:seed", {std::move(databaseCmd),
QStringLiteral("--force"),
valueCmd("seeder", "class")});
}
} // namespace Tom::Commands::Migrations
TINYORM_END_COMMON_NAMESPACE

View File

@@ -0,0 +1,61 @@
#include "tom/commands/migrations/resetcommand.hpp"
#include <QCommandLineParser>
#include <orm/constants.hpp>
#include "tom/migrator.hpp"
using Orm::Constants::database_;
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Commands::Migrations
{
/* public */
ResetCommand::ResetCommand(
Application &application, QCommandLineParser &parser,
std::shared_ptr<Migrator> migrator
)
: Command(application, parser)
, Concerns::Confirmable(*this, 0)
, m_migrator(std::move(migrator))
{}
QList<QCommandLineOption> ResetCommand::optionsSignature() const
{
return {
{database_, "The database connection to use", database_}, // Value
{"force", "Force the operation to run when in production"},
{"pretend", "Dump the SQL queries that would be run"},
};
}
int ResetCommand::run()
{
Command::run();
// Ask for confirmation in the production environment
if (!confirmToProceed())
return EXIT_FAILURE;
// Database connection to use
return m_migrator->usingConnection(value(database_), isDebugVerbosity(), [this]
{
if (!m_migrator->repositoryExists()) {
comment(QLatin1String("Migration table not found."));
return EXIT_FAILURE;
}
m_migrator->reset(isSet("pretend"));
return EXIT_SUCCESS;
});
}
} // namespace Tom::Commands::Migrations
TINYORM_END_COMMON_NAMESPACE

View File

@@ -0,0 +1,58 @@
#include "tom/commands/migrations/rollbackcommand.hpp"
#include <QCommandLineParser>
#include <orm/constants.hpp>
#include "tom/migrator.hpp"
using Orm::Constants::database_;
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Tom::Commands::Migrations
{
/* public */
RollbackCommand::RollbackCommand(
Application &application, QCommandLineParser &parser,
std::shared_ptr<Migrator> migrator
)
: Command(application, parser)
, Concerns::Confirmable(*this, 0)
, m_migrator(std::move(migrator))
{}
QList<QCommandLineOption> RollbackCommand::optionsSignature() const
{
return {
{database_, "The database connection to use", database_}, // Value
{"force", "Force the operation to run when in production"},
{"pretend", "Dump the SQL queries that would be run"},
{"step", "The number of migrations to be reverted", "step"}, // Value
};
}
int RollbackCommand::run()
{
Command::run();
// Ask for confirmation in the production environment
if (!confirmToProceed())
return EXIT_FAILURE;
// Database connection to use
return m_migrator->usingConnection(value(database_), isDebugVerbosity(), [this]
{
// Validation not needed as the toInt() returns 0 if conversion fails, like it
m_migrator->rollback({.pretend = isSet("pretend"),
.stepValue = value("step").toInt()});
return EXIT_SUCCESS;
});
}
} // namespace Tom::Commands::Migrations
TINYORM_END_COMMON_NAMESPACE

Some files were not shown because too many files have changed in this diff Show More