diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1b43329c..f230597b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,473 +1,475 @@ # Private options (visible only within KDb) simple_option(KDB_EXPRESSION_DEBUG "Debugging of Expression classes" OFF) simple_option(KDB_DRIVERMANAGER_DEBUG "Debugging of the Driver Manager class" OFF) simple_option(KDB_TRANSACTIONS_DEBUG "Debugging of the Transaction class" OFF) simple_option(KDB_TABLESCHEMACHANGELISTENER_DEBUG "Debugging of the KDbTableSchemaChangeListener class" OFF) simple_option(KDB_QUERYSCHEMA_DEBUG "Debugging of the QuerySchema class" OFF) simple_option(KDB_SQLPARSER_DEBUG "Debugging of the SQL parser" OFF) # Public options (affecting public behavior or contents of KDb) simple_option(KDB_DEBUG_GUI "GUI for debugging" OFF) # NOTE: always add public options to KDbConfig.cmake.in as well include(CheckIncludeFile) check_include_file(unistd.h HAVE_UNISTD_H) #add_definitions( # TODO -DKDE_DEFAULT_DEBUG_AREA=44000 #) ########### generate parser/lexer files ############### # as described at https://public.kitware.com/pipermail/cmake/2002-September/003028.html # Create target for the parser add_custom_target(parser echo "Creating parser/lexer files") set(PARSER_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/parser) # Create custom command for flex/lex (note the outputs) # TODO(GEN) uncomment GENERATED if we ever use this patch: https://phabricator.kde.org/D357 "No more generated parser/scanner files in the source dir" add_custom_command( TARGET parser COMMAND ${PARSER_SOURCE_DIR}/generate_parser_code.sh DEPENDS ${PARSER_SOURCE_DIR}/KDbSqlParser.y ${PARSER_SOURCE_DIR}/KDbSqlScanner.l ${PARSER_SOURCE_DIR}/generate_parser_code.sh OUTPUT #TODO(GEN) ${PARSER_SOURCE_DIR}/generated/sqlparser.h #TODO(GEN) ${PARSER_SOURCE_DIR}/generated/sqlparser.cpp #TODO(GEN) ${PARSER_SOURCE_DIR}/generated/sqlscanner.cpp #TODO(GEN) ${PARSER_SOURCE_DIR}/generated/KDbToken.h #TODO(GEN) ${PARSER_SOURCE_DIR}/generated/KDbToken.cpp ) string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) if("${CMAKE_BUILD_TYPE_LOWER}" MATCHES "debug") add_definitions(-DYYDEBUG=1) # needed where sqlparser.h is used endif() if(NOT HAVE_UNISTD_H) set(EXTRA_SCANNER_COMPILE_FLAGS "-DYY_NO_UNISTD_H=1") endif() if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_CLANG) set(EXTRA_SCANNER_COMPILE_FLAGS "${EXTRA_SCANNER_COMPILE_FLAGS} -Wno-sign-compare -Wno-unused-function -Wno-deprecated-register -Wno-zero-as-null-pointer-constant") elseif(MSVC) set(EXTRA_SCANNER_COMPILE_FLAGS "${EXTRA_SCANNER_COMPILE_FLAGS} /wd4018") # disable warning C4018: '<' : signed/unsigned mismatch endif() # Mark files as generated, set compile flags set_source_files_properties(${PARSER_SOURCE_DIR}/generated/sqlparser.cpp PROPERTIES #TODO(GEN) GENERATED TRUE SKIP_AUTOMOC ON # YYERROR_VERBOSE=1 needed to get a token table for tokenName() even for release builds COMPILE_FLAGS "-DYYERROR_VERBOSE=1 ${EXTRA_PARSER_COMPILE_FLAGS} " ) # TODO(GEN) set_source_files_properties(${PARSER_SOURCE_DIR}/generated/sqlparser.h PROPERTIES GENERATED TRUE) # TODO(GEN) set_source_files_properties(${PARSER_SOURCE_DIR}/generated/KDbToken.h PROPERTIES GENERATED TRUE) set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/KDbConnectionData_sdc.cpp PROPERTIES GENERATED TRUE SKIP_AUTOMOC ON ) set_source_files_properties( ${PARSER_SOURCE_DIR}/generated/sqlscanner.cpp PROPERTIES #TODO(GEN) GENERATED TRUE SKIP_AUTOMOC ON COMPILE_FLAGS "${EXTRA_SCANNER_COMPILE_FLAGS} " ) set(kdb_LIB_SRCS parser/generated/sqlscanner.cpp parser/generated/sqlparser.cpp parser/generated/KDbToken.cpp parser/KDbParser.cpp parser/KDbParser_p.cpp parser/KDbSqlParser.y parser/KDbSqlScanner.l parser/generate_parser_code.sh parser/extract_tokens.sh parser/TODO tools/KDbJsonTrader_p.cpp # mostly copied from KReport's KReportJsonTrader_p.cpp tools/KDbValidator.cpp tools/KDbFieldValidator.cpp tools/KDbLongLongValidator.cpp tools/KDbObjectNameValidator.cpp tools/KDbIdentifierValidator.cpp tools/KDbUtils.cpp #TODO tools/debuggui.cpp #TODO tools/KDbSimpleCommandLineApp.cpp tools/transliteration/transliteration_table.cpp tools/transliteration/generate_transliteration_table.sh tools/transliteration/transliteration_table.readme + KDbDateTime.cpp KDbEscapedString.cpp KDbResult.cpp KDbQueryAsterisk.cpp KDbConnectionData.cpp KDbVersionInfo.cpp ${CMAKE_CURRENT_BINARY_DIR}/KDbConnectionData_sdc.cpp KDbField.cpp KDbQuerySchemaParameter.cpp expression/KDbExpression.cpp expression/KDbNArgExpression.cpp expression/KDbUnaryExpression.cpp expression/KDbBinaryExpression.cpp expression/KDbConstExpression.cpp expression/KDbQueryParameterExpression.cpp expression/KDbVariableExpression.cpp expression/KDbFunctionExpression.cpp KDbFieldList.cpp KDbTableSchema.cpp KDbTableSchemaChangeListener.cpp KDbIndexSchema.cpp KDbOrderByColumn.cpp KDbQuerySchema.cpp KDbQuerySchema_p.cpp KDbQueryColumnInfo.cpp KDbTableOrQuerySchema.cpp KDbDriverManager.cpp KDbDriver.cpp KDbDriver_p.cpp KDbDriverMetaData.cpp KDbConnection.cpp KDbConnectionProxy.cpp generated/sqlkeywords.cpp KDbObject.cpp KDb.cpp KDbRecordData.cpp KDbCursor.cpp KDbTransaction.cpp KDbGlobal.cpp KDbRelationship.cpp KDbRecordEditBuffer.cpp KDbMessageHandler.cpp KDbPreparedStatement.cpp KDbProperties.cpp KDbAdmin.cpp KDbLookupFieldSchema.cpp KDbAlter.cpp KDbNativeStatementBuilder.cpp kdb_debug.cpp views/KDbTableViewData.cpp views/KDbTableViewColumn.cpp views/chartable.txt sql/KDbSqlField.cpp sql/KDbSqlRecord.cpp sql/KDbSqlResult.cpp # private: tools/KDbUtils_p.h # non-source: Mainpage.dox Messages.sh ) ecm_create_qm_loader(kdb_LIB_SRCS kdb_qt) add_library(KDb SHARED ${kdb_LIB_SRCS}) set_coinstallable_lib_version(KDb) kdb_create_shared_data_classes( kdb_GENERATED_SHARED_DATA_CLASS_HEADERS # output variable with list of headers NO_PREFIX # subdirectory in which the headers should be generated KDbConnectionData.shared.h KDbObject.shared.h KDbQuerySchemaParameter.shared.h KDbResult.shared.h KDbSelectStatementOptions.shared.h KDbVersionInfo.shared.h ) kdb_remove_extensions( kdb_GENERATED_SHARED_DATA_CLASS_BASENAMES ${kdb_GENERATED_SHARED_DATA_CLASS_HEADERS} ) #message(STATUS "kdb_GENERATED_SHARED_DATA_CLASS_HEADERS: ${kdb_GENERATED_SHARED_DATA_CLASS_HEADERS}") #add_dependencies(KDb _shared_classes) # generate shared classes before they can be used in KDb generate_export_header(KDb) set(kdb_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/expression ${CMAKE_CURRENT_SOURCE_DIR}/interfaces ${CMAKE_CURRENT_SOURCE_DIR}/parser ${CMAKE_CURRENT_SOURCE_DIR}/parser/generated ${CMAKE_CURRENT_SOURCE_DIR}/sql ${CMAKE_CURRENT_SOURCE_DIR}/tools ${CMAKE_CURRENT_SOURCE_DIR}/views ) target_include_directories(KDb PUBLIC "$" INTERFACE "$" PRIVATE ${ICU_INCLUDE_DIRS} ) target_link_libraries(KDb PUBLIC Qt5::Core Qt5::Gui Qt5::Widgets KF5::CoreAddons PRIVATE Qt5::Xml ${ICU_I18N_LIBRARY} ) if(BUILD_TEST_COVERAGE) target_link_libraries(KDb PRIVATE gcov ) endif() # Create a Config.cmake and a ConfigVersion.cmake file and install them set(CMAKECONFIG_INSTALL_DIR "${CMAKECONFIG_INSTALL_PREFIX}/${KDB_BASE_NAME}") ecm_setup_version(${PROJECT_VERSION} VARIABLE_PREFIX KDB SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kdb_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KDbConfigVersion.cmake" ) install(TARGETS KDb EXPORT KDbTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KDbConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KDbConfig.cmake" INSTALL_DESTINATION "${CMAKECONFIG_INSTALL_DIR}" ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KDbConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KDbConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel) install(EXPORT KDbTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KDbTargets.cmake) ecm_generate_pri_file( BASE_NAME ${KDB_BASE_NAME} LIB_NAME ${KDB_BASE_NAME} DEPS "widgets xml" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDB_INCLUDE_INSTALL_DIR} ) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/kdb_version.h" DESTINATION "${KDB_INCLUDE_INSTALL_DIR}" COMPONENT Devel) ecm_generate_headers(kdb_FORWARDING_HEADERS REQUIRED_HEADERS kdb_HEADERS ORIGINAL CAMELCASE HEADER_NAMES KDb KDbAdmin KDbAlter KDbQueryAsterisk KDbConnection KDbConnectionOptions KDbConnectionProxy KDbCursor + KDbDateTime KDbDriver KDbDriverBehavior KDbDriverManager KDbDriverMetaData KDbError KDbEscapedString KDbField KDbFieldList KDbGlobal KDbIndexSchema KDbLookupFieldSchema KDbMessageHandler KDbNativeStatementBuilder KDbPreparedStatement KDbProperties KDbQueryColumnInfo KDbOrderByColumn KDbQuerySchema KDbRecordData KDbRecordEditBuffer KDbRelationship KDbTableOrQuerySchema KDbTableSchema KDbTableSchemaChangeListener KDbTransaction KDbTransactionData KDbTransactionGuard ) ecm_generate_headers(kdb_FORWARDING_HEADERS REQUIRED_HEADERS kdb_HEADERS ORIGINAL CAMELCASE RELATIVE expression HEADER_NAMES KDbExpression KDbExpressionData ) ecm_generate_headers(kdb_FORWARDING_HEADERS REQUIRED_HEADERS kdb_HEADERS ORIGINAL CAMELCASE RELATIVE interfaces HEADER_NAMES KDbPreparedStatementInterface ) ecm_generate_headers(kdb_FORWARDING_HEADERS REQUIRED_HEADERS kdb_HEADERS ORIGINAL CAMELCASE RELATIVE parser HEADER_NAMES KDbParser ) ecm_generate_headers(kdb_FORWARDING_HEADERS REQUIRED_HEADERS kdb_HEADERS ORIGINAL CAMELCASE RELATIVE parser/generated HEADER_NAMES KDbToken ) ecm_generate_headers(kdb_FORWARDING_HEADERS REQUIRED_HEADERS kdb_HEADERS ORIGINAL CAMELCASE RELATIVE sql HEADER_NAMES KDbSqlField KDbSqlRecord KDbSqlResult KDbSqlString ) ecm_generate_headers(kdb_FORWARDING_HEADERS REQUIRED_HEADERS kdb_HEADERS ORIGINAL CAMELCASE RELATIVE views HEADER_NAMES KDbTableViewData KDbTableViewColumn ) ecm_generate_headers(kdb_FORWARDING_HEADERS REQUIRED_HEADERS kdb_HEADERS ORIGINAL CAMELCASE RELATIVE tools HEADER_NAMES KDbValidator KDbUtils KDbTristate #todo KDbSimpleCommandLineApp KDbLongLongValidator KDbIdentifierValidator KDbFieldValidator KDbObjectNameValidator ) #message(STATUS "%% ${kdb_GENERATED_SHARED_DATA_CLASS_BASENAMES}") ecm_generate_headers(kdb_FORWARDING_HEADERS_FROM_BUILDDIR REQUIRED_HEADERS kdb_HEADERS_FROM_BUILDDIR ORIGINAL CAMELCASE SOURCE_DIR ${PROJECT_BINARY_DIR}/src HEADER_NAMES ${kdb_GENERATED_SHARED_DATA_CLASS_BASENAMES} ) #message(STATUS "%%kdb_HEADERS_FROM_BUILDDIR ${kdb_HEADERS_FROM_BUILDDIR}") install( FILES ${kdb_HEADERS} ${kdb_HEADERS_FROM_BUILDDIR} DESTINATION ${KDB_INCLUDE_INSTALL_DIR} COMPONENT Devel ) install( FILES ${kdb_FORWARDING_HEADERS} ${kdb_FORWARDING_HEADERS_FROM_BUILDDIR} ${PROJECT_BINARY_DIR}/src/kdb_export.h ${PROJECT_BINARY_DIR}/src/config-kdb.h DESTINATION ${KDB_INCLUDE_INSTALL_DIR} COMPONENT Devel ) # KDb/Private includes # install( FILES # Connection_p.h # Driver_p.h # DESTINATION ${KDB_INCLUDE_INSTALL_DIR}/Private COMPONENT Devel # ) # KDb/Interfaces includes # install( FILES # Interfaces/KDbPreparedStatementInterface.h includes/KDb/Interfaces/KDbPreparedStatementInterface # DESTINATION ${KDB_INCLUDE_INSTALL_DIR}/Interfaces COMPONENT Devel # ) if(BUILD_QCH) kdb_add_qch( KDb_QCH NAME KDb BASE_NAME ${KDB_BASE_NAME} VERSION ${PROJECT_VERSION} NAMESPACE org.kde.${KDB_BASE_NAME} SOURCES Mainpage.dox ${kdb_HEADERS} ${kdb_HEADERS_FROM_BUILDDIR} LINK_QCHS Qt5Core_QCH Qt5Gui_QCH Qt5Widgets_QCH KF5CoreAddons_QCH BLANK_MACROS KDB_EXPORT KDB_DEPRECATED TAGFILE_INSTALL_DESTINATION ${KDB_QTQCH_FULL_INSTALL_DIR} QCH_INSTALL_DESTINATION ${KDB_QTQCH_FULL_INSTALL_DIR} ) set(kdb_qch_targets KDb_QCH) endif() kdb_install_qch_export( TARGETS ${kdb_qch_targets} FILE KDbQCHTargets.cmake ​ DESTINATION "${CMAKECONFIG_INSTALL_DIR}" ​ COMPONENT Devel ​) add_subdirectory(drivers) enable_testing() configure_file(config-kdb.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kdb.h) configure_file(config-kdb-private.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kdb-private.h) target_sources(KDb PRIVATE ${PROJECT_BINARY_DIR}/src/config-kdb-private.h ) diff --git a/src/KDbDateTime.cpp b/src/KDbDateTime.cpp new file mode 100644 index 00000000..66348e08 --- /dev/null +++ b/src/KDbDateTime.cpp @@ -0,0 +1,446 @@ +/* This file is part of the KDE project + Copyright (C) 2018 Jarosław Staniek + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + * Boston, MA 02110-1301, USA. + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + */ + +#include "KDbDateTime.h" + +#include + +const int UNCACHED_YEAR = -1; +const int INVALID_YEAR = -2; + +namespace { +template +std::function byteArrayToString() +{ + return [](const T &v) { return QString::fromLatin1(v.toString()); }; +} + +struct KDbDateTimeMetatypeInitializer { + KDbDateTimeMetatypeInitializer() + { + using namespace std::placeholders; + QMetaType::registerConverter(byteArrayToString()); + QMetaType::registerConverter(std::bind(&KDbYear::toIsoValue, _1)); + QMetaType::registerConverter(byteArrayToString()); + QMetaType::registerConverter(std::bind(&KDbDate::toQDate, _1)); + QMetaType::registerConverter(byteArrayToString()); + QMetaType::registerConverter(std::bind(&KDbTime::toQTime, _1)); + QMetaType::registerConverter(byteArrayToString()); + QMetaType::registerConverter(std::bind(&KDbDateTime::toQDateTime, _1)); + QMetaType::registerComparators(); + QMetaType::registerComparators(); + QMetaType::registerComparators(); + QMetaType::registerComparators(); + } +}; + +KDbDateTimeMetatypeInitializer s_init; +} + +bool KDbYear::operator==(const KDbYear &other) const +{ + return m_sign == other.sign() && m_string == other.yearString(); +} + +bool KDbYear::operator<(const KDbYear &other) const +{ + return toQDateValue() < other.toQDateValue(); +} + +bool KDbYear::isValid() const +{ + return std::get<1>(intValue()); +} + +bool KDbYear::isNull() const +{ + return m_sign == Sign::None && m_string.isEmpty(); +} + +QByteArray KDbYear::signString() const +{ + QByteArray result; + switch (m_sign) { + case Sign::Plus: + result = QByteArrayLiteral("+"); + break; + case Sign::Minus: + result = QByteArrayLiteral("-"); + break; + default: + break; + } + return result; +} + +KDB_EXPORT QDebug operator<<(QDebug dbg, KDbYear::Sign sign) +{ + QDebugStateSaver saver(dbg); + switch (sign) { + case KDbYear::Sign::None: + break; + case KDbYear::Sign::Plus: + dbg.nospace() << '+'; + break; + case KDbYear::Sign::Minus: + dbg.nospace() << '-'; + break; + } + return dbg.maybeSpace(); +} + +KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbYear& year) +{ + QDebugStateSaver saver(dbg); + dbg.nospace().noquote() << "KDbYear(" << year.sign() << year.yearString(); + if (!year.isValid()) { + dbg.nospace() << " INVALID"; + } + dbg.nospace() << ")"; + return dbg.maybeSpace(); +} + +QByteArray KDbYear::toString() const +{ + QByteArray result; + if (isNull()) { + result = QByteArrayLiteral(""); + } else { // can be invalid, that's OK + result = signString() + m_string; + } + return result; +} + +int KDbYear::toIsoValue() const +{ + return std::get<0>(intValue()); +} + +int KDbYear::toQDateValue() const +{ + int result; + bool ok; + std::tie(result, ok) = intValue(); + if (!ok) { + return 0; + } + if (result > 0) { + return result; + } + return result - 1; +} + +namespace { +int intValueInternal(KDbYear::Sign sign, const QByteArray &string) +{ + const int length = string.length(); + if (length < 4) { + // TODO message: at least 4 digits required + return INVALID_YEAR; + } else if (length > 4) { + if (sign == KDbYear::Sign::None) { + // TODO message: more than 4 digits, sign required + return INVALID_YEAR; + } + } + + static QRegularExpression digitsRegExp(QStringLiteral("^\\d+$")); + if (!digitsRegExp.match(QString::fromLatin1(string)).hasMatch()) { + // TODO message: only digits are accepted for year + return INVALID_YEAR; + } + + bool ok; + int result = string.toInt(&ok); + if (!ok || result < 0) { + // TODO message: failed to convert year to integer >= 0 + return INVALID_YEAR; + } + int qDateYear; + if (result == 0) { + if (sign != KDbYear::Sign::Plus) { + // TODO message: + required for 0000 + return INVALID_YEAR; + } + qDateYear = -1; + } else if (sign == KDbYear::Sign::Minus) { + qDateYear = - result - 1; + } else { // Plus or None + qDateYear = result; + } + // verify if this year is within the limits of QDate (see QDate::minJd(), QDate::maxJd()) + if (!QDate(qDateYear, 1, 1).isValid()) { + // TODO message: year is not within limits + return INVALID_YEAR; + } + return result; +} +} + +std::tuple KDbYear::intValue() const +{ + if (m_isoValue == UNCACHED_YEAR) { + const_cast(m_isoValue) = intValueInternal(m_sign, m_string); // cache + } + if (m_isoValue == INVALID_YEAR) { + return std::make_tuple(0, false); + } + return std::make_tuple(m_sign == Sign::Minus ? -m_isoValue : m_isoValue, true); +} + +bool KDbDate::operator==(const KDbDate &other) const +{ + return m_year == other.year() && m_monthString == other.monthString() + && m_dayString == other.dayString(); +} + +bool KDbDate::operator<(const KDbDate &other) const +{ + return toQDate() < other.toQDate(); +} + +bool KDbDate::isValid() const +{ + return toQDate().isValid(); +} + +bool KDbDate::isNull() const +{ + return m_year.isNull() && m_monthString.isEmpty() && m_dayString.isEmpty(); +} + +QDate KDbDate::toQDate() const +{ + return { m_year.toQDateValue(), month(), day() }; +} + +namespace { +int toInt(const QByteArray &string, int min, int max, int minLength, int maxLength) +{ + if (string.length() < minLength || string.length() > maxLength) { + // TODO message: invalid length + return -1; + } + bool ok = true; + const int result = string.isEmpty() ? 0 : string.toInt(&ok); + if (!ok || result < min || result > max) { + // TODO message: could not convert string to integer + return -1; + } + return result; +} +} + +int KDbDate::month() const +{ + return toInt(m_monthString, 1, 12, 1, 2); +} + +int KDbDate::day() const +{ + return toInt(m_dayString, 1, 31, 1, 2); +} + +QByteArray KDbDate::toString() const +{ + QByteArray result; + if (isNull()) { + result = QByteArrayLiteral(""); + } else { // can be invalid, that's OK + result = m_year.toString() + '-' + m_monthString + '-' + m_dayString; + } + return result; +} + +KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbDate &date) +{ + QDebugStateSaver saver(dbg); + dbg.nospace().noquote() << "KDbDate(" << date.toString(); + if (!date.isValid()) { + dbg.nospace() << " INVALID"; + } + dbg.nospace() << ")"; + return dbg.maybeSpace(); +} + +bool KDbTime::operator==(const KDbTime &other) const +{ + return m_hourString == other.hourString() && m_minuteString == other.minuteString() + && m_secondString == other.secondString() && m_msecString == other.msecString() + && m_period == other.period(); +} + +bool KDbTime::operator<(const KDbTime &other) const +{ + return toQTime() < other.toQTime(); +} + +QTime KDbTime::toQTime() const +{ + // Rules for hours based on https://www.timeanddate.com/time/am-and-pm.html#converting + int h = hour(); + if (h == -1) { + return {}; + } + const int m = minute(); + if (m == -1) { + return {}; + } + const int s = second(); + if (s == -1) { + return {}; + } + const int ms = msec(); + if (ms == -1) { + return {}; + } + if (m_period == Period::None) { + return { h, m, s, ms }; + } + return QTime::fromString( + QStringLiteral("%1:%2:%3.%4 %5") + .arg(h) + .arg(m) + .arg(s) + .arg(ms) + .arg(m_period == Period::Am ? QLatin1String("AM") : QLatin1String("PM")), + QStringLiteral("h:m:s.z AP")); +} + +int KDbTime::hour() const +{ + switch (m_period) { + case Period::None: + return toInt(m_hourString, 0, 23, 1, 2); + case Period::Am: + case Period::Pm: + return toInt(m_hourString, 1, 12, 1, 2); + } + return -1; +} + +int KDbTime::minute() const +{ + return toInt(m_minuteString, 0, 59, 1, 2); +} + +int KDbTime::second() const +{ + return toInt(m_secondString, 0, 59, 0, 2); +} + +int KDbTime::msec() const +{ + return toInt(m_msecString, 0, 999, 0, 3); +} + +bool KDbTime::isValid() const +{ + return toQTime().isValid(); +} + +bool KDbTime::isNull() const +{ + return m_hourString.isEmpty() || m_minuteString.isEmpty(); +} + +QByteArray KDbTime::toString() const +{ + QByteArray result; + if (isNull()) { + result = QByteArrayLiteral(""); + } else if (m_msecString.isEmpty()) { // can be invalid, that's OK + if (m_secondString.isEmpty()) { + result = m_hourString + ':' + m_minuteString; + } else { + result = m_hourString + ':' + m_minuteString + ':' + m_secondString; + } + } else { // can be invalid, that's OK + result = m_hourString + ':' + m_minuteString + ':' + m_secondString + '.' + m_msecString; + } + switch (m_period) { + case KDbTime::Period::Am: + result += " AM"; + break; + case KDbTime::Period::Pm: + result += " PM"; + break; + default: + break; + } + return result; +} + +KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbTime &time) +{ + QDebugStateSaver saver(dbg); + dbg.nospace().noquote() << "KDbTime(" << time.toString(); + if (!time.isValid()) { + dbg.nospace() << " INVALID"; + } + dbg.nospace() << ")"; + return dbg.maybeSpace(); +} + +bool KDbDateTime::operator==(const KDbDateTime &other) const +{ + return date() == other.date() && time() == other.time(); +} + +bool KDbDateTime::operator<(const KDbDateTime &other) const +{ + return toQDateTime() < other.toQDateTime(); +} + +bool KDbDateTime::isValid() const +{ + return m_date.isValid() && m_time.isValid(); +} + +bool KDbDateTime::isNull() const +{ + return m_date.isNull() || m_time.isNull(); +} + +QDateTime KDbDateTime::toQDateTime() const +{ + return { m_date.toQDate(), m_time.toQTime() }; +} + +QByteArray KDbDateTime::toString() const +{ + QByteArray result; + if (isNull()) { + result = QByteArrayLiteral(""); + } else { + result = m_date.toString() + ' ' + m_time.toString(); // can be invalid, that's OK + } + return result; +} + +KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbDateTime &dateTime) +{ + QDebugStateSaver saver(dbg); + dbg.nospace().noquote() << "KDbDateTime(" << dateTime.toString(); + if (!dateTime.isValid()) { + dbg.nospace() << "INVALID"; + } + dbg.nospace() << ")"; + return dbg.maybeSpace(); +} diff --git a/src/KDbDateTime.h b/src/KDbDateTime.h new file mode 100644 index 00000000..faa4f218 --- /dev/null +++ b/src/KDbDateTime.h @@ -0,0 +1,475 @@ +/* This file is part of the KDE project + Copyright (C) 2018 Jarosław Staniek + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KDB_DATETIME_H +#define KDB_DATETIME_H + +#include "kdb_export.h" + +#include +#include + +/** + * Generic year constant based on extended ISO 8601 specification + * + * This class as well as KDbDate, KDbTime and KDbDateTime is used as a replacement for Qt date/time + * value classes in case when invalid values have to be preserved. Without this, SQL parsers would + * not function properly because they would loose information about mistyped constants. + * The KDb* make it possible for the user to store the constants in SQL expressions and fix them + * at later time. + * + * See this page for specifications of the date/time formats supported: + * https://community.kde.org/Kexi/Plugins/Queries/SQL_Constants#Date_constants + * + * @since 3.1.1 + */ +class KDB_EXPORT KDbYear +{ +public: + /** + * Specifies sign which is used to annotate year + */ + enum class Sign { + None, + Plus, + Minus + }; + + /** + * Constructs year based on given sign and string + * + * Resulting year can be invalid but the string is always preserved. + */ + KDbYear(Sign sign, const QByteArray &string) : m_sign(sign), m_string(string) + { + } + + /** + * Constructs yea based on given string, with sign set to Sign::None + * + * Resulting year can be invalid but the string is always preserved. + */ + explicit KDbYear(const QByteArray &string) : KDbYear(Sign::None, string) + { + } + + /** + * Contructs a null year + */ + KDbYear() : KDbYear(QByteArray()) + { + } + + bool operator==(const KDbYear &other) const; + + inline bool operator!=(const KDbYear &other) const { return !operator==(other); } + + bool operator<(const KDbYear &other) const; + + inline bool operator<=(const KDbYear &other) const { return operator<(other) || operator==(other); } + + inline bool operator>=(const KDbYear &other) const { return !operator<(other); } + + inline bool operator>(const KDbYear &other) const { return !operator<=(other); } + + /** + * Returns @c true if the year is valid + * + * Year is invalid if it is null or if the supplied string is invalid according to specification. + */ + bool isValid() const; + + /** + * Returns @c true if the year is null + * + * A year is null if its sign is equal to Sign::None and string is empty. + */ + bool isNull() const; + + /** + * Returns the sign which is used to annotate year + */ + Sign sign() const { return m_sign; } + + QByteArray signString() const; + + /** + * Returns the string representation of year value even if it is invalid + */ + QByteArray yearString() const { return m_string; } + + /** + * Returns entire year value (with sign) converted to string even if it is invalid + */ + QByteArray toString() const; + + /** + * Returns the integer year value as defined by extended ISO 8601 + * + * 0 is returned for invalid year. + */ + int toIsoValue() const; + + /** + * Returns the integer year value as defined by the QDate API + * + * 0 is returned for invalid year. + */ + int toQDateValue() const; + +private: + std::tuple intValue() const; + + const Sign m_sign; + const QByteArray m_string; + int m_isoValue = -1; //!< Cached value for intValue(), -1 if the cache is missing +}; + +Q_DECLARE_METATYPE(KDbYear) + +//! Sends information about value @a sign to debug output @a dbg. +KDB_EXPORT QDebug operator<<(QDebug dbg, KDbYear::Sign sign); + +//! Sends information about value @a year to debug output @a dbg. +KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbYear &year); + +/** + * Generic date constant + * + * @since 3.1.1 + */ +class KDB_EXPORT KDbDate +{ +public: + KDbDate(const KDbYear &year, const QByteArray &monthString, const QByteArray &dayString) + : m_year(year), m_monthString(monthString), m_dayString(dayString) + { + } + + /** + * Constructs a null date + */ + KDbDate() = default; + + bool operator==(const KDbDate &other) const; + + inline bool operator!=(const KDbDate &other) const { return !operator==(other); } + + bool operator<(const KDbDate &other) const; + + inline bool operator<=(const KDbDate &other) const { return operator<(other) || operator==(other); } + + inline bool operator>=(const KDbDate &other) const { return !operator<(other); } + + inline bool operator>(const KDbDate &other) const { return !operator<=(other); } + + /** + * Returns @c true if the date is valid + * + * Validation is performed by converting to QDate (toQDate()) and checking if it is valid. + */ + bool isValid() const; + + /** + * Returns @c true if the date is null + * + * A date is null if its year is null, month string is empty and date string is empty. + */ + bool isNull() const; + + /** + * Returns the date converted to QDate value + * + * Invalid QDate is returned if the KDbDate is invalid. + */ + QDate toQDate() const; + + /** + * Returns the date value converted to string even if it is invalid + * + * For null dates empty string is returned. + */ + QByteArray toString() const; + + /** + * Returns the year part of the date + */ + KDbYear year() const { return m_year; } + + /** + * Returns the month part of the date converted to integer + * + * Correct values are in range 1..12, -1 is returned for invalid month. + */ + int month() const; + + /** + * Returns the month part of the date + * + * It is original string passed to the KDbDate object and may be invalid. + */ + QByteArray monthString() const { return m_monthString; } + + /** + * Returns the day part of the date converted to integer + * + * Correct values are in range 1..31, -1 is returned for invalid day. + * THe date can be still invalid for valid integer, e.g. February 31st. + */ + int day() const; + + /** + * Returns the day part of the date + * + * It is original string passed to the KDbDate object and may be invalid. + */ + QByteArray dayString() const { return m_dayString; } + +private: + const KDbYear m_year; + const QByteArray m_monthString; + const QByteArray m_dayString; +}; + +Q_DECLARE_METATYPE(KDbDate) + +//! Sends information about value @a date to debug output @a dbg. +KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbDate &date); + +/** + * Generic time constant + * + * @since 3.1.1 + */ +class KDB_EXPORT KDbTime +{ +public: + /** + * Specifies hour period + */ + enum class Period { + None, //!< 2-hour time + Am, //!< AM, before noon + Pm //!< PM, after noon, before midnight + }; + + KDbTime(const QByteArray &hourString, const QByteArray &minuteString, + const QByteArray &secondString = QByteArray(), + const QByteArray &msecString = QByteArray(), Period period = Period::None) + : m_hourString(hourString) + , m_minuteString(minuteString) + , m_secondString(secondString) + , m_msecString(msecString) + , m_period(period) + { + } + + /** + * Constructs a null time + */ + KDbTime() = default; + + bool operator==(const KDbTime &other) const; + + inline bool operator!=(const KDbTime &other) const { return !operator==(other); } + + bool operator<(const KDbTime &other) const; + + inline bool operator<=(const KDbTime &other) const { return operator<(other) || operator==(other); } + + inline bool operator>=(const KDbTime &other) const { return !operator<(other); } + + inline bool operator>(const KDbTime &other) const { return !operator<=(other); } + + /** + * Returns @c true if the time is valid + * + * Validation is performed by converting to QTime (toQTime()) and checking if it is valid. + */ + bool isValid() const; + + /** + * Returns @c true if the time is null + * + * A time is null if its hour string or minute string is empty. + */ + bool isNull() const; + + /** + * Returns the time value converted to QTime type + * + * Invalid QTime is returned if the KDbTime is invalid. + */ + QTime toQTime() const; + + /** + * Returns the time value converted to string even if it is invalid + * + * For null times empty string is returned. + */ + QByteArray toString() const; + + /** + * Returns the hour part of the time converted to integer + * + * Correct values are in range 0..23 for None period, and 1..12 for Am and Pm periods. + * -1 is returned for invalid hour. + */ + int hour() const; + + /** + * Returns the hour part of the date + * + * It is original string passed to the KDbTime object and may be invalid. + */ + QByteArray hourString() const { return m_hourString; } + + /** + * Returns the minute part of the time converted to integer + * + * Correct values are in range 0..59. -1 is returned for invalid minute. + */ + int minute() const; + + /** + * Returns the minute part of the date + * + * It is original string passed to the KDbTime object and may be invalid. + */ + QByteArray minuteString() const { return m_minuteString; } + + /** + * Returns the second part of the time converted to integer + * + * Correct values are in range 0..59. -1 is returned for invalid second. + */ + int second() const; + + /** + * Returns the second part of the date + * + * It is original string passed to the KDbTime object and may be invalid. + */ + QByteArray secondString() const { return m_secondString; } + + int msec() const; + + /** + * Returns the milliseconds part of the date + * + * It is original string passed to the KDbTime object and may be invalid. + */ + QByteArray msecString() const { return m_msecString; } + + /** + * Specifies hour period + */ + Period period() const { return m_period; } + +private: + const QByteArray m_hourString; + const QByteArray m_minuteString; + const QByteArray m_secondString; + const QByteArray m_msecString; + const Period m_period = Period::None; +}; + +Q_DECLARE_METATYPE(KDbTime) + +//! Sends information about value @a time to debug output @a dbg. +KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbTime &time); + +/** + * Generic date/time constant + * + * @since 3.1.1 + */ +class KDB_EXPORT KDbDateTime +{ +public: + KDbDateTime(const KDbDate &date, const KDbTime &time) : m_date(date), m_time(time) + { + } + + /** + * Constructs a null date/time + */ + KDbDateTime() = default; + + bool operator==(const KDbDateTime &other) const; + + inline bool operator!=(const KDbDateTime &other) const { return !operator==(other); } + + bool operator<(const KDbDateTime &other) const; + + inline bool operator<=(const KDbDateTime &other) const { return operator<(other) || operator==(other); } + + inline bool operator>=(const KDbDateTime &other) const { return !operator<(other); } + + inline bool operator>(const KDbDateTime &other) const { return !operator<=(other); } + + /** + * Returns @c true if the date/time is valid + * + * Validation is performed by converting to QDateTime (toQDateTime()) and checking if it is valid. + */ + bool isValid() const; + + /** + * Returns @c true if the date/time is null + * + * A time is null if its date or time is null. + */ + bool isNull() const; + + /** + * Returns the date/time converted to QDateTime value + * + * Invalid QDateTime is returned if the KDbDateTime is invalid. + */ + QDateTime toQDateTime() const; + + /** + * Returns the date/time value converted to string even if it is invalid + * + * For null date/times empty string is returned. + */ + QByteArray toString() const; + + /** + * Returns the date part of the date/time + */ + KDbDate date() const { return m_date; } + + /** + * Returns the time part of the date/time + */ + KDbTime time() const { return m_time; } + +private: + const KDbDate m_date; + const KDbTime m_time; +}; + +Q_DECLARE_METATYPE(KDbDateTime) + +//! Sends information about value @a dateTime to debug output @a dbg. +KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbDateTime &dateTime); + +#endif