Files
TinyORM/NOTES.txt
T
2024-02-05 16:27:49 +01:00

2676 lines
93 KiB
Plaintext

Increase/bump the release version:
----------------------------------
- bump message format:
bump version to TinyORM v0.36.5 and tom v0.7.0
- just simply search the current version number in all files eg. 0.36.5
- don't forget to update a version number in the silverqx/TinyORM-HelloWorld find_package() call
- TinyORM:
9 files must be modified, README.md, docs/README.mdx, two vcpkg.json files, NOTES.txt,
hello-world.mdx, migrations.mdx, tinyorm.mdx plus bumped version.hpp file
- tom:
5 files must be modified, README.md, docs/README.mdx, NOTES.txt, plus bumped version.hpp file
- increase in the following files
include/orm/version.hpp
tests/TinyUtils/src/version.hpp
tom/include/tom/version.hpp
- and also informational version in badges
README.md
docs/README.md
- vcpkg ports version number
cmake/vcpkg/ports/tinyorm/vcpkg.json
cmake/vcpkg/ports/tinyorm-qt5/vcpkg.json
- verions in docs
hello-world.mdx#cmake-project
hello-world.mdx#fetchcontent
migrations.mdx#cmake-project
tinyorm.mdx#consume-tinyorm-library-cmake
Number of Unit tests:
---------------------
- Qt5 has 14 unit tests less; the QSQLITE driver tests are excluded in the tst_migrate;
Not SKIPPED but excluded using preprocessor macros!
Testing of the Qt 5 QSQLITE driver is excluded because it doesn't support
ALTER TABLE DROP COLUMN, support for dropping columns was added
in the SQLite v3.35.0.
- Linux has 3 unit tests less and 3 SKIPPED unit tests more;
checking exe properties are excluded in the tst_versions;
They are SKIPPED!
Linux doesn't have exe properties like version, description, ...
How to updated vcpkg tinyorm and tinyorm-qt5 ports:
---------------------------------------------------
Everything needed is in the tools/deploy.ps1 script so I write only a small summary.
Important are the vcpkg_from_github() REF and SHA512 options in the portfile.cmake.
Prefer tags in the REF but can also be a commit ID.
The SHA512 is a hash of the source code tinyorm.tar.gz archive, the tools/Get-VcpkgHash.ps1
script can be used to obtain this hash. The URL to download this archive is:
https://github.com/silverqx/TinyORM/archive/v0.36.5.tar.gz
https://github.com/silverqx/TinyORM/archive/ca8909896247b21bf08d62a5109b23e9f65c89e1.tar.gz
If only the vcpkg is updated but the TinyORM version number is not bumped then
the port-version field must be added or bumped in the vcpkg.json file.
But all of this is handled by the tools/deploy.ps1 script. 👌
Proxy methods that internally call the toBase() (applySoftDeletes):
-------------------------------------------------------------------
I have little misunderstood when the SoftDeletes constraint will be applied, I thought that it will be applied only
during the TinyBuilder::toBase(), that is not absolutely true because whether it will be applied is controlled by
the Model::newQueryXyz() methods. These Model::newQueryXyz() methods are called on various places, somehwhere
the newQuery() (applies SoftDeletes) is called and on another places the newModelQuery() (doesn't apply SoftDeletes) or newQueryWithoutRelationships() is called.
Anyway I leave this new structure of proxy methods that are divided by the toBase() or getQuery() internal method calls,
it makes sense.
Proxy methods on the TinyBuilder that need to apply SoftDeletes (call toBase() internally).
aggregate
average
avg
count
dd
decrement
doesntExist
doesntExistOr
dump
exists
existsOr
explain (currently not implemented)
implode
increment
max
min
pluck
remove
sum
toSql
update
upsert
These three getters are discutable, but I will apply SoftDeletes, it won't hurt anything:
from
getConnection
getGrammar
There is not need to apply SoftDeletes for the getBindings() as the BuildsSoftDeletes is not adding any new bindings:
getBindings
There is not need to apply SoftDeletes on insert methods:
insert
insertGetId
insertOrIgnore
insertUsing
There is not need to apply SoftDeletes on these:
raw
QDateTime and database date/time types:
---------------------------------------
The TinyORM handles the QDateTime's time zone correctly, it converts a time zone to the time zone
defined in the "qt_timezone" connection configuration option and then it converts a QDateTime
instance to a string using the Model::u_dateFormat,
look at the Model::setAttribute() -> Model::fromDateTime().
Also, look at the commit that has an extensive description:
QDateTime overhaul 🤯🤐🙃 (1ded27bb)
MySQL:
---
Qt QMYSQL driver:
It's not so simple, first, it ignores the QDateTime timezone, it simply takes
a DateTime value that was given during the QDateTime creation (ctor/fromString/...),
and exactly this value will be sent to the MySQL database.
datetime column type:
- will save exactly the same value that was sent to the database
timestamp column type:
- is another story, here is important the MySQL timezone session variable,
the MySQL server converts the DateTime value that was sent to the UTC timezone
for storage and this UTC value will be saved in the database, the same is true during
retrieval of this value, so it converts it from the UTC to a session timezone
Database:
MySQL database converts TIMESTAMP values from the current timezone to UTC
for storage, and back from UTC to the current timezone for retrieval.
This does not occur for other types such as DATETIME.
By default, the current timezone for each connection is the server's time.
The timezone can be set on a per-connection basis using the time_zone system
variable.
Summary:
Sent:
It IGNORES the QDateTime timezone!
MYSQL_TIME *myTime = toMySqlDate(val.toDate(), val.toTime(), val.userType());
And the toMySqlDate() function picks internally the year, month, hour, minute, ...
one by one, so it calls date.year(), date.month(), time.hour(), ...
Retrieval:
Returns in the local timezone.
- for prepared statements:
return QDateTime(date, time);
- for non-prepared statements:
return qDateTimeFromString(val); it internally calls
return QVariant(QDateTime::fromString(val, Qt::ISODate));
SQLite:
---
Qt QSQLITE driver:
It doesn't ignore the QDateTime timezone, it converts the QDateTime to a string using
.toString(Qt::ISODateWithMs) and this string value is sent to the database and
will be saved as a string column.
Database:
The SQLite database doesn't support DateTime-related types but has functions
for working with or converting DateTimes; they also allow to convert between
different timezones (using eg. strftime()).
Summary:
Sent:
It DOESN'T ignore the QDateTime timezone.
dateTime.toString(Qt::ISODateWithMs);
Retrieval:
It returns QVariant(QMetaType::QString).
- so it's up to a user how it will instantiate a QDateTime object from this
string value
PostgreSQL:
---
Qt QPSQL driver:
It's not so simple, it converts all QDateTime time zone to UTC and sends it to the database
in the ISO UTC format, for both for timestamps/datetime with or without time zone.
So the timestamps with time zone behave exactly like for the MySQL DB.
But timestamps without time zone don't, they are converted to the LOCAL DB server time zone
by DB before they are saved to DB storage.
Database:
The PostgreSQL server timezone can be set using the set TIME ZONE or set TIMEZONE TO
session variable or by the PGTZ environment variable, or of course using the main
configuration file and using the timezone configuration setting eg.
timezone = 'Europe/Bratislava'.
Summary:
Sent:
It DOESN'T ignore the QDateTime timezone.
// we force the value to be considered with a timezone information, and we force it to be UTC
// this is safe since postgresql stores only the UTC value and not the timezone offset (only used
// while parsing), so we have correct behavior in both case of with timezone and without tz
r = QStringLiteral("TIMESTAMP WITH TIME ZONE ") + QLatin1Char('\'') +
QLocale::c().toString(field.value().toDateTime().toUTC(), u"yyyy-MM-ddThh:mm:ss.zzz") +
QLatin1Char('Z') + QLatin1Char('\'');
Retrieval:
return QVariant(QDateTime::fromString(QString::fromLatin1(val),
Qt::ISODate).toLocalTime());
Handling NULL values:
---------------------
Also look qsqlresult.cpp -> QSqlResultPrivate::isVariantNull()
Also, look at the commit:
tests QDateTime null values (e8b3c3c5)
MySQL:
---
Summary:
Sent:
if (field.isNull())
r = QStringLiteral("NULL");
Retrieval:
return QVariant(f.type);
SQLite:
---
Summary:
Sent:
if (QSqlResultPrivate::isVariantNull(value))
res = sqlite3_bind_null(d->stmt, i + 1);
Retrieval:
case SQLITE_NULL:
values[i + idx] = QVariant(QMetaType::fromType<QString>());
PostgreSQL:
---
Summary:
Sent:
const auto nullStr = [](){ return QStringLiteral("NULL"); };
QString r;
if (field.isNull())
r = nullStr();
Retrieval:
if (PQgetisnull(d->result, currentRow, i))
return QVariant(type, nullptr);
Handling integer values:
------------------------
bool, smallint, bigint, ... are database types, _u suffix means unsigned.
Int, Short, LongLong, ... are QMetaType-s.
bool smallint smallint_u int int_u bigint bigint_u double decimal
MySQL Int Short UShort Int UInt LongLong ULongLong Double Double
PostgreSQL Bool Int - Int - - - Double Double
SQLite LongLong LongLong LongLong LongLong LongLong LongLong LongLong Double Double
Special rules:
-----
PostgreSQL:
---
- doesn't have unsigned integer numbers.
- returns Int for all types <Int
- bigint has special handling, it returns LongLong for positive numbers and ULongLong for negative,
it simply detects - character at the beginning
null values:
-----
In this case Int, Short, LongLong, ... are QMetaType-s that are passed to the QVariant ctor to
create a null QVariant eg. QVariant(QMetaType(QMetaType::Int)).
bool smallint smallint_u int int_u bigint bigint_u double decimal
MySQL Char Short UShort Int UInt LongLong ULongLong Double Double
PostgreSQL Bool Int - Int - LongLong LongLong Double Double
SQLite QString QString QString QString QString QString QString QString QString
Special rules:
-----
- QSQLITE driver - returns null QVariant(QMetaType(QMetaType::QString)) for all null values
- QMYSQL driver - returns null QVariant(QMetaType(QMetaType::Char)) for tinyint database types
- QPSQL driver - the logic for - and ULongLong is not applied for the null values, so it's
everytime LongLong
All places where is created some Model instance:
------------------------------------------------
The following list contains only a direct Model's constructor calls like "Derived model;",
these direct constructor calls have been replaced everywhere to Model::instance() related
methods to support the Default Attribute values.
All instance allocation are on the stack unless otherwise noted.
Model:
ctors
instance()
on()
query()
newTinyBuilder()
newFromBuilder()
newInstance()
HasAttributes:
getOriginal()
HasRelationships:
newRelatedInstance() - heap allocation
ModelProxies:
destroy()
BasePivot:
fromAttributes()
Orm::DatabaseConnection smart pointers data members graph:
----------------------------------------------------------
The order is also correct in the following graph, I'm visualizing it to better understand and try
to avoid shared pointers reference cycles.
- DatabaseConnection : public std::enable_shared_from_this<DatabaseConnection>
- shared_ptr<QueryGrammar> m_queryGrammar
- shared_ptr<SchemaGrammar> m_schemaGrammar
- unique_ptr<SchemaBuilder> m_schemaBuilder
- shared_ptr<DatabaseConnection> m_connection
- shared_ptr<SchemaGrammar> m_grammar
- unique_ptr<QueryProcessor> m_postProcessor
- QueryBuilder
- std::shared_ptr<DatabaseConnection> m_connection
- std::shared_ptr<QueryGrammar> m_grammar
- SchemaBuilder
- std::shared_ptr<DatabaseConnection> m_connection
- std::shared_ptr<QueryGrammar> m_grammar
- Relation
- std::shared_ptr<Related> m_related
- TinyBuilder
- std::shared_ptr<QueryBuilder> m_query
C preprocessor macros, defines, private, public, interface:
-----------------------------------------------------------
- qmake:
- TINYORM_MYSQL_PING and linking against MySQL C library:
- TINYORM_MYSQL_PING is public macro and should also be defined in the application that
consumes TinyORM library. If the TinyORM library was build with the mysql_ping config. then
it should also be defined in the consumer application. But, if the mysql_ping is not defined
Nothing Bad Happens because the TINYORM_MYSQL_PING is only used in the databaseconnection.hpp
to #ifdef the friend MySqlConnection declaration what means that it doesn't affect
a consumer application in any sense.
- linking against MySQL C library is not needed in the TinyOrm.pri and tom.pri
Todos to check in TinyOrm:
--------------------------
- QueryBuilder::insertGetId() allows insert with empty attributes, also Model::performInsert()
when incrementing == true, but all other insert methods don't, it's big inconsistency, unify it
Documentation TinyOrm Todos:
----------------------------
- how to refer NULL in docs, for now I leave it NULL
TODOs which look bad in code:
-----------------------------
- future add onDelete (and similar) callback feature
/*! Delete records from the database. */
void deleteModel()
{
// TODO future add onDelete (and similar) callback feature silverqx
// if (isset($this->onDelete)) {
// return call_user_func($this->onDelete, $this);
// }
return toBase().deleteRow();
}
- add c++20 compiler check, something like:
#ifdef __cplusplus
# if __cplusplus < 201103L && !defined(Q_CC_MSVC)
# error Qt requires a C++11 compiler and yours does not seem to be that.
# endif
#endif
- check this in cmake build:
#include(GenerateExportHeader)
#_test_compiler_hidden_visibility()
Todo categories:
----------------
Common:
- api different : different api than Laravel's Eloquent
- check : something to find out 🤔
- concept : add concept or constraint
- docs : document code or update markdown documentation
- desirable : feature which is extremely wanted
- dilemma : some sort of a fuckup
- duplicate : duplicate code
- feature : some feature to implement, perpend before feature described below
- future : task which has lower priority, because still much to do
- mistake : bad decision during prototyping 😭
- move : c++ move semantics
- mystery : don't know why that stuff is happening, find out what's up
- now : do it before commit
- next : next thing in the row to do after commit
- overflow : add check code, eg when size_t to int conversion
- perf : performance
- production : check before deploy to production
- regex : mark regex-es, try to avoid them
- reliability : make things more robust and reliable
- repeat : tasks that should I make from time to time
- security : self explaining
- study : don't know how something works, need to check up
- sync : synchronization in multi thread environment silverqx
- test : tasks in auto tests
- types : juggling with c++ types
Features related/to implement:
- aggregates : aggregate values like count, max, min, avg and sum
- castable : attributes casting
- default attributes : Default Attribute Values
- dilemma primarykey : different types for primary keys
- expression : DB::raw() support in the query builder
- events : event system
- ga : github actions
- guarded : related to the mass assignable feature
- json columns : JSON support
- logging : logging related
- migrations : database migrations related
- multidriver : task related to adding support for another drivers PostgreSQL, SQLite and SQL Server
- pivot : pivot table in the many-to-many relationship
- postgres : specific to PostgreSQL server
- qt6 : related to Qt6 upgrade or compatibility
- read/write connection : read/write connection
- relations : relations related 🤓
- savepoints : database savepoints
- scopes : query scopes
- seeders : seeders related
- table prefix : table prefix in the query grammar
- tom : tom migrations related
Upgrade the Qt to new version:
------------------------------
- run the QtCreator and do the PrintScreen of the Settings - Kits - Kits
- close the QtCreator
- run the Qt Maintenance Tool and add a new Qt version and also remove the old Qt version if want
- remove the old C:\Qt\x.y.z version folder (leftovers)
- open dotfiles in the vscode and file/replace all occurences of x.y.z and x_y_z to the new version; commit - upgraded to Qt vx.y.z
- add a new Qt version on the user PATH environment variable
- build the QMYSQL driver - qtbuild-qmysql-driver.ps1 6.5.3
- open the QtCreator
- fix all the Settings - Kits - Kits Qt versions
- fix the Settings - Debugger - Source Paths Mapping
- open the Projects mode (ctrl+5) for TinyORM and TinyOrmPlayground and disable/enable kit to update all build paths
- rebuild TinyORM and invoke unit tests
GitHub Action workflows:
- open TinyORM in the vscode and file/replace all occurrences of x.y.z and x_y_z to the new version
- commit - workflows upgraded to Qt vx.y.z
- TinyOrm-files:
- 7zip the qsqlmysql.dll, qsqlmysql.pdb, qsqlmysqld.dll, qsqlmysqld.pdb to the TinyOrm-files/qmysql_dlls-x.y.z.7z
- remove the old TinyOrm-files/qmysql_dlls-x.y.z.7z file
- generate the new hash - .\tools\Get-DownloadsHash.ps1 -Platform Linux, Windows
- commit - upgraded to Qt v6.5.3
- update the URL_QMYSQL_DLLS_MSVC_X64_x_y_z GitHub secret
MySQL Timezone tables:
- update the URL_MYSQL_TIMEZONE_TABLES GitHub secret
- download from https://dev.mysql.com/downloads/timezones.html
- pick the posix version without the leap second
Versions info:
--------------
This is laravel/framework version, not laravel/laravel version:
- I have cloned repository at - E:\htdocs\laravel-src-master
- based on Laravel v8.26.1
- upgrade to Laravel v8.41.0 ( 15.5.2021, but I didn't merged/reflected new changes to TinyORM )
- upgrade to Laravel v8.80.0 ( 19.1.2021, upgrade from v8.41.0 )
- compare URLs (remove after merge):
- https://github.com/laravel/framework/compare/v8.26.1...v8.41.0
- https://github.com/laravel/framework/compare/v8.41.0...v8.80.0
- upgraded to Laravel 9
- v9.44.0 to v9.48.0
- v9.48.0 to v9.50.2
- upgraded to Laravel 10
- v9.50.2 to v10.0.3
- v10.9.0 to v10.14.1
- close all expanded sections in github compare diff:
document.querySelectorAll('.btn-octicon.js-details-target[aria-expanded=true]').forEach((value) => value.click())
Maintenance:
------------
- from time to time try:
- compile without PCH
- compile with Qt6, I have still problem with clazy
GitHub Actions:
---------------
- Mass deletion of GitHub workflow runs
- authenticate using the gh if auth expired
- gh auth login -h github.com
- dwr silverqx/TinyORM
- <tab> or <shift-tab> to un/select
- <enter> to delete selected runs
- ~/.dotfiles/bin/dwr
- upgrade to the latest dwr
- https://qmacro.org/blog/posts/2021/03/26/mass-deletion-of-github-actions-workflow-runs/
- https://raw.githubusercontent.com/qmacro/dotfiles/230c6df494f239e9d1762794943847816e1b7c32/scripts/dwr
- Runner images: https://github.com/actions/runner-images/tree/main/images
- My common folders structure:
- checkout to the current folder
- the github.workspace is the current workspace folder
- if using more repo checkouts then checkout using side-by-side method:
https://github.com/actions/checkout?tab=readme-ov-file#Checkout-multiple-repos-side-by-side
- the runner.workspace points to the parent folder
- I'm using it for build folders or things that are generated during workflows
- the env.TinyRunnerWorkPath points two folders up ../..
- it's directly in the _work/ or work/ folder
- I'm using it for more common things like data from GH extensions or databases data folders
- all actions with versions currently used by TinyORM:
actions/cache@v3
actions/checkout@v4
actions/github-script@v6
ilammy/msvc-dev-cmd@v1
jurplel/install-qt-action@v3
KyleMayes/install-llvm-action@v1
lukka/get-cmake@latest
msys2/setup-msys2@v2
mxschmitt/action-tmate@v3
seanmiddleditch/gha-setup-ninja@master
Not using anymore:
Chocobo1/setup-ccache-action@v1
- periodical upgrades:
- analyzers-qtX.yml
- add-apt-repository Clang 14
- clang-cl-qt6.yml
- Install LLVM and Clang 15.0.6 (KyleMayes/install-llvm-action@v1)
- Qt 6.3.2 install base components (jurplel/install-qt-action@v3)
- QMYSQL install driver dlls (Qt 6.3.2) - needed to rebuild the QSQL QMYSQL driver
- linux-qtX.yml
- add-apt-repository Clang 15
- msvc-2022.yml
- Qt 6.3.2 install base components (jurplel/install-qt-action@v3)
- QMYSQL install driver dlls (Qt 6.3.2) - needed to rebuild the QSQL QMYSQL driver
- how to update the clazy-standalone (analyzers.yml):
- update the QtCreator to latest version, copy libexec/qtcreator/clang/ to some empty folder
- leave only the clazy-standalone executable in the bin/ and lib/ folder, and compress it:
tar cjvf ../clazy-standalone.tar.bz2 .
- then update this file in the https://github.com/silverqx/files project
- no need to udpate the URL in the GitHub URL_CLAZY_STANDALONE_LINUX_X64 secret,
it's still the same:
https://github.com/silverqx/files/raw/main/clazy-standalone.tar.bz2
- prepend to the env. variables:
- name: Print env
run: |
echo "LD_LIBRARY_PATH: $LD_LIBRARY_PATH"
echo "LIBRARY_PATH: $LIBRARY_PATH"
echo "PATH: $PATH"
- name: TinyORM prepend to the system $PATH
working-directory: ..
run: |
echo "LD_LIBRARY_PATH=$PWD${LD_LIBRARY_PATH:+:}$LD_LIBRARY_PATH" >> $GITHUB_ENV
echo "LIBRARY_PATH=$GITHUB_WORKSPACE" >> $GITHUB_ENV
pwd >> $GITHUB_PATH
- name: Print env
working-directory: ..
run: |
pwd
echo "LD_LIBRARY_PATH: $LD_LIBRARY_PATH"
echo "LIBRARY_PATH: $LIBRARY_PATH"
echo "PATH: $PATH"
- run: exit 1
- vcpkg debug:
- name: TinyORM cmake configure (msvc-cmake-debug)
run: >-
echo $env:VCPKG_ROOT
echo $env:VCPKG_DEFAULT_TRIPLET
echo $env:VCPKG_MAX_CONCURRENCY
cmake
-S .
-B ../TinyORM-builds-cmake/build-msvc-cmake-debug
...
- ssh into runner:
- shortcuts:
ctrl-b ? - help (show commands)
ctrl-b c - new window
ctrl-b l - last window (switch to)
ctrl-b n - next window (switch to)
ctrl-b p - previous window (switch to)
ctrl-b 0-9 - switch to window
ctrl-b [ - enable scroll mode (eg. Up Arrow or PgDn), press q to quit the scroll mode
ctrl-b " - split pane (horizontally)
ctrl-b % - split pane (vertically)
ctrl-b z - resize pane (toggle maximize)
ctrl-b M-up - resize pane (up, down, left, right; M- is L-ALT)
ctrl-b up - select pane (up, down, left, right)
- run if failure or success:
- name: Setup tmate session
if: ${{ always() }}
uses: mxschmitt/action-tmate@v3
with:
limit-access-to-actor: true
- run: exit 1
- !!! with if: ${{ failure() }} condition !!!
--------------------
- name: Setup tmate session
if: ${{ failure() }}
uses: mxschmitt/action-tmate@v3
with:
limit-access-to-actor: true
- run: exit 1
- all TinyORM env. variables
env:
DB_MYSQL_CHARSET: ${{ secrets.DB_MYSQL_CHARSET }}
DB_MYSQL_COLLATION: ${{ secrets.DB_MYSQL_COLLATION }}
DB_MYSQL_DATABASE: ${{ secrets.DB_MYSQL_DATABASE }}
DB_MYSQL_HOST: ${{ secrets.DB_MYSQL_HOST_SSL }}
DB_MYSQL_PASSWORD: ${{ secrets.DB_MYSQL_PASSWORD }}
DB_MYSQL_SSL_CA: ${{ runner.workspace }}/../mysql/data/ca.pem
DB_MYSQL_SSL_CERT: ${{ runner.workspace }}/../mysql/data/client-cert.pem
DB_MYSQL_SSL_KEY: ${{ runner.workspace }}/../mysql/data/client-key.pem
DB_MYSQL_USERNAME: ${{ secrets.DB_MYSQL_USERNAME }}
DB_PGSQL_CHARSET: utf8
DB_PGSQL_DATABASE: ${{ secrets.DB_PGSQL_DATABASE }}
DB_PGSQL_HOST: ${{ secrets.DB_PGSQL_HOST }}
DB_PGSQL_PASSWORD: ${{ secrets.DB_PGSQL_PASSWORD }}
DB_PGSQL_USERNAME: ${{ secrets.DB_PGSQL_USERNAME }}
DB_SQLITE_DATABASE: ${{ runner.temp }}/${{ secrets.DB_SQLITE_DATABASE }}
TOM_TESTDATA_ENV: testing
- print all contexts:
Linux:
- name: Dump pwd
run: pwd
- name: Dump OS env (unsorted)
run: env
- name: Dump OS env (sorted)
run: |
env | sort --ignore-case
Windows:
- name: Dump pwd
run: Get-Location
- name: Dump OS env
run: |
Get-ChildItem env:
Common:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJSON(github) }}
run: echo "$GITHUB_CONTEXT"
- name: Dump job context
env:
JOB_CONTEXT: ${{ toJSON(job) }}
run: echo "$JOB_CONTEXT"
- name: Dump steps context
env:
STEPS_CONTEXT: ${{ toJSON(steps) }}
run: echo "$STEPS_CONTEXT"
- name: Dump runner context
env:
RUNNER_CONTEXT: ${{ toJSON(runner) }}
run: echo "$RUNNER_CONTEXT"
- name: Dump strategy context
env:
STRATEGY_CONTEXT: ${{ toJSON(strategy) }}
run: echo "$STRATEGY_CONTEXT"
- name: Dump matrix context
env:
MATRIX_CONTEXT: ${{ toJSON(matrix) }}
run: echo "$MATRIX_CONTEXT"
- name: Dump env context
env:
ENV_CONTEXT: ${{ toJSON(env) }}
run: echo "$ENV_CONTEXT"
- run: exit 1
- query caches using the gh api
# Print all caches usage summary
gh api -H "Accept: application/vnd.github+json" /repos/silverqx/TinyORM/actions/cache/usage
# List caches
gh api -H "Accept: application/vnd.github+json" /repos/silverqx/TinyORM/actions/caches
# Delete all caches for TinyORM repo
gh api --method DELETE -H "Accept: application/vnd.github+json" /repos/silverqx/TinyORM/actions/caches
# Delete a cache by ID for TinyORM repo
gh api --method DELETE -H "Accept: application/vnd.github+json" /repos/silverqx/TinyORM/actions/caches/CACHE_ID
- ternary operator alternative:
${{ matrix.compiler.key == 'gcc' && 1 || 2 }}
- use quotes for strings:
${{ matrix.compiler.key == 'gcc' && '400M' || '250M' }}
- ccache initial build sizes:
First value is with a default compression and second uncompressed (ccache --show-compression)
with PCH:
linux-gcc-qt64 - 780MB (5.0GB)
linux-clang-qt63 - 440MB (1.3GB)
linux-gcc-qt5 - 770MB (4.5GB)
w/o PCH:
clang-cl-qt64 - 135MB (880MB)
msvc2022-qt64 - 190MB (1.5GB)
msvc2019-qt5 - 175MB (1.5GB)
linux-gcc-qt64 - 165MB (700MB)
linux-clang-qt63 - 100MB (680MB)
linux-gcc-qt5 - 115MB (665MB)
linux-clang-qt5 - 90MB (640MB)
MSYS2-clang-qt5 - 140MB (900MB)
MSYS2-clang-qt6 - 140MB (895MB)
MSYS2-gcc-qt5 - 180MB (940MB)
MSYS2-gcc-qt6 - 180MB (940MB)
- ccache debugging (Linux):
- name: Ccache enable debugging
id: ccache-debug
run: |
ccacheDebugDir0='${{ runner.temp }}/ccache_debug/run_0'
ccacheDebugDir1='${{ runner.temp }}/ccache_debug/run_1'
mkdir -p "$ccacheDebugDir0"
mkdir -p "$ccacheDebugDir1"
echo "CCACHE_DEBUG=1" >> $GITHUB_ENV
echo "TinyCcacheDebugDir0=$ccacheDebugDir0" >> $GITHUB_OUTPUT
echo "TinyCcacheDebugDir1=$ccacheDebugDir1" >> $GITHUB_OUTPUT
... do build here
- name: TinyORM cmake build ✨ (${{ matrix.compiler.key }}-cmake-debug) (ccache debug 0)
run: >-
export CCACHE_DEBUGDIR='${{ steps.ccache-debug.outputs.TinyCcacheDebugDir0 }}'
cmake --build ../TinyORM-builds-cmake/build-${{ matrix.compiler.key }}-cmake-debug
--target all --parallel 2
- name: Ccache statistics
run: |
ccache --show-stats -vv
ccache --zero-stats
- name: Ccache upload debugging logs (ccache debug 0)
uses: actions/upload-artifact@v3
with:
name: ccache_debug_run_0
path: ${{ steps.ccache-debug.outputs.TinyCcacheDebugDir0 }}
if-no-files-found: error
- name: TinyORM cmake build ✨ (${{ matrix.compiler.key }}-cmake-debug) (ccache debug 1)
run: >-
cmake --build ../TinyORM-builds-cmake/build-${{ matrix.compiler.key }}-cmake-debug
--target clean
export CCACHE_DEBUGDIR='${{ steps.ccache-debug.outputs.TinyCcacheDebugDir1 }}'
cmake --build ../TinyORM-builds-cmake/build-${{ matrix.compiler.key }}-cmake-debug
--target all --parallel 2
- name: Ccache statistics (ccache debug 1)
run: |
ccache --show-stats -vv
ccache --zero-stats
- name: Ccache upload debugging logs (ccache debug 1)
uses: actions/upload-artifact@v3
with:
name: ccache_debug_run_1
path: ${{ steps.ccache-debug.outputs.TinyCcacheDebugDir1 }}
if-no-files-found: error
RegEx-s:
-------
- const data members:
(?<![\(\)])(const) +.* +\bm_.*\b( +.*)?;$
(?<![\(\)])(const) +.* +\bm_.*\b +=
(?<![\(\)])(const) +.* +\bm_.*\b +{
- const data member references:
(?<![\(\)])(const) +.* +&\bm_.*\b( +.*)?;$
- all exceptions:
throw (.*::)?\w+(E|_error)
Powershell commands:
--------------------
- export todos to csv:
Get-ChildItem -Path *.cpp,*.hpp -Recurse | sls -Pattern ' (TODO|NOTE|FIXME|BUG|WARNING|CUR|FEATURE|TEST|FUTURE) ' -CaseSensitive | % { $_.Line = $_.Line.Trim().TrimStart('// '); return $_; } | select Line,LineNumber,Path | Export-Csv todos.csv -Delimiter ';' -NoTypeInformation
- search in todos:
Get-ChildItem -Path *.cpp,*.hpp -Recurse | sls -Pattern ' (TODO|NOTE|FIXME|BUG|WARNING|CUR|FEATURE|TEST|FUTURE) ' -CaseSensitive | % { $_.Line = $_.Line.Trim().TrimStart('// '); return $_; } | where Line -Match 'pch' | select Line,LineNumber,Path | ft -AutoSize
- filter out executed queries:
Get-Content .\tmp.sql | sls -Pattern '^(Executed prepared query)' | Set-Content executed_queries.sql
- TinyOrmPlayground - run InvokeXTimes.ps1 on Linux:
stp && sq5
export LD_LIBRARY_PATH=../../../TinyORM/TinyORM-builds-qmake/build-TinyORM-Desktop_Qt_5_15_2_GCC_64bit_ccache-Debug/src
pwsh -NoLogo -NoProfile -File InvokeXTimes.ps1 2 ../../../TinyOrmPlayground/TinyOrmPlayground-builds-qmake/build-TinyOrmPlayground-Desktop_Qt_5_15_2_GCC_64bit_ccache-Debug/TinyOrmPlayground
- TinyORM - run InvokeXTimes.ps1 on Linux:
stp && sq5 && cdtq && cd build-TinyORM-Desktop_Qt_5_15_2_GCC_64bit_ccache-Debug
export LD_LIBRARY_PATH=./src:./tests/TinyUtils${LD_LIBRARY_PATH:+:}$LD_LIBRARY_PATH
export PATH=$HOME/Code/c/TinyORM/tools:$PATH
InvokeXTimes.ps1
pwsh -NoLogo -NoProfile -File InvokeXTimes.ps1 100
Powershell Clang analyzers:
---------------------------
qa-lint-tinyorm-qt5.ps1 is tailor-made for TinyORM project.
qa-clang-tidy.ps1, qa-clazy-standalone.ps1, qa-clazy-standalone-st.ps1 are more general, they can be used with any project, "-st" script calls raw clazy-standalone.exe.
run-clang-tidy.ps1, run-clazy-standalone.ps1 are raw Powershell wrappers around python run-clang-tidy/run-clazy-standalone.py python scripts.
Invoke the run-clang-tidy.ps1 manually:
--
run-clang-tidy.ps1 -use-color -extra-arg-before='-Qunused-arguments' -j=10 -p='E:\c\qMedia\TinyORM\TinyORM-builds-cmake\build-lint-qt6_Debug' -checks='-*,readability-convert-member-functions-to-static' '(?:src|tests)[\\\/]+.+?[\\\/]+(?!mocs_)[\w_\-\+]+\.cpp$'
- test *.hpp count
gci -Force -Recurse -Include *.hpp `
-Path E:\c\qMedia\TinyORM\TinyORM\include,E:\c\qMedia\TinyORM\TinyORM\examples,e:\c\qMedia\TinyORM\TinyORM\tom,E:\c\qMedia\TinyORM\TinyORM\tests | `
select -ExpandProperty FullName > b.txt
- test *.cpp count
cat .\compile_commands.json | sls '"file":' > a.txt
And use regex-es from analyzers-qt5/6.yml
pwsh autocomeplete:
-------------------
For a fast lookup for debugging.
- tom command:
- argument:
tom complete --word="mi" --commandline="tom mi" --position=6
- option:
tom complete --word="--" --commandline="tom migrate --" --position=14
tom complete --word="--p" --commandline="tom migrate --p" --position=15
cmake build commands:
---------------------
vcvars64.ps1
cd E:\c\qMedia\TinyORM\TinyORM-builds-cmake\build-cmake\
cmake.exe -S E:/c/qMedia/TinyORM/TinyORM -B E:/c/qMedia/TinyORM/TinyORM-builds-cmake/build-cmake -GNinja `
-DCMAKE_BUILD_TYPE:STRING=Debug `
-DCMAKE_TOOLCHAIN_FILE:PATH=E:/c_libs/vcpkg/scripts/buildsystems/vcpkg.cmake
cmake --build . --target all
- generate Graphviz dependency image:
cmake.exe -S E:/c/qMedia/TinyORM/TinyORM -B E:/c/qMedia/TinyORM/TinyORM-builds-cmake/build-cmake -GNinja `
-DCMAKE_BUILD_TYPE:STRING=Debug `
-DCMAKE_TOOLCHAIN_FILE:PATH=E:/c_libs/vcpkg/scripts/buildsystems/vcpkg.cmake `
--graphviz=E:/c/qMedia/TinyORM/TinyORM-builds-cmake/build-cmake/graph/graph.dot; `
`
dot -Tpng -o .\graph\graph.png .\graph\graph.dot; `
.\graph\graph.png
- running ctest (use CTEST_OUTPUT_ON_FAILURE=1 env. or --output-on-failure, to see what failed)
E:\c\qMedia\TinyORM\TinyORM\tests\auto\utils\testdata\dotenv.ps1
$env:Path = "E:\c\qMedia\TinyORM\TinyORM-builds-cmake\build-TinyORM-Desktop_Qt_5_15_2_MSVC2019_64bit-Debug-cmake;E:\c\qMedia\TinyORM\TinyORM-builds-cmake\build-TinyORM-Desktop_Qt_5_15_2_MSVC2019_64bit-Debug-cmake\tests\auto\utils;" + $env:Path
ctest
ctest --progress
- some debug output:
cmake --trace-expand --trace-source=tests/auto/unit/orm/query/mysql_querybuilder/CMakeLists.txt -LA ..
cmake -LA .
- full build command, not needed, I leave it here as a shortcut:
cmake.exe -S E:/c/qMedia/TinyORM/TinyORM -B E:/c/qMedia/TinyORM/TinyORM-builds-cmake/build-cmake -GNinja `
"-DCMAKE_BUILD_TYPE:STRING=Debug" `
"-DCMAKE_PROJECT_INCLUDE_BEFORE:PATH=E:/Qt/Tools/QtCreator/share/qtcreator/package-manager/auto-setup.cmake" `
"-DQT_QMAKE_EXECUTABLE:STRING=E:/Qt/5.15.2/msvc2019_64/bin/qmake.exe" `
"-DCMAKE_PREFIX_PATH:STRING=E:/Qt/5.15.2/msvc2019_64" `
"-DCMAKE_C_COMPILER:STRING=C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.29.30037/bin/HostX64/x64/cl.exe" `
"-DCMAKE_CXX_COMPILER:STRING=C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.29.30037/bin/HostX64/x64/cl.exe" ` "-DCMAKE_TOOLCHAIN_FILE:PATH=E:/c_libs/vcpkg/scripts/buildsystems/vcpkg.cmake"
- put TinyOrm and TinyUtils libraries on the system path:
$env:Path = "E:\c\qMedia\TinyORM\TinyORM-builds-cmake\build-cmake;E:\c\qMedia\TinyORM\TinyORM-builds-cmake\build-cmake\tests\auto\utils;" + $env:Path
TinyORM docs github pages:
--------------------------
tags: docusaurus
---
- npm run clear
Clear a Docusaurus site's generated assets, caches, build artifacts.
We recommend running this command before reporting bugs, after upgrading versions, or anytime you have issues with your Docusaurus site.
- deploy:
.\dotenv.ps1
npm run deploy; echo "Algolia Rebuild"; sleep 30; .\algolia_rebuild.ps1
- local development:
npm start
npm start -- --no-open
- update Algolia index by DocSearch:
.\dotenv.ps1
.\algolia_rebuild.ps1
- upgrade Docusaurus npm:
npm install @docusaurus/core@latest @docusaurus/plugin-client-redirects@latest @docusaurus/plugin-ideal-image@latest @docusaurus/preset-classic@latest
- upgrade Docusaurus yarn:
yarn upgrade @docusaurus/core@latest @docusaurus/preset-classic@latest
CMake Guidelines:
-----------------
All are snake-case unless otherwise specified.
- variable names:
- global variables: tiny_
- local variables: preferred camelCase or snake-case
- function parameters: lowercase and optional tiny_ prefix
- cmake_parse_arguments: TINY_
- option variables: upper case without prefix
- cached variables: TINY_
- function names has the tiny_ prefix
- compile definitions prefix by project eg. TINYORM_, TINYUTILS_
- commands:
- foreach: use the foreach(depend ${depends}) syntax everywhere as default syntax and use
the IN LISTS XYZ syntax only if needed
CMake confusions:
-----------------
- CMAKE_SOURCE_DIR, PROJECT_SOURCE_DIR, CMAKE_BINARY_DIR, CMAKE_CURRENT_SOURCE_DIR, ...
Look at commit:
cmake added FetchContent support (23d0d904)
- foreach ITEMS vs LISTS
- ITEMS:
- used like foreach(depend ${depends})
- it loops over the each item in the given list
- can't wrap in quotes as it will be parsed as a one-value
- LISTS:
- used like foreach(depend IN LISTS depends anotherList)
- it loops over the each item in the given LISTS so you can pass MORE list variables
- the forms ITEMS ${A} and LISTS A are equivalent
- I'm using the first syntax everywhere, eg. vcpkg uses the second syntax everywhere
- Variables
- every new scope (block, function, add_subdirectory(), ...) creates a copy of all variables
from the current scope that means all variable are immutable so you can't change/overwrite
variables in parent scopes using simple set(xyz ...) call
- add_custom_command()
- COMMAND_EXPAND_LISTS
- if something output eg. a;b;c eg. genex then it will be converted to string like a b c
- VERBATIM
- you don't need to care about escaping so what you type in command arguments will be exactly
what you get as output or on command line and it will be correctly escaped
- only one thing that I discovered that need to be escaped is backslash like \\
- w/o VERBATIM you need to escape eg. \" to be passed on command-line
- also, everything is already quoted correctly with or w/o verbatim that is a little weird,
eg. if some list item contains space and is used with copy command then it will be quotes,
also the main command is quoted everytime
- hard to tell how this works but use VERBATIM and don't care about escaping 🤔 because it's
also cross-platform or cross-shell so it knows which OS or shell is used and will escape
correctly
TinyORM CMake build:
--------------------
Here I describe how the TinyORM's CMake build works because I was confused last time I returned
to it and had to fix some problems.
- at the beginning are prepared and initialized CMake options and compiler requirements so
nothing special
- then are set header/sources files, PCH, prepared TinyOrm target definitions, properties
- Windows RC and manifest
- resolved and linked dependencies
the tiny_find_package() macro is used for this and it has additional functionality and
it is to collect the dependency names for the find_dependency() that will be used during
the deployment ❗
- nothing special until Deployment
How the Deployment works:
- first the find_dependency() calls are generated using the tiny_generate_find_dependency_calls()
- then are created Package Config and Package Config Version files for the Build and Install Tree
- look at the TinyDeployment.cmake tiny_install_tinyorm() and tiny_export_build_tree() functions
they are greatly commented so it's crystal clear how they works
- generating the Package Config for the Install tree also takes into account the vcpkg package
manager
- TinyOrmConfig.cmake.in is for the Install tree
- TinyOrmBuildTreeConfig.cmake.in is for the Build tree
How the Package Config file works:
- they are used when linking against the TinyORM library so generating these config files and
linking against them are two different things so a different code is executed for these two
things
- first info messages are are printed:
- log messages output can be controlled by the --log-level=<level> and --log-context CMake
parameters during configure eg.:
- --log-level=DEBUG --log-context (full ouput)
- --log-level=VERBOSE (only verbose without debug output)
- I have invested a lot of effort to these info messages
- whether linking against the single, multi, vcpkg builds
- against which TinyORM package is linking eg.:
Found package TinyOrm 0.36.5.0 Debug (requested 0.36.5) at O:/Code/c/qMedia/TinyORM/TinyORM-builds-cmake/build-TinyORM-Desktop_Qt_6_5_3_MSVC2022_64bit-Debug/TinyOrmConfig.cmake
- whether Matching build type for Build tree was enabled/disabled eg.:
Matching build type for the TinyOrm 0.36.5.0 package build tree was enabled
- Matching build type is controlled by the MATCH_EQUAL_EXPORTED_BUILDTREE CMake config. option
during the TinyORM library configure
- if the MYSQL_PING was enabled then it also sets the CMAKE_MODULE_PATH to our Modules folder
to find the FindMySQL.cmake package module
- then are called find_dependency() from CMakeFindDependencyMacro modules to find Qt,
range-v3, and tabulate
- setting up meaningful values for MAP_IMPORTED_CONFIG_<CONFIG> to avoid link a release type
builds against a debug build
- as the last thing is called the check_required_components() from CMakePackageConfigHelpers
module. https://cmake.org/cmake/help/latest/module/CMakePackageConfigHelpers.html
Differences between Package Config files for Install and Build tree:
- they are pactically identical
- this is how the Package Config for the Install tree differs from the Build tree:
- it has a little different logic for the Information message about build type used because
it must the vcpkg package manager into account, it calls the tiny_get_build_types() and
because of this it needs to include the TinyPackageConfigHelpers.cmake module
- doesn't print the Info message about matching build type (it's Build tree specific)
- sets the set() and set_and_check() for the cmake --target install;
they are the Install destination directories for the Install Tree
- and that's all
How the Package Config Version file works:
- they are complex for sure, I will not describe how they work because I don't exactly remember
- ok, I added comments to the tiny_build_type_requirements_install_tree() and
tiny_build_type_requirements_build_tree() in the TinyPackageConfigHelpers.cmake so it should be
clear how they works now
- they check if Package configs are suitable, eg.:
- single-config is not suitable for multi-config
- or no CMake targets installed so unsuitable
- or linking debug against release for MSVC is unsuitable
- my logic also adds nice info messages if linking against unsuitable CMake package
- also a lot of debug messages that can be enabled using the --log-level=<level> and
--log-context CMake parameters during configure eg.:
- --log-level=DEBUG --log-context (full ouput)
- --log-level=VERBOSE (only verbose without debug output)
- they check if the versions are correct, the COMPATIBILITY SameMajorVersion is the main
thing here! (defined in the TinyDeployment.cmake during the write_basic_package_version_file()
Check here what the SameMajorVersion means:
https://cmake.org/cmake/help/latest/module/CMakePackageConfigHelpers.html
Differences between Package Config files for Install and Build tree:
- all differences are in the tiny_build_type_requirements_install_tree() and
tiny_build_type_requirements_build_tree() in the TinyPackageConfigHelpers.cmake
- for Install tree:
- no CMake targets installed so unsuitable
- takes into account also the vcpkg (is multi-config so don't do the checks)
- for Build tree:
- if matching equal build tree was enabled and builds types don't match then tag as unsuitable
It's crazy 🙃😲🙋‍
TinyORM vcpkg testing (CMake build):
------------------------------------
Don't install it to the main vcpkg installation because it can happend that whole qtbase will be
installed and it pulls in many boost packages or mysql client library pulls in much more in.
I have created the svcpkg_tinyorm_port_qt5/qt6_testing.ps1 scripts so execute it and
use the installation at:
O:\Code\c_libs\vcpkg-tinyorm-port-qt5\
O:\Code\c_libs\vcpkg-tinyorm-port-qt6\
To install only the neccesary minimum use:
vcpkg install tinyorm[core] qtbase[core,sql] --dry-run
vcpkg install tinyorm[core] qtbase[core,sql-sqlite] --dry-run
vcpkg install tinyorm[core]
vcpkg install tinyorm[core] --editable
With --editable option you can edit TinyORM source code at:
buildtrees\tinyorm\src\b067300643-08e2ea7916\
The o:\Code\c_libs\vcpkg-tinyorm-port\buildtrees\tinyorm\src\b067300643-08e2ea7916.clean\ folder
is for the vcpkg install tinyorm[core] without the --editable option.
You can call the vcpkg install tinyorm[core] --editable right away, w/o calling
the vcpkg install tinyorm[core] first.
- To debug eg. vcpkg-cmake scripts set around a command to debug:
set(PORT_DEBUG ON)
set(PORT_DEBUG OFF)
Drop all PostgreSQL tables:
---------------------------
DROP TABLE IF EXISTS "tag_properties" CASCADE;
DROP TABLE IF EXISTS "tag_torrent" CASCADE;
DROP TABLE IF EXISTS "torrent_tags" CASCADE;
DROP TABLE IF EXISTS "roles" CASCADE;
DROP TABLE IF EXISTS "role_user" CASCADE;
DROP TABLE IF EXISTS "users" CASCADE;
DROP TABLE IF EXISTS "user_phones" CASCADE;
DROP TABLE IF EXISTS "settings" CASCADE;
DROP TABLE IF EXISTS "torrents" CASCADE;
DROP TABLE IF EXISTS "torrent_peers" CASCADE;
DROP TABLE IF EXISTS "torrent_previewable_files" CASCADE;
DROP TABLE IF EXISTS "torrent_previewable_file_properties" CASCADE;
DROP TABLE IF EXISTS "file_property_properties" CASCADE;
DROP TABLE IF EXISTS "migrations" CASCADE;
DROP TABLE IF EXISTS "migrations_example" CASCADE;
DROP TABLE IF EXISTS "migrations_unit_testing" CASCADE;
UBSan:
------
Has to be invoked on Linux clang and errors are detected at runtime.
This enable the undefined group:
QMAKE_CXXFLAGS += -O1 -fsanitize=undefined
QMAKE_LFLAGS += -fsanitize=undefined
This with the above one! enables all the possible sanitizers:
QMAKE_CXXFLAGS += -O1 -fsanitize=nullability -fsanitize=float-divide-by-zero -fsanitize=unsigned-integer-overflow -fsanitize=implicit-conversion -fsanitize=local-bounds
QMAKE_LFLAGS += -fsanitize=nullability -fsanitize=float-divide-by-zero -fsanitize=unsigned-integer-overflow -fsanitize=implicit-conversion -fsanitize=local-bounds
Set in the QtCreator env. before execution, it suspends some errors detected in the Qt library:
UBSAN_OPTIONS=suppressions=/home/silverqx/Code/c/TinyORM/TinyORM/tools/configs/UBSan.supp:print_stacktrace=0
Valgrind, callgrind, and KCachegrind:
-------------------------------------
This only works on the Linux, I have setup Gentoo for this.
- QtCreator debug view (ctrl+4) and open/switch to the Callgrind view (in the bottom view)
- simply start and open the resut in the KCachegrind
- described here - https://doc.qt.io/qtcreator/creator-cache-profiler.html
I have enabled debug symbols and source files for the glibc, so I can see everything nicely:
- described here - https://wiki.gentoo.org/wiki/Debugging
KCachegrind legend:
- Self Cost - function itself ("Self" is sometimes also referred to as "Exclusive" costs)
- Inclusive Cost - the cost including all called functions (Incl. in UI)
- described in the F1 KCachegrind help - Chapter 5. Questions and Answers
- sidebar Flat Profile next to the Search is the grouping dropdown by ELF is useful 🔥
- select function and in the bottom view select the "Call Graph" or "Callees" tab, they are most
useful 🔥
- you can double-click through the "Callees" tab down through most expensive functions and
track down what is causing high cost 🔥
- I have selected % Relative, Cycle Detection, Shorten Templates, and Cycle Estimation
in the top toolbar
- Instruction Fetch vs Cycle Estimation
- Instruction Fetch looks like the Instruction Read Access and it means how much assembler
instructions a selected function coasted
- I don't exactly understand it
- described here https://stackoverflow.com/questions/38311201/kcachegrind-cycle-estimation
- also these two video were useful:
- https://www.youtube.com/watch?v=h-0HpCblt3A&ab_channel=DerickRethans
- https://www.youtube.com/watch?v=iH-hDOuQfcY&ab_channel=DerickRethans
inline constants:
-----------------
- for static archive build only inline constants are allowed, this is the default for all scenarios, any ify
- cmake
- normal behavior is that user can switch between inline and extern constants using the INLINE_CONSTANTS cmake option
- default is to provide/show this INLINE_CONSTANTS cmake option with the default value OFF, so extern constants are enabled by default
- MinGW clang shared build crashes with inline constants
- so don't show INLINE_CONSTANTS cmake option and value is NOT DEFINED in this case so the default will be used and it's extern constants
- related issue: https://github.com/llvm/llvm-project/issues/55938
- MinGW clang static build is not supported, problem with inline constants :/
- this is different than qmake build, it compiles (qmake has problem with duplicit symbols) but it crashes
- so throw cmake message(FATAL_ERROR) with a nice message in TinyHelpers tiny_check_unsupported_build()
- related issue: https://github.com/llvm/llvm-project/issues/55938
- clang-cl shared build crashes with extern constants, so force to inline constants 😕🤔
- don't show INLINE_CONSTANTS cmake option and provide the default value ON using feature_option_dependent(INLINE_CONSTANTS) depends option, so inline constants is the default
- so inline constants are only one option with clang-cl
- qmake
- normal behavior is that user can switch between inline and extern constants using the inline_constants/extern_constants qmake CONFIG option
- default is when no qmake CONFIG option is set and in this case extern constants (extern_constants CONFIG option) is set/enabled
- MinGW clang shared build crashes with inline constants
- when user set inline_constants then qmake error() is thrown with nice message
- so the default is extern constants, if no CONFIG option was set or user can set the extern_constants qmake CONFIG option manually
- MinGW clang static build is not supported, contains a problem with duplicit symbols, this build type is disabled
- qmake error() is thrown with a nice message
- cmake compiles ok but crashes in this scenario
- clang-cl shared build crashes with extern constants, so force to inline constants 😕🤔
- when is shared build then inline_constants qmake CONFIG option is set (this is the default)
- when user set extern_constants then qmake error() is thrown with nice message
- so inline constants are only one option with clang-cl
The conclusion is that the funckin string constants 💥, it was bearable until I have added the clang-cl MSVC support.
eagerLoadRelations() and Model::load() history:
-----------------------------------------------
At the beginning it was like this:
// FUTURE make possible to pass single model to eagerLoadRelations() and whole relation flow, I indicative counted how many methods would have to rewrite and it is around 12 methods silverqx
/* I have to make a copy here of this, because of eagerLoadRelations(),
the solution would be to add a whole new chain for eager load relations,
which will be able to work only on one Model &, but it is around
10-15 methods refactoring, or add a variant which can process
QVector<std::reference_wrapper<Derived>>.
For now, I have made a copy here and save it into the QVector and after
that move relations from this copy to the real instance. */
ModelsCollection<Model> models {&model};
/* Replace only relations which was passed to this method, leave other
relations untouched.
They do not need to be removed before 'eagerLoadRelations(models)'
call, because only the relations passed to the 'with' at the beginning
will be loaded anyway. */
this->replaceRelations(models.first()->getRelations(), relations);
builder->with(relations).eagerLoadRelations(models);
After this commit:
Added eagerLoadRelations(ModelsCollection<Model *> &) that accepts
a collection of model pointers. It allows to implement
the ModelsCollection::load() and also avoids making a copy of Model
in Model::load().
It's like this:
ModelsCollection<Model *> models {&model};
builder->with(relations).eagerLoadRelations(models);
Conclusion:
It helped to avoid one Model copy 🙃 but the reason why it was refactored like this was
the ModelsCollection::load() method, this method needs to operate also
on the ModelsCollection<Model *> so this eagerLoadRelations<Model *>() overload was needed
to make it real.
constructor copy/move snippet:
------------------------------
Add code below to class you want to optimize and set breakpoints inside and you will see what cause what 😎:
Torrent(const Torrent &torrent)
: Model(torrent)
{
qDebug() << "Torrent copy ctor";
}
Torrent(Torrent &&torrent) noexcept
: Model(std::move(torrent))
{
qDebug() << "Torrent move ctor";
}
Torrent &operator=(const Torrent &torrent)
{
Model::operator=(torrent);
qDebug() << "Torrent copy assign";
return *this;
}
Torrent &operator=(Torrent &&torrent) noexcept
{
Model::operator=(std::move(torrent));
qDebug() << "Torrent move assign";
return *this;
}
Model QDebug operator<<():
--------------------------
/* Non-member functions */
template<Orm::Tiny::ModelConcept Model>
QDebug operator<<(QDebug debug, const Model &model)
{
const QDebugStateSaver saver(debug);
debug.nospace() << Orm::Utils::Type::classPureBasename<Model>().toUtf8().constData()
<< model.getKey().template value<Model::KeyType>() << ", "
<< model.getAttribute("name").template value<QString>()
<< ')';
return debug;
}
conversions:
------------
Makes possible to assign QVector<AttributeItem> to the Model,
or implicitly converts a QVector<AttributeItem> to Model:
Model(const QVector<AttributeItem> &attributes);
Model(QVector<AttributeItem> &&attributes);
--
Allows initialize the Model with QVector<AttributeItem>:
Model(std::initializer_list<AttributeItem> attributes)
: Model(QVector<AttributeItem> {attributes.begin(), attributes.end()})
{}
--
Makes possible to assign the Model to the QVector<AttributeItem>,
or converts the Model to the QVector<AttributeItem>:
operator QVector<AttributeItem>() const;
Check copy/move/swap operations:
--------------------------------
{
using TypeToCheck = Orm::SqlQuery;
using ConstructibleFrom = QVariant;
qDebug() << "\n-- is_trivial";
qDebug() << std::is_trivial_v<TypeToCheck>;
qDebug() << "\n-- nothrow";
qDebug() << std::is_nothrow_default_constructible_v<TypeToCheck>;
qDebug() << std::is_nothrow_copy_constructible_v<TypeToCheck>;
qDebug() << std::is_nothrow_copy_assignable_v<TypeToCheck>;
qDebug() << std::is_nothrow_move_constructible_v<TypeToCheck>;
qDebug() << std::is_nothrow_move_assignable_v<TypeToCheck>;
qDebug() << std::is_nothrow_swappable_v<TypeToCheck>;
qDebug() << std::is_nothrow_destructible_v<TypeToCheck>;
qDebug() << "-- throw";
qDebug() << std::is_default_constructible_v<TypeToCheck>;
qDebug() << std::is_copy_constructible_v<TypeToCheck>;
qDebug() << std::is_copy_assignable_v<TypeToCheck>;
qDebug() << std::is_move_constructible_v<TypeToCheck>;
qDebug() << std::is_move_assignable_v<TypeToCheck>;
qDebug() << std::is_swappable_v<TypeToCheck>;
qDebug() << std::is_destructible_v<TypeToCheck>;
qDebug() << "-- trivially";
qDebug() << std::is_trivially_default_constructible_v<TypeToCheck>;
qDebug() << std::is_trivially_copyable_v<TypeToCheck>;
qDebug() << std::is_trivially_copy_constructible_v<TypeToCheck>;
qDebug() << std::is_trivially_copy_assignable_v<TypeToCheck>;
qDebug() << std::is_trivially_move_constructible_v<TypeToCheck>;
qDebug() << std::is_trivially_move_assignable_v<TypeToCheck>;
qDebug() << std::is_trivially_destructible_v<TypeToCheck>;
if constexpr (!std::is_void_v<ConstructibleFrom>) {
qDebug() << "-- nothrow constructible from";
qDebug() << std::is_nothrow_constructible_v<TypeToCheck, ConstructibleFrom>;
qDebug() << std::is_nothrow_constructible_v<TypeToCheck, const ConstructibleFrom &>;
qDebug() << std::is_nothrow_constructible_v<TypeToCheck, ConstructibleFrom &&>;
}
}
Ranges transform:
-----------------
const auto relationToWithItem = [](const auto &relation) -> WithItem
{
return WithItem {relation};
};
builder->with(relations | ranges::views::transform(relationToWithItem)
| ranges::to<QVector<WithItem>>());
DatabaseConnection config:
--------------------------
QHash<QString, QVariant> config {
// {"driver", "mysql"},
// {"url", qEnvironmentVariable("DATABASE_URL")},
// {"url", qEnvironmentVariable("MYSQL_DATABASE_URL")},
{"host", qEnvironmentVariable("DB_MYSQL_HOST", "127.0.0.1")},
{"port", qEnvironmentVariable("DB_MYSQL_PORT", "3306")},
{"database", qEnvironmentVariable("DB_MYSQL_DATABASE", "")},
{"username", qEnvironmentVariable("DB_MYSQL_USERNAME", "root")},
{"password", qEnvironmentVariable("DB_MYSQL_PASSWORD", "")},
// {"unix_socket", qEnvironmentVariable("DB_MYSQL_SOCKET", "")},
{"charset", qEnvironmentVariable("DB_MYSQL_CHARSET", "utf8mb4")},
{"collation", qEnvironmentVariable("DB_MYSQL_COLLATION", "utf8mb4_unicode_ci")},
// {"collation", qEnvironmentVariable("DB_MYSQL_COLLATION", "utf8mb4_0900_ai_ci")},
// {"timezone", "+00:00"},
// {"prefix", ""},
// {"prefix_indexes", true},
{"strict", true},
// {"engine", {}},
{"options", ""},
};
QHash<QString, QVariant> config {
{"driver", "QSQLITE"},
{"database", qEnvironmentVariable("DB_SQLITE_DATABASE", "")},
{"prefix", ""},
{"options", QVariantHash()},
{"foreign_key_constraints", qEnvironmentVariable("DB_SQLITE_FOREIGN_KEYS",
"true")},
{"check_database_exists", true},
};
QtCreator common CMake options:
-------------------------------
-G Ninja
-D CMAKE_BUILD_TYPE:STRING=Debug
-D BUILD_TESTS:BOOL=OFF
-D MATCH_EQUAL_EXPORTED_BUILDTREE:BOOL=ON
-D MYSQL_PING:BOOL=ON
-D ORM:BOOL=OFF
-D TOM:BOOL=ON
-D TOM_EXAMPLE:BOOL=ON
-D TOM_MIGRATIONS_DIR:PATH=database/migrations
-D VERBOSE_CONFIGURE:BOOL=OFF
-D CMAKE_VERBOSE_MAKEFILE:BOOL=OFF
-D CMAKE_DISABLE_PRECOMPILE_HEADERS:BOOL=OFF
-D CMAKE_EXPORT_COMPILE_COMMANDS:BOOL=OFF
-D CMAKE_PROJECT_INCLUDE_BEFORE:PATH=%{IDE:ResourcePath}/package-manager/auto-setup.cmake
-D QT_QMAKE_EXECUTABLE:STRING=%{Qt:qmakeExecutable}
-D CMAKE_PREFIX_PATH:STRING=%{Qt:QT_INSTALL_PREFIX}
-D CMAKE_C_COMPILER:STRING=%{Compiler:Executable:C}
-D CMAKE_CXX_COMPILER:STRING=%{Compiler:Executable:Cxx}
- for QtCreator:
-D CMAKE_CXX_COMPILER_LAUNCHER:FILEPATH=C:/Users/<username>/scoop/shims/ccache.exe
-D CMAKE_VERBOSE_MAKEFILE:BOOL=OFF
-D VERBOSE_CONFIGURE:BOOL=ON
-D BUILD_TESTS:BOOL=ON
-D MATCH_EQUAL_EXPORTED_BUILDTREE:BOOL=OFF
-D MYSQL_PING:BOOL=ON
-D ORM:BOOL=ON
-D TOM:BOOL=ON
-D TOM_EXAMPLE:BOOL=ON
-D TOM_MIGRATIONS_DIR:PATH=database/migrations
- MSYS2 ccache
-D CMAKE_CXX_COMPILER_LAUNCHER:FILEPATH=C:/msys64/ucrt64/bin/ccache.exe
DatabaseConnection debug code:
------------------------------
{
auto [ok, query] = select("select @@session.time_zone, @@global.time_zone");
while(query.next()) {
qDebug().nospace() << query.value(0).toString() << "\n"
<< query.value(1).toString();
}
}
{
auto [ok, query] = select("select @@session.character_set_client, @@session.character_set_connection, "
"@@session.character_set_results, @@session.collation_connection");
while(query.next()) {
qDebug().nospace() << query.value(0).toString() << "\n"
<< query.value(1).toString() << "\n"
<< query.value(2).toString() << "\n"
<< query.value(3).toString();
}
}
{
auto [ok, query] = select("select @@global.character_set_client, @@global.character_set_connection, "
"@@global.character_set_results, @@global.collation_connection");
while(query.next()) {
qDebug().nospace() << query.value(0).toString() << "\n"
<< query.value(1).toString() << "\n"
<< query.value(2).toString() << "\n"
<< query.value(3).toString();
}
}
{
auto [ok, query] = select("select @@global.sql_mode, @@session.sql_mode");
while(query.next()) {
qDebug().nospace() << query.value(0).toString() << "\n"
<< query.value(1).toString();
}
}
Database connection character set and collation debug code:
-----------------------------------------------------------
{
auto query = DB::select("SHOW VARIABLES like 'version%';", {}, connection);
while (query.next()) {
qDebug().noquote().nospace()
<< query.value("Variable_name").toString() << ": "
<< query.value("Value").toString();
}
}
{
auto query = DB::select("SHOW SESSION VARIABLES LIKE 'character_set_%'", {}, connection);
while (query.next()) {
qDebug().noquote().nospace()
<< query.value("Variable_name").toString() << ": "
<< query.value("Value").toString();
}
}
{
auto query = DB::select("SHOW SESSION VARIABLES LIKE 'collation_%';", {}, connection);
while (query.next()) {
qDebug().noquote().nospace()
<< query.value("Variable_name").toString() << ": "
<< query.value("Value").toString();
}
}
ModelsCollection<std::reference_wrapper<Model>>:
------------------------------------------------
/*! Converting constructor from the ModelsCollection<Model>. */
ModelsCollection(ModelsCollection<std::unwrap_reference_t<Model>> &models) // NOLINT(google-explicit-constructor)
requires (!std::is_pointer_v<Model> &&
std::same_as<Model, std::reference_wrapper<ModelRawType>>)
{
ModelsCollection<std::reference_wrapper<ModelRawType>> result;
result.reserve(models.size());
for (ModelRawType &model : models)
this->emplace_back(model);
}
ModelsCollection::operator==():
-------------------------------
/* Others */
/*! Equality comparison operator for the ModelsCollection. */
inline bool operator==(const ModelsCollection<Model> &) const = default;
bool operator==(const ModelsCollection<ModelRawType *> &other) const
requires (!std::is_pointer_v<Model>)
{
if (this->size() != other.size())
return false;
for (size_type index = 0; index < this->size(); ++index)
if (this->at(index) != *other.at(index))
return false;
return true;
}
tmp notes:
----------
message(-------)
message(XXX config.pri)
message(PWD: $$PWD)
message(OUT_PWD: $$OUT_PWD)
message(_PRO_FILE_PWD_: $$_PRO_FILE_PWD_)
message(INCLUDEPATH: $$INCLUDEPATH)
message(-------)
tmp notes - Queryable columns:
------------------------------
template<SubQuery T>
struct Queryable
{
QString as;
// std::variant<std::function<void(Orm::QueryBuilder &)>> queryable;
T queryable;
};
Queryable(Orm::QueryBuilder &) -> Queryable<Orm::QueryBuilder &>;
/*! Set the columns to be selected. */
template<SubQuery T>
Builder &select(const QVector<Queryable<T>> &columns)
// Builder &select(const QVector<Queryable> &columns)
{
clearColumns();
for (const auto &q : columns)
selectSub(q.queryable, q.as);
return *this;
}
/*! Set the column to be selected. */
// template<SubQuery T>
// Builder &select(const Column &column);
// /*! Add new select columns to the query. */
// template<SubQuery T>
// Builder &addSelect(const QVector<Column> &columns);
// /*! Add a new select column to the query. */
// template<SubQuery T>
// Builder &addSelect(const Column &column);
/*! Makes "from" fetch from a subquery. */
// template<SubQuery T>
// Builder &whereSub(T &&query, const QVariant &value)
// {
// /* If the column is a Closure instance and there is an operator value, we will
// assume the developer wants to run a subquery and then compare the result
// of that subquery with the given value that was provided to the method. */
// auto [queryString, bindings] = createSub(std::forward<T>(query));
// addBinding(bindings, BindingType::WHERE);
// return where(Expression(QStringLiteral("(%1)").arg(queryString)),
// QStringLiteral("="), value);
// }
Model copy ctor:
----------------
template<typename Derived, AllRelationsConcept ...AllRelations>
Model<Derived, AllRelations...>::Model(const Model &model)
: exists(model.exists)
, u_table(model.u_table)
, u_connection(model.u_connection)
, u_incrementing(model.u_incrementing)
, u_primaryKey(model.u_primaryKey)
, u_relations(model.u_relations)
, u_with(model.u_with)
, m_attributes(model.m_attributes)
, m_original(model.m_original)
, m_changes(model.m_changes)
, m_attributesHash(model.m_attributesHash)
, m_originalHash(model.m_originalHash)
, m_changesHash(model.m_changesHash)
, m_relations(model.m_relations)
, u_touches(model.u_touches)
, m_pivots(model.m_pivots)
, u_timestamps(model.u_timestamps)
{}
AssignmentList:
---------------
I want to save this pattern:
struct AssignmentListItem
{
QString column;
QVariant value;
};
class AssignmentList final : public QVector<AssignmentListItem>
{
// Inherit all the base class constructors, wow 😲✨
using QVector<AssignmentListItem>::QVector;
public:
AssignmentList(const QVariantHash &variantHash)
{
auto itHash = variantHash.constBegin();
while (itHash != variantHash.constEnd()) {
*this << AssignmentListItem({itHash.key(), itHash.value()});
++itHash;
}
}
};
Example of the std::hash<> specialization:
------------------------------------------
/*! The std::hash specialization for the CastItem. */
template<>
class std::hash<TINYORM_END_COMMON_NAMESPACE::Orm::Tiny::CastItem>
{
/*! Alias for the CastItem. */
using CastItem = TINYORM_END_COMMON_NAMESPACE::Orm::Tiny::CastItem;
/*! Alias for the CastType. */
using CastType = TINYORM_END_COMMON_NAMESPACE::Orm::Tiny::CastType;
/*! Alias for the helper utils. */
using Helpers = Orm::Utils::Helpers;
public:
/*! Generate hash for the given CastItem. */
inline std::size_t operator()(const CastItem &castItem) const noexcept
{
/*! CastType underlying type. */
using CastTypeUnderlying = std::underlying_type_t<CastType>;
std::size_t resultHash = 0;
const auto castType = static_cast<CastTypeUnderlying>(castItem.type());
Helpers::hashCombine<CastTypeUnderlying>(resultHash, castType);
Helpers::hashCombine<QString>(resultHash, castItem.modifier());
return resultHash;
}
};
Grammar::SelectComponentType:
-----------------------------
- if by any chance will be needed in the future
/*! Select component types. */
enum struct SelectComponentType
{
AGGREGATE,
COLUMNS,
FROM,
JOINS,
WHERES,
GROUPS,
HAVINGS,
ORDERS,
LIMIT,
OFFSET,
LOCK,
};
EntityManager.hpp:
------------------
#ifndef ENTITYMANAGER_H
#define ENTITYMANAGER_H
#include "orm/databaseconnection.hpp"
#include "orm/repositoryfactory.hpp"
#ifdef TINYORM_COMMON_NAMESPACE
namespace TINYORM_COMMON_NAMESPACE
{
#endif
namespace Orm
{
/*! The EntityManager is the central access point to ORM functionality. */
class SHAREDLIB_EXPORT EntityManager final
{
Q_DISABLE_COPY(EntityManager)
public:
EntityManager(const QVariantHash &config);
EntityManager(DatabaseConnection &connection);
~EntityManager();
/*! Factory method to create EntityManager instances. */
static EntityManager create(const QVariantHash &config);
/*! Gets the repository for an entity class. */
template<typename Repository>
QSharedPointer<Repository> getRepository() const;
/*! Create a new QSqlQuery. */
QSqlQuery query() const;
/*! Get a new query builder instance. */
QSharedPointer<QueryBuilder> queryBuilder() const;
/*! Check database connection and show warnings when the state changed. */
bool pingDatabase();
/*! Start a new database transaction. */
bool transaction();
/*! Commit the active database transaction. */
bool commit();
/*! Rollback the active database transaction. */
bool rollback();
/*! Start a new named transaction savepoint. */
bool savepoint(const QString &id);
/*! Rollback to a named transaction savepoint. */
bool rollbackToSavepoint(const QString &id);
/*! Get underlying database connection. */
inline DatabaseConnection &connection() const
{ return m_db; }
protected:
/*! Factory method to create DatabaseConnection instances. */
static DatabaseConnection &
createConnection(const QVariantHash &config);
private:
/*! The database connection used by the EntityManager. */
DatabaseConnection &m_db;
/*! The repository factory used to create dynamic repositories. */
RepositoryFactory m_repositoryFactory;
};
template<typename Repository>
QSharedPointer<Repository> EntityManager::getRepository() const
{
return m_repositoryFactory.getRepository<Repository>();
}
} // namespace Orm
#ifdef TINYORM_COMMON_NAMESPACE
} // namespace TINYORM_COMMON_NAMESPACE
#endif
#endif // ENTITYMANAGER_H
EntityManager.cpp:
------------------
#include "orm/entitymanager.hpp"
#include <QtSql/QSqlQuery>
#ifdef TINYORM_COMMON_NAMESPACE
namespace TINYORM_COMMON_NAMESPACE
{
#endif
namespace Orm
{
/*!
\class EntityManager
\brief The EntityManager class manages repositories and a connection
to the database.
\ingroup database
\inmodule Export
EntityManager is the base class to work with the database, it creates
and manages repository classes by helping with the RepositoryFactory
class.
Creates the database connection which is represented by
DatabaseConnection class.
EntityManager should be used in controllers ( currently TorrentExporter
is like a controller class ), services, and repository classes to access
the database. There is no need to use the QSqlDatabase or the
DatabaseConnection classes directly.
EntityManager is also injected into a repository and a service
classes constructors.
The circular dependency problem is solved by including entitymanager.hpp
in the baserepository.hpp file.
*/
EntityManager::EntityManager(const QVariantHash &config)
: m_db(createConnection(config))
, m_repositoryFactory(*this)
{}
EntityManager::EntityManager(DatabaseConnection &connection)
: m_db(connection)
, m_repositoryFactory(*this)
{}
EntityManager::~EntityManager()
{
DatabaseConnection::freeInstance();
}
EntityManager EntityManager::create(const QVariantHash &config)
{
return EntityManager(createConnection(config));
}
QSqlQuery EntityManager::query() const
{
return m_db.query();
}
QSharedPointer<QueryBuilder> EntityManager::queryBuilder() const
{
return m_db.query();
}
bool EntityManager::pingDatabase()
{
return m_db.pingDatabase();
}
bool EntityManager::transaction()
{
return m_db.transaction();
}
bool EntityManager::commit()
{
return m_db.commit();
}
bool EntityManager::rollback()
{
return m_db.rollback();
}
bool EntityManager::savepoint(const QString &id)
{
return m_db.savepoint(id);
}
bool EntityManager::rollbackToSavepoint(const QString &id)
{
return m_db.rollbackToSavepoint(id);
}
DatabaseConnection &
EntityManager::createConnection(const QVariantHash &config)
{
return DatabaseConnection::create(config.find("database").value().toString(),
config.find("prefix").value().toString(),
config);
}
} // namespace Orm
#ifdef TINYORM_COMMON_NAMESPACE
} // namespace TINYORM_COMMON_NAMESPACE
#endif
Check whether first data member in BaseCommand is QString (unsuccessful):
---
struct CommandDefinition
{};
struct BaseCommand : CommandDefinition
{
QString name;
};
struct Idx1 : CommandDefinition
{
int i = 0;
QString name;
};
template<typename T>
concept IsQString = requires(T t)
{
// requires std::same_as<decltype (std::declval<T>().name), QString>;
{t.name} -> std::same_as<QString &>;
};
BaseCommand i {.name = "h i"};
Idx1 i1 {.i = 10, .name = "h i1"};
CommandDefinition &bi = i;
CommandDefinition &bi1 = i1;
if constexpr (IsQString<decltype (reinterpret_cast<BaseCommand &>(bi))>)
qDebug() << "y";
else
qDebug() << "n";
Test Column expressions code, just swap groupBy
-----
auto q1 = Torrent::find(1)->torrentFiles()->groupBy({"xyz", "abc"}).toSql();
qDebug() << q1;
auto q2 = Torrent::find(1)->torrentFiles()->groupBy("xyz").toSql();
qDebug() << q2;
auto q = Torrent::find(1)->torrentFiles()->groupBy("abc", "def").toSql();
qDebug() << q;
auto t1 = Torrent::find(1)->torrentFiles()->groupBy({DB::raw("xyz"), "abc"}).toSql();
qDebug() << t1;
auto t2 = Torrent::find(1)->torrentFiles()->groupBy(DB::raw("xyz")).toSql();
qDebug() << t2;
auto t = Torrent::find(1)->torrentFiles()->groupBy("abc", DB::raw("def")).toSql();
qDebug() << t;
QString s1("abc");
const QString s2("fgh");
auto q3 = Torrent::find(1)->torrentFiles()->groupBy(s1, s2).toSql();
qDebug() << q3;
auto q4 = Torrent::find(1)->torrentFiles()->groupBy(std::move(s1), s2).toSql();
qDebug() << q4;
const QString s3("jkl");
auto t3 = Torrent::find(1)->torrentFiles()->groupBy(s3, DB::raw(s2)).toSql();
qDebug() << t3;
auto t4 = Torrent::find(1)->torrentFiles()->groupBy(std::move(s3), DB::raw(s2)).toSql();
qDebug() << t4;
Performance measure timer:
--------------------------
#include <QElapsedTimer>
QElapsedTimer timer;
timer.start();
qDebug().noquote() << QStringLiteral("Elapsed in XX : %1ms").arg(timer.elapsed());
$startTime = microtime(true);
printf("Elapsed in %s : %sms\n", $connection,
number_format((microtime(true) - $startTime) * 1000, 2));
Fastly write to the file:
-------------------------
#include <fstream>
std::ofstream("E:/tmp/aa.txt", std::ios::out | std::ios::app) << "first\nsecond\n";
std::ofstream("E:/tmp/aa.txt", std::ios::out | std::ios::app) << "another line\n";
Connect to the MySQL server using the raw QSqlDatabase:
-------------------------------------------------------
auto db = QSqlDatabase::addDatabase("QMYSQL");
db.setHostName(qEnvironmentVariable("DB_MYSQL_HOST"));
db.setDatabaseName(qEnvironmentVariable("DB_MYSQL_DATABASE"));
db.setUserName(qEnvironmentVariable("DB_MYSQL_USERNAME"));
db.setPassword(qEnvironmentVariable("DB_MYSQL_PASSWORD"));
db.setPort(qEnvironmentVariable("DB_MYSQL_PORT").toUInt());
db.setConnectOptions(QStringLiteral("SSL_CERT=%1;SSL_KEY=%2;SSL_CA=%3")
.arg(qEnvironmentVariable("DB_MYSQL_SSL_CERT"),
qEnvironmentVariable("DB_MYSQL_SSL_KEY"),
qEnvironmentVariable("DB_MYSQL_SSL_CA")));
auto ok = db.open();
if (ok) {
qDebug() << "yes";
}
else {
qDebug() << "no";
}
Invoke-Tests.ps1:
-----------------
- 100 times run
- 28. dec 2021
- Windows 10:
- Qt 5.15.2 ; msvc 16.11.8 x64
- debug build
All AutoTests Execution time : 792519ms
All AutoTests Average Execution time : 7925ms
- Qt 6.2.1 ; msvc 16.11.8 x64
- debug build
All AutoTests Execution time : 986531ms
All AutoTests Average Execution time : 9865ms
- Gentoo:
- Qt 5.15.2 ; GCC 11.2 x64 ccache
- debug build
All AutoTests Execution time : 519138ms
All AutoTests Average Execution time : 5191ms
- Qt 6.2.2 ; GCC 11.2 x64 ccache
- debug build
All AutoTests Execution time : 546585ms
All AutoTests Average Execution time : 5466ms
Compilation time and Memory usage:
----------------------------------
- 04. jun 2022 qmake ("CONFIG+=mysql_ping tom_example build_tests")
- Qt 6.2.4
MSVC2019 9.9GB 1:27
MSVC2022 8.0GB 1:25
clang-cl MSVC2022 5.5GB 1:35
bugs:
-----
- BUG std::unordered_map can not be instantiated with the incomplete value type, reproducible only on the Linux GCC/Clang, MSYS2 and msvc don't have any problem with the incomplete type ✨🚀
add this to the testforplay.cpp
#include <filesystem>
#include <iostream>
#include <typeindex>
#include <typeinfo>
#include <unordered_map>
namespace Models {
class Torrent;
}
struct Test1
{
std::unordered_map<QString, Models::Torrent> m_a {};
};
#include <range/v3/all.hpp>
#include <orm/db.hpp>
Unused code:
------------
Orm::Utils::Container:
- hpp
/*! Get a size of the greatest element in the container. */
template<QStringContainer T, typename SizeType = typename T::size_type>
static SizeType
maxElementSize(const T &container, typename T::size_type addToElement = 0);
template<QStringContainer T, typename SizeType>
SizeType
Container::maxElementSize(const T &container,
const typename T::size_type addToElement)
{
// Nothing to do
if (container.empty())
return 0;
SizeType result = 0;
for (const auto &element : container)
if (const auto elementSize = element.size();
elementSize > result
)
result = elementSize;
/* This is the reason for the addToElement argument, this algorithm returns 0,
if the result is 0. */
if (result == 0)
return 0;
return result + addToElement;
}
Orm::Utils::String:
- hpp
/*! Convert a string to kebab case. (kebab-case). */
inline static QString kebab(const QString &string);
/*! Get the singular form of an English word. */
static QString singular(const QString &string);
QString String::kebab(const QString &string)
{
return snake(string, Orm::Constants::DASH);
}
- cpp
QString String::singular(const QString &string)
{
if (!string.endsWith(QLatin1Char('s')))
return string;
return string.chopped(1);
}
std::format for QString:
template <>
struct std::formatter<QString>
{
constexpr static auto parse(std::format_parse_context &ctx)
{
return ctx.begin();
}
static auto format(const QString &string, std::format_context &ctx)
{
return std::format_to(ctx.out(), "{}", string.toUtf8().constData());
}
};
usage:
std::cout << std::format("String {}", QString("hello")) << "\n";
Upgrade Laravel main version:
-----------------------------
composer selfup
composer global outd -D
composer global up
laravel new laravel-10
cd .\laravel-10\
npm install
composer require laravel/breeze --dev
art breeze:install
composer require --dev barryvdh/laravel-ide-helper
npm install
| WinMerge composer.json
composer up
| create a new nginx site
install-Dotfiles.ps1 -Pretend
install-Dotfiles.ps1
new-Item -ItemType SymbolicLink -Path .\laravel-10.conf -Target ..\sites-available\laravel-10.conf
edithosts.ps1
Restart-Service phpcgi -Force
| WinMerge .env
| copy app/Support/TestSchema.php
| WinMerge app/Models/User.php
| copy app/Models/
| copy app/Http/Controllers/TestController.php
| WinMerge app/
art vendor:publish --provider="Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider" --tag=config
| copy resources/views/test/index.blade.php
| WinMerge routes/
| WinMerge config/
| WinMerge database/
| copy MySQL database using phpMyAdmin Operations - Copy Database
| copy PostgreSQL database using Create database - Template to previous DB - set Template to unchecked (false)
| replace all occurences of DB_LARAVEL_X to the new X version (.env, config/database.php)
art migrate:status
art migrate --pretend
art migrate
art migrate:status --database=pgsql
art migrate --pretend --database=pgsql
art migrate --database=pgsql
| PHPStorm create a new server and debug configuration
| copy and apply instructions in the NOTES.txt
| set MAIL_MAILER=log and MAIL_LOG_CHANNEL=mail in .env file
| create mail log channel in the config/logging.php (copy of single channel and mail.log file)
| regenerate passwords (Forgot your password?), the reset link will be logged to the storage/logs/mail.log
| login
| set breakpoint and try query DB and check storage/app/sql.log if queries are logged correctly
| done 🎉
Performance:
------------
tests was done on std::vector<AttributeItem> with 1'000'000 values
--
- appending using grow container when needed vs loop over input data, compute size and reserve
Result: reserve is 200% faster
- QString.append() vs QStringLiteral.arg()
Result: QString.append() is 50% faster
- moving vs copying data to the container
Result: moving is ~40-50% faster
- std::unordered_map lookup vs std::vector eg. with std::ranges::find_if
Result: unordered_map lookup is blazing fast 2.8s on 1'000'000 items;
std::vector with eg. std::ranges::find_if is unusable,
it so slow that on 1000 items it takes 1s, on 4000 items it takes 17s, and
on 100'000 items it takes forever, 1'000'000 items would take days 😮
- breaking point when the std::unordered_map takes performance advantage over manually
searching the std::vector:
Result for int (trivial types): ~1000 items - map 0ms and vector 1ms;
~2000 items - map 0ms and vector 5ms;
~4000 items - map 1ms and vector 21ms;
Result for QString: ~200 items - map 0ms and vector 1ms;
~400 items - map 1ms and vector 7ms;
~1000 items - map 2ms and vector 52ms;
Conclusion: so for QString if the vector has more than 300 items then is good to initialize
the unordered_map and use fast lookup
- std::unordered_map<int, int> at() vs operator[] vs find()
Result: at() and operator[] are equally fast ~120ms, find() is ~35% slower
- global inline or extern constants are much faster than constructing new strings
Result: on 1'000'000 loop for a simple QString construction it's like 8ms vs 570ms
- the QHash<QString, RelationVisitor> u_relations are ~50ms faster than the std::unordered_map,
tested with the TinyOrmPlayground with ~1595 queries
- QString constants - returning the QString from the function, 1'000'000 loops:
The following proves that TinyORM inline and extern constants are worth it.
It's 6000% faster!!! than constructing a new QString-s still again, benefits are huge. 😎👌
The function simply contains only the return xyz-scenarios described below (NRVO applied at 100%):
~9ms - QStringLiteral defined in the same TU
~9ms - inline QStringLiteral in another hpp
~10ms - extern QStringLiteral in another TU
~682ms - returning const char * directly eg. return "xyz"
~628ms - returning QLatin1String directly eg. return "xyz"_L1
~14ms - returning QStringLiteral directly using return u"xyz"_s
~14ms - returning QStringLiteral directly using return QStringLiteral("xyz")
All the same as above but forced a copy inside the function like:
QString s;
s = sl;
return s;
~46ms - QStringLiteral defined in the same TU
~46ms - inline QStringLiteral in another hpp
~47ms - extern QStringLiteral in another TU
~734ms - returning const char * directly eg. return "xyz"
~683ms - returning QLatin1String directly eg. return "xyz"_L1
~48ms - returning QStringLiteral directly using return u"xyz"_s
~50ms - returning QStringLiteral directly using return QStringLiteral("xyz")
QtSql vs TinyDrivers performance:
---------------------------------
- invoke tests Alt+PgDw
- Switch tests output to Text View and copy the whole output
- paste it to some file eg. pp.txt
- check if has the correct number of '^ <Duration ' (using RegEx Find)
- invoke the Get-TextCasesDuration.ps1 on this file to obtain the result (Duration in seconds)
TestCases UnitTests BuildTime Type Duration in seconds
39 1924(50) 3:13 loadable - 25.00, 26.00, 25.90, 25.23, 25.21
39 1922(50) 2:58 shared - 24.68, 24.92, 24.96
39 1920(50) 3:04 static - 25.50, 24.58, 24.34
37 1914(42) 2:51 QtSql - 26.10, 25.75, 25.20, 25.31
Qt confusions:
--------------
QVariant null vs invalid:
- null QVariant is valid
- invalid QVariant is also null!
- so checks like this (attribute.isNull() || !attribute.isValid()) are redundand because it would
be enough to only check (attribute.isNull()), but I'm checking both in most cases.
QVariant vnull {QMetaType(QMetaType::QString)};
QVariant vinvalid;
qDebug() << vnull.isNull();
qDebug() << !vnull.isValid();
qDebug() << vinvalid.isNull();
qDebug() << !vinvalid.isValid();
c++ library confusions:
-----------------------
- std::unordred_map reserve() vs ctor(size_type), they are the same
- shared_ptr and unique_ptr complete vs incomeplete type:
- https://en.cppreference.com/w/cpp/memory/unique_ptr
- https://howardhinnant.github.io/incomplete.html
local copy in Books (search):
smart_pointers_shared_ptr_unique_ptr_incomplete_types.html
- shared_ptr doesn't need polymorphic class to correctly destroy it through the shared_ptr<Base>
- unique_ptr does need polymorphic class to correctly destroy it through the unique_ptr<Base>
- shared_ptr needs complete type during construction
- unique_ptr can be constructed with incomplete type
c++ mutex-es:
-------------
The Note, to avoid always figuring out how this works, update this if I will use mutexes and will
have more experiences.
Nice sum up, all about multithreading:
https://www.youtube.com/watch?v=A7sVFJLJM-A&t=2450s&ab_channel=CppCon
- always use std::scoped_lock
- std::lock and std::try_lock are simple functions which lock mutex, like std::mutex::lock/try_lock
- std::lock_guard unlocks mutex at the end of a scope
- std::scoped_lock does the same as std::lock and std::lock_guard
- std::unique_lock is needed with the std::conditional_variable, with these you can wait
for data across threads
- the best example is in the cpp std::scoped_lock documentation:
https://en.cppreference.com/w/cpp/thread/scoped_lock
Shared/exclusive (read/write) mutex-es:
- std::shared_mutex - use it for this purposes
- std::shared_lock locks shared_mutex in the shared/read mode
- std::scoped_lock and std::unique_lock locks shared_mutex in the exclusive/write mode
- std::scoped_lock can be used with shared_mutex-es the same way as the std::unique_lock
- also everything described in the section above is true for the shared_mutex
in the exclusive/write mode but not for the std::shared_lock (it's special)
Qt mappings:
- QReadWriteLock = std::shared_mutex
- QReadLocker = std::shared_lock
- QWriteLocker = std::scoped_lock or std::unique_lock
Serialization - Appending Values To JSON:
-----------------------------------------
Currently isn't possible to serialize appended Aggregate types and Value Objects because
of the QJsonValue, the reason is explained in the QJsonValue::fromVariant() documentation
at https://doc.qt.io/qt-6/qjsonvalue.html#fromVariant
The best what can be done is to return the QVariantMap or QVariantList from the accessors.
Aggregate types and Value Objects works good with the Model's toMap() and toVector() methods,
especially with Qt6's QVariant::fromValue(), Qt6 doesn't need to declare the Q_DECLARE_METATYPE().
PowerShell confusions:
----------------------
- .Count comes from IEnumerate interface and should be used for array-like containers
- on arrays the Count is an alias to the Length property
- .Length should be used for strings
- string doesn't have Count property but 'str'.Count returns 1
- array-s are immutable
- -match operator:
- calling -match on string returns bool and sets the $Matches variable
- calling -match container filters it
- variable names are case-insensitive
- you don't have to ` escape newline: after + | = or inside () -join -replace
- you must ` escape newline between function parameters eg.: -Path `\n -Message
qmake confusions:
-----------------
- load() vs include() prf files:
- if load() or include() is called inside other feature prf or pri file then you don't need
to load() or include() again, it's imported into the current scope (what is logical)
- if any variable is defined inside other feature prf or pri file and this file is
include()-ed or load()-ed then the variable will be available in the so called parent file
where the inclusion was called; this is also true if the variable is defined inside
the qmake Scope but isn't true if the variable is defined inside the test or replace function
- include() or load() inside function will not be available outside this function
- include()
- includes the contents of the file specified by filename into the current project
at the point where it is included
- succeeds if filename is included; otherwise it fails
- included file is processed immediately
- load()
- loads the feature file (.prf) specified by feature, unless the feature has already been loaded
- if the CONFIG contains the feature name it will be loaded after the current project is
processed
- the feature will be loaded for every subproject
- the load() function can also be in scope checks (returns true/false) but it doesn't make
sense because it also throws an error
- using CONFIG in the qmake features (prf files):
- you set CONFIG in the feature prf files but they will no effect on the current project file
because feature prf files are processed after the current project
- so if you need to set another CONFIG option inside the feature prf file you need to call
the load(feature) manually
- build_pass:
Additional makefile is being written (build_pass is not set when the primary Makefile
is being written).
When both debug_and_release and static_and_shared are used all four Debug/Release and
Static/Shared combinations will occur in addition to build_pass.
- static vs staticlib
- static
means prefer linking against static library archives and if the subproject TEMAPLTE qmake
option constains ".*lib" then set the CONFIG += staticlib
- staticlib
means build all subprojects as static archive libraries and it also sets the CONFIG += static
What means don't use it for the whole TinyORM project because it contains many subprojects
and it can cause problems, instead use the CONFIG += static!
- export() variable in deeper functions:
- if we are eg. three functions deep inside then it's enough to call export() only once,
there is no need to re-export() in parent functions
tools/deploy.ps1:
-----------------
Alternative committing code:
function Invoke-BumpVersions
NewLine
Write-Info ('Please check updated versions and commit all files in SmartGit with ' +
'the following commit message')
NewLine
Write-Output (Get-BumpCommitMessage)
Approve-Continue -Exit
function Invoke-CreateTag
Write-Info 'Please create annotated signed tag in SmartGit with the following tag message'
NewLine
Write-Output (Get-TagMessage)
Approve-Continue -Exit
Windows File properties:
------------------------
tags: FILEFLAGS, debug
--
They can also be checked using, especially checking the IsDebug build:
(Get-Item .\tom.exe).VersionInfo | fl
TinyORM C++ Coding Style:
-------------------------
- Macro guard must copy what will be in the #include for the current header file,
not how the namespace is named and isn't based on the folder structure.
Eg. if the following header files will be included like this:
#include "orm/drivers/mysql/mysqlresult_p.hpp"
#include "orm/drivers/mysql/constants_extern_p.hpp"
Then the macro guards will be:
#define ORM_DRIVERS_MYSQL_MYSQLRESULT_P_HPP
#define ORM_DRIVERS_MYSQL_CONSTANTS_EXTERN_P_HPP
GitHub actions self-hosted runners:
-----------------------------------
After a few week all stoped runners are invalided so they must be re-configured.
Simply delete the .runner file and invoke the config script again as is described here:
https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/adding-self-hosted-runners
- !!! backup .env and .path (Linux only) before deleting whole runner's folder
- no need to remove services before re-configuring
- on Linux call the sudo ./svc.sh stop before
- Linux svc commands: sudo ./svc.sh start/status/stop
- config.sh script on Linux can be invoked without sudo
- config.cmd on Windows must be invoked in Admin. shell because it installs service
- all prompt questions leave at default, only one thing is to change the Windows runner name
to the lowercase
- I have added two helper scripts to execute/stop these runners:
- Start-TinyORMActions
- Stop-TinyORMActions
- needed manual actions:
- deleting build folders eg. after compiler or Qt upgrade
- deleting and re-cloning the vcpkg repo
- the owner must be changed on Windows to NETWORK SERVICE because of git filesystem owner check
- isn't needed often, look on this step: vcpkg upgrade repository (latest version)