pax_global_header00006660000000000000000000000064147367505500014526gustar00rootroot0000000000000052 comment=5b10c3d435d3f92ccc2f05b69ff10516ef3154e0 pgagent-pgagent-4.2.3/000077500000000000000000000000001473675055000146045ustar00rootroot00000000000000pgagent-pgagent-4.2.3/.gitignore000066400000000000000000000006261473675055000166000ustar00rootroot00000000000000.idea CMakeCache.txt CMakeFiles/ CPackConfig.cmake CPackSourceConfig.cmake /Makefile cmake_install.cmake install_manifest.txt # Ignore 'pgagent' & 'pgagent.control' in root directory only. /pgagent !/*/pgagent /pgagent.control !/*/pgagent.control *.log # Ignore '*.sql' files in root directory only. /*.sql !/*/*.sql /results/init.out /results/job.out /regression.diffs /regression.out /pgagent.out .cmake pgagent-pgagent-4.2.3/CMakeLists.txt000077500000000000000000000212041473675055000173460ustar00rootroot00000000000000####################################################################### # # pgAgent - PostgreSQL tools # Copyright (C) 2002 - 2021, The pgAdmin Development Team # This software is released under the PostgreSQL Licence # # CMakeLists.txt - CMake build configuration # ####################################################################### ################################################################################ # Initial setup ################################################################################ CMAKE_MINIMUM_REQUIRED(VERSION 3.0) IF(COMMAND cmake_policy) CMAKE_POLICY(SET CMP0003 NEW) ENDIF(COMMAND cmake_policy) SET(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) SET(CMAKE_FIND_LIBRARY_PREFIXES "") SET(CMAKE_FIND_LIBRARY_SUFFIXES ".lib") SET(STATIC_BUILD NO CACHE BOOL "Statically link the executable?") SET(BOOST_MULTITHREADED_BUILD YES CACHE BOOL "Build multithreaded executable?") SET(BOOST_STATIC_BUILD NO CACHE BOOL "Statically link the executable?") ################################################################################ # Apple stuff ################################################################################ IF(APPLE) # Setup default values IF(NOT HAVE_CACHED_VALUES) IF(EXISTS ${CMAKE_OSX_SYSROOT}) SET(CMAKE_OSX_SYSROOT ${CMAKE_OSX_SYSROOT} CACHE FILEPATH "isysroot used for universal binary support" FORCE) ENDIF(EXISTS ${CMAKE_OSX_SYSROOT}) IF(NOT "${CMAKE_OSX_ARCHITECTURES}" STREQUAL "") SET(CMAKE_OSX_ARCHITECTURES ${CMAKE_OSX_ARCHITECTURES} CACHE STRING "Build architectures for OSX" FORCE) ELSE() SET(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE STRING "Build architectures for OSX" FORCE) ENDIF(NOT ${CMAKE_OSX_ARCHITECTURES} STREQUAL "") ENDIF(NOT HAVE_CACHED_VALUES) # Target Tiger SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.9") ENDIF(APPLE) ################################################################################ # Project config ################################################################################ PROJECT(pgagent) # If changing the version number, remember to change here and under the CPack # settings in this file, as well as the definition for pgagent_schema_version() # in pgagent.sql and upgrade_pgagent.sql if the major version number is # changed. The full version number also needs to be included in pgAgent.rc and # pgaevent/pgamsgevent.rc at present. SET(VERSION "4.2.3") # CPack stuff SET(CPACK_PACKAGE_VERSION_MAJOR 4) SET(CPACK_PACKAGE_VERSION_MINOR 2) SET(CPACK_PACKAGE_VERSION_PATCH 2) SET(CPACK_PACKAGE_NAME "pgAgent") SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "pgAgent is a job scheduling engine for PostgreSQL") SET(CPACK_PACKAGE_VENDOR "the pgAdmin Development Team") IF(WIN32) SET(CPACK_GENERATOR ZIP) ELSE(WIN32) SET(CPACK_GENERATOR TGZ) ENDIF(WIN32) SET(CPACK_SOURCE_GENERATOR TGZ) SET(CPACK_SOURCE_IGNORE_FILES "\\\\.DS_Store;/CVS/;/\\\\.svn/;\\\\.swp$;\\\\.#;/#;.*~;cscope.*") ADD_DEFINITIONS(-DPGAGENT_VERSION_MAJOR=${CPACK_PACKAGE_VERSION_MAJOR}) ADD_DEFINITIONS(-DPGAGENT_VERSION="${VERSION}") IF(WIN32) SET(BOOST_WIN_VERSION 0x0501) ADD_DEFINITIONS(-D_WIN32_WINNT=${BOOST_WIN_VERSION} -D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS) ENDIF(WIN32) # This must come after we set the CPACK variables!! INCLUDE(CPack) ################################################################################ # Find PostgreSQL ################################################################################ SET(PG_STATIC ${STATIC_BUILD}) FIND_PACKAGE(PG REQUIRED) INCLUDE_DIRECTORIES(${PG_INCLUDE_DIRS}) LINK_DIRECTORIES(${PG_LIBRARY_DIRS}) ################################################################################ # Find Boost Libraries ################################################################################ SET (Boost_FIND_REQUIRED TRUE) SET (Boost_FIND_QUIETLY FALSE) SET (Boost_DEBUG FALSE) SET (Boost_USE_MULTITHREADED ${BOOST_MULTITHREADED_BUILD}) SET (Boost_USE_STATIC_LIBS ${BOOST_STATIC_BUILD}) FIND_PACKAGE(Boost COMPONENTS filesystem regex date_time thread system) if(Boost_FOUND) INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIRS}) LINK_DIRECTORIES(${Boost_LIBRARY_DIRS}) ELSE() MESSAGE(FATAL_ERROR "Boost library not found.") endif() ################################################################################ # Let's rock! ################################################################################ INCLUDE_DIRECTORIES(${pgagent_SOURCE_DIR} ${pgagent_SOURCE_DIR}/include) FILE(GLOB _cpp_files *.cpp) FILE(GLOB _h_files include/*.h) SET(_srcs ${_cpp_files} ${_h_files}) IF(WIN32) SET(_srcs ${_srcs} pgagent.rc) ENDIF(WIN32) ADD_EXECUTABLE(pgagent ${_srcs}) IF(UNIX AND NOT APPLE) TARGET_LINK_LIBRARIES( pgagent ${PG_LIBRARIES} ${Boost_LIBRARIES} -pthread ) ELSE() TARGET_LINK_LIBRARIES( pgagent ${PG_LIBRARIES} ${Boost_LIBRARIES} ) ENDIF() # Installation IF (WIN32) INSTALL(TARGETS pgagent DESTINATION .) INSTALL(FILES ${pgagent_SOURCE_DIR}/sql/pgagent.sql DESTINATION .) ELSE(WIN32) INSTALL(TARGETS pgagent DESTINATION bin) INSTALL(FILES ${pgagent_SOURCE_DIR}/sql/pgagent.sql DESTINATION share) ENDIF(WIN32) INSTALL(FILES ${pgagent_SOURCE_DIR}/README DESTINATION .) INSTALL(FILES ${pgagent_SOURCE_DIR}/LICENSE DESTINATION .) # Extension IF(PG_EXTENSION) ADD_CUSTOM_COMMAND( OUTPUT ${CMAKE_BINARY_DIR}/pgagent--${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.sql pgagent.control COMMAND ${CMAKE_COMMAND} -D MAJOR_VERSION=${CPACK_PACKAGE_VERSION_MAJOR} -D MINOR_VERSION=${CPACK_PACKAGE_VERSION_MINOR} -DPGAGENT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/MakeExt.cmake MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/sql/pgagent.sql ) ADD_CUSTOM_TARGET(run ALL DEPENDS ${CMAKE_BINARY_DIR}/pgagent--${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.sql ${CMAKE_BINARY_DIR}/pgagent.control) INSTALL(FILES ${CMAKE_BINARY_DIR}/pgagent--${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.sql DESTINATION ${PG_SHARE_DIR}/extension) FILE(GLOB SQL "${CMAKE_CURRENT_SOURCE_DIR}/sql/*--*.sql") FILE(GLOB CONTROL "${CMAKE_CURRENT_SOURCE_DIR}/*.control") INSTALL(FILES ${CMAKE_BINARY_DIR}/pgagent.control ${CONTROL} ${SQL} DESTINATION ${PG_SHARE_DIR}/extension) ENDIF(PG_EXTENSION) ################################################################################ # pgaevent ################################################################################ IF(WIN32) ADD_SUBDIRECTORY(pgaevent) ENDIF(WIN32) ################################################################################ # Build summary ################################################################################ MESSAGE(STATUS " ") MESSAGE(STATUS "================================================================================") MESSAGE(STATUS "Configuration summary:") MESSAGE(STATUS " ") MESSAGE(STATUS " Project : ${PROJECT_NAME}") MESSAGE(STATUS " Description : ${CPACK_PACKAGE_DESCRIPTION_SUMMARY}") MESSAGE(STATUS " Version : ${VERSION}") MESSAGE(STATUS " ") MESSAGE(STATUS " PostgreSQL version string : ${PG_VERSION_STRING}") IF(${PG_MAJOR_VERSION} GREATER 9) MESSAGE(STATUS " PostgreSQL version parts : ${PG_MAJOR_VERSION}") ELSE() MESSAGE(STATUS " PostgreSQL version parts : ${PG_MAJOR_VERSION}.${PG_MINOR_VERSION}") ENDIF(${PG_MAJOR_VERSION} GREATER 9) MESSAGE(STATUS " PostgreSQL path : ${PG_ROOT_DIR}") MESSAGE(STATUS " PostgreSQL config binary : ${PG_CONFIG_PATH}") MESSAGE(STATUS " PostgreSQL include path : ${PG_INCLUDE_DIRS}") MESSAGE(STATUS " PostgreSQL library path : ${PG_LIBRARY_DIRS}") MESSAGE(STATUS " PostgreSQL share path : ${PG_SHARE_DIR}") MESSAGE(STATUS " ") MESSAGE(STATUS " Boost version : ${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}") MESSAGE(STATUS " Boost path : ${Boost_INCLUDE_DIRS}") MESSAGE(STATUS " Boost include directory : ${Boost_INCLUDE_DIRS}") MESSAGE(STATUS " Boost library directory : ${Boost_LIBRARY_DIRS}") MESSAGE(STATUS " Boost Static linking : ${Boost_USE_STATIC_LIBS}") MESSAGE(STATUS "================================================================================") MESSAGE(STATUS " ") ################################################################################ # Give ourselves a hint that we have cached values - must be last! ################################################################################ IF(NOT HAVE_CACHED_VALUES) SET(HAVE_CACHED_VALUES 1 CACHE INTERNAL "Flag to indicate that we have cached values") ENDIF(NOT HAVE_CACHED_VALUES) pgagent-pgagent-4.2.3/LICENSE000066400000000000000000000020231473675055000156060ustar00rootroot00000000000000pgAgent Copyright (C) 2002 - 2024, The pgAdmin Development Team Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. IN NO EVENT SHALL THE PGADMIN DEVELOPMENT TEAM BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE PGADMIN DEVELOPMENT TEAM HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THE PGADMIN DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE PGADMIN DEVELOPMENT TEAM HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. pgagent-pgagent-4.2.3/README000066400000000000000000000040761473675055000154730ustar00rootroot00000000000000pgAgent ======= This document describes the compilation of pgAgent, a job scheduler for PostgreSQL. pgAgent is managed using pgAdmin (http://www.pgadmin.org). The pgAdmin documentation contains details of the setup and use of pgAgent with your PostgreSQL system. The latest build of the documentation can be found at https://www.pgadmin.org/docs/pgadmin4/development/pgagent.html. Building pgAgent ---------------- You will need: - A C/C++ compiler, such as GCC or Microsoft Visual C++ on Windows. - CMake 3.3 (from www.cmake.org) - A Boost library 1.41 or higher installation - A PostgreSQL 8.3 or higher installation 1) Unpack the pgAgent source code 2) Create a build directory in which the code will be built. 3) Run ccmake from the build directory (on Windows, use the CMake graphical interface). By default, ccmake will generate Unix Makefiles - consult the documentation if you wish to generate other types of output: $ ccmake /path/to/pgagent 4) If required, press 'c' to generate a default configuration: CMAKE_BUILD_TYPE Release CMAKE_INSTALL_PREFIX /usr/local CMAKE_OSX_ARCHITECTURES x86_64 CMAKE_OSX_SYSROOT /Developer/SDKs/MacOSX10.10.sdk PostgreSQL_CONFIG_EXECUTABLE /usr/local/pgsql/bin/pg_config Boost_FIND_REQUIRED ON Boost_FIND_QUIETLY OFF Boost_DEBUG OFF Boost_USE_MULTITHREADED ON Boost_USE_STATIC_LIBS OFF 5) Use the ccmake interface to adjust any settings as required. When configured as required, press 'c' to re-configure (if required) and 'g' to generate the build files and exit. 6) Run 'make' to build pgAgent on Mac or Unix, or open the generated project files in VC++ on Windows and build the solution in the desired configuration. Running Regression Tests ======================== To run the regression tests, use a command such as: make USE_PGXS=1 -f test/Makefile installcheck You will need to ensure that the appropriate pg_config executable is in the path and that variables such as PGPORT and PGUSER are set if required. pgagent-pgagent-4.2.3/cmake/000077500000000000000000000000001473675055000156645ustar00rootroot00000000000000pgagent-pgagent-4.2.3/cmake/FindPG.cmake000066400000000000000000000121541473675055000200000ustar00rootroot00000000000000####################################################################### # # FindPg.cmake - A CMake module for locating PostgreSQL # # Dave Page, EnterpriseDB UK Ltd. # This code is released under the PostgreSQL Licence # ####################################################################### # To use this module, simply include it in your CMake project. # If set, PostgreSQL will be assumed to be in the location specified # by the PGDIR environment variable. Otherwise, it will be searched # for in a number of standard locations. # # For statically linked builds, the PG_STATIC variable can be set to # true. # # The following CMake variable will be set: # # PG_FOUND - Set to TRUE if PostgreSQL is located # PG_CONFIG_PATH - The pg_config executable path # PG_ROOT_DIR - The base install directory for PostgreSQL # PG_INCLUDE_DIRS - The directory containing the PostgreSQL headers. # PG_LIBRARIES - The PostgreSQL client libraries. # PG_LIBRARY_DIRS - The directory containing the PostgreSQL client libraries. # PG_PKG_LIBRARY_DIRS - The directory containing the PostgreSQL package libraries. # PG_SHARE_DIR - The directory containing architecture-independent PostgreSQL support files. # PG_VERSION_STRING - The PostgreSQL version number. # PG_MAJOR_VERSION - The PostgreSQL major version (x in x.y.z). # PG_MINOR_VERSION - The PostgreSQL minor version (y in x.y.z). # PG_EXTENSION - Set to TRUE if PostgreSQL supports extensions. IF(NOT PG_STATIC OR PG_STATIC STREQUAL "") SET(_static "no") ELSE(NOT PG_STATIC OR PG_STATIC STREQUAL "") IF(PG_STATIC) SET(_static "yes") ELSE(PG_STATIC) SET(_static "no") ENDIF(PG_STATIC) ENDIF(NOT PG_STATIC OR PG_STATIC STREQUAL "") IF(NOT $ENV{PGDIR} STREQUAL "") FIND_PROGRAM(PG_CONFIG_PATH pg_config PATH $ENV{PGDIR}/bin DOC "Path to the pg_config executable" NO_DEFAULT_PATH) ELSE(NOT $ENV{PGDIR} STREQUAL "") FIND_PROGRAM(PG_CONFIG_PATH pg_config PATH /usr/local/pgsql/bin /opt/PostgreSQL/*/bin /Library/PostgreSQL/*/bin $ENV{ProgramFiles}/PostgreSQL/*/bin $ENV{SystemDrive}/PostgreSQL/*/bin DOC "Path to the pg_config executable") ENDIF(NOT $ENV{PGDIR} STREQUAL "") EXEC_PROGRAM(${PG_CONFIG_PATH} ARGS --version OUTPUT_VARIABLE PG_VERSION_STRING RETURN_VALUE _retval) IF(NOT _retval) SET(PG_FOUND TRUE) # Strip the bin and pg_config from the path GET_FILENAME_COMPONENT(PG_ROOT_DIR ${PG_CONFIG_PATH} PATH) GET_FILENAME_COMPONENT(PG_ROOT_DIR ${PG_ROOT_DIR} PATH) # Split the version into its component parts. STRING(REGEX MATCHALL "[0-9]+" PG_VERSION_PARTS "${PG_VERSION_STRING}") LIST(GET PG_VERSION_PARTS 0 PG_MAJOR_VERSION) IF((PG_MAJOR_VERSION LESS 10)) LIST(GET PG_VERSION_PARTS 1 PG_MINOR_VERSION) ENDIF((PG_MAJOR_VERSION LESS 10)) # Are extensions supported? IF((PG_MAJOR_VERSION GREATER 9) OR ((PG_MAJOR_VERSION EQUAL 9) AND (PG_MINOR_VERSION GREATER 0))) SET(PG_EXTENSION TRUE) ENDIF((PG_MAJOR_VERSION GREATER 9) OR ((PG_MAJOR_VERSION EQUAL 9) AND (PG_MINOR_VERSION GREATER 0))) IF(WIN32 AND NOT CYGWIN AND NOT MSYS) SET(PG_INCLUDE_DIRS "${PG_ROOT_DIR}/include") SET(PG_LIBRARY_DIRS "${PG_ROOT_DIR}/lib") SET(PG_PKG_LIBRARY_DIRS "${PG_ROOT_DIR}/lib") SET(PG_SHARE_DIR "${PG_ROOT_DIR}/share") # There iare no static libraries on VC++ builds of PG. LIST(APPEND PG_LIBRARIES libpq.lib) ELSE(WIN32 AND NOT CYGWIN AND NOT MSYS) EXEC_PROGRAM(${PG_CONFIG_PATH} ARGS --includedir OUTPUT_VARIABLE PG_INCLUDE_DIRS) EXEC_PROGRAM(${PG_CONFIG_PATH} ARGS --libdir OUTPUT_VARIABLE PG_LIBRARY_DIRS) EXEC_PROGRAM(${PG_CONFIG_PATH} ARGS --pkglibdir OUTPUT_VARIABLE PG_PKG_LIBRARY_DIRS) EXEC_PROGRAM(${PG_CONFIG_PATH} ARGS --sharedir OUTPUT_VARIABLE PG_SHARE_DIR) IF(_static) LIST(APPEND PG_LIBRARIES ${PG_LIBRARY_DIRS}/libpq.a) # Check for SSL and Kerberos EXEC_PROGRAM("nm" ARGS ${PG_LIBRARY_DIRS}/libpq.a OUTPUT_VARIABLE _op) IF(_op MATCHES "SSL_connect") LIST(APPEND PG_LIBRARIES "ssl") ENDIF(_op MATCHES "SSL_connect") IF(_op MATCHES "krb5_free_principal") LIST(APPEND PG_LIBRARIES "krb5") ENDIF(_op MATCHES "krb5_free_principal") IF(_op MATCHES "ldap_init") LIST(APPEND PG_LIBRARIES "ldap") ENDIF(_op MATCHES "ldap_init") LIST(APPEND PG_LIBRARIES "crypto") IF(NOT APPLE) LIST(APPEND PG_LIBRARIES "crypt") ENDIF(NOT APPLE) ELSE(_static) LIST(APPEND PG_LIBRARIES pq) ENDIF(_static) ENDIF(WIN32 AND NOT CYGWIN AND NOT MSYS) ELSE(NOT _retval) SET(PG_FOUND FALSE) SET(PG_ROOT_DIR PG_ROOT_DIR-NO_FOUND) IF(PG_FIND_REQUIRED) MESSAGE(FATAL_ERROR "No PostgreSQL installation could be found.") ELSE(PG_FIND_REQUIRED) MESSAGE(STATUS "No PostgreSQL installation could be found.") ENDIF(PG_FIND_REQUIRED) ENDIF(NOT _retval) pgagent-pgagent-4.2.3/cmake/FindPackageHandleStandardArgs.cmake000066400000000000000000000554471473675055000244530ustar00rootroot00000000000000# Distributed under the OSI-approved BSD 3-Clause License. See accompanying # file Copyright.txt or https://cmake.org/licensing for details. #[=======================================================================[.rst: FindPackageHandleStandardArgs ----------------------------- This module provides functions intended to be used in :ref:`Find Modules` implementing :command:`find_package()` calls. .. command:: find_package_handle_standard_args This command handles the ``REQUIRED``, ``QUIET`` and version-related arguments of :command:`find_package`. It also sets the ``_FOUND`` variable. The package is considered found if all variables listed contain valid results, e.g. valid filepaths. There are two signatures: .. code-block:: cmake find_package_handle_standard_args( (DEFAULT_MSG|) ... ) find_package_handle_standard_args( [FOUND_VAR ] [REQUIRED_VARS ...] [VERSION_VAR ] [HANDLE_VERSION_RANGE] [HANDLE_COMPONENTS] [CONFIG_MODE] [NAME_MISMATCHED] [REASON_FAILURE_MESSAGE ] [FAIL_MESSAGE ] ) The ``_FOUND`` variable will be set to ``TRUE`` if all the variables ``...`` are valid and any optional constraints are satisfied, and ``FALSE`` otherwise. A success or failure message may be displayed based on the results and on whether the ``REQUIRED`` and/or ``QUIET`` option was given to the :command:`find_package` call. The options are: ``(DEFAULT_MSG|)`` In the simple signature this specifies the failure message. Use ``DEFAULT_MSG`` to ask for a default message to be computed (recommended). Not valid in the full signature. ``FOUND_VAR `` .. deprecated:: 3.3 Specifies either ``_FOUND`` or ``_FOUND`` as the result variable. This exists only for compatibility with older versions of CMake and is now ignored. Result variables of both names are always set for compatibility. ``REQUIRED_VARS ...`` Specify the variables which are required for this package. These may be named in the generated failure message asking the user to set the missing variable values. Therefore these should typically be cache entries such as ``FOO_LIBRARY`` and not output variables like ``FOO_LIBRARIES``. .. versionchanged:: 3.18 If ``HANDLE_COMPONENTS`` is specified, this option can be omitted. ``VERSION_VAR `` Specify the name of a variable that holds the version of the package that has been found. This version will be checked against the (potentially) specified required version given to the :command:`find_package` call, including its ``EXACT`` option. The default messages include information about the required version and the version which has been actually found, both if the version is ok or not. ``HANDLE_VERSION_RANGE`` .. versionadded:: 3.19 Enable handling of a version range, if one is specified. Without this option, a developer warning will be displayed if a version range is specified. ``HANDLE_COMPONENTS`` Enable handling of package components. In this case, the command will report which components have been found and which are missing, and the ``_FOUND`` variable will be set to ``FALSE`` if any of the required components (i.e. not the ones listed after the ``OPTIONAL_COMPONENTS`` option of :command:`find_package`) are missing. ``CONFIG_MODE`` Specify that the calling find module is a wrapper around a call to ``find_package( NO_MODULE)``. This implies a ``VERSION_VAR`` value of ``_VERSION``. The command will automatically check whether the package configuration file was found. ``REASON_FAILURE_MESSAGE `` .. versionadded:: 3.16 Specify a custom message of the reason for the failure which will be appended to the default generated message. ``FAIL_MESSAGE `` Specify a custom failure message instead of using the default generated message. Not recommended. ``NAME_MISMATCHED`` .. versionadded:: 3.17 Indicate that the ```` does not match ``${CMAKE_FIND_PACKAGE_NAME}``. This is usually a mistake and raises a warning, but it may be intentional for usage of the command for components of a larger package. Example for the simple signature: .. code-block:: cmake find_package_handle_standard_args(LibXml2 DEFAULT_MSG LIBXML2_LIBRARY LIBXML2_INCLUDE_DIR) The ``LibXml2`` package is considered to be found if both ``LIBXML2_LIBRARY`` and ``LIBXML2_INCLUDE_DIR`` are valid. Then also ``LibXml2_FOUND`` is set to ``TRUE``. If it is not found and ``REQUIRED`` was used, it fails with a :command:`message(FATAL_ERROR)`, independent whether ``QUIET`` was used or not. If it is found, success will be reported, including the content of the first ````. On repeated CMake runs, the same message will not be printed again. .. note:: If ```` does not match ``CMAKE_FIND_PACKAGE_NAME`` for the calling module, a warning that there is a mismatch is given. The ``FPHSA_NAME_MISMATCHED`` variable may be set to bypass the warning if using the old signature and the ``NAME_MISMATCHED`` argument using the new signature. To avoid forcing the caller to require newer versions of CMake for usage, the variable's value will be used if defined when the ``NAME_MISMATCHED`` argument is not passed for the new signature (but using both is an error).. Example for the full signature: .. code-block:: cmake find_package_handle_standard_args(LibArchive REQUIRED_VARS LibArchive_LIBRARY LibArchive_INCLUDE_DIR VERSION_VAR LibArchive_VERSION) In this case, the ``LibArchive`` package is considered to be found if both ``LibArchive_LIBRARY`` and ``LibArchive_INCLUDE_DIR`` are valid. Also the version of ``LibArchive`` will be checked by using the version contained in ``LibArchive_VERSION``. Since no ``FAIL_MESSAGE`` is given, the default messages will be printed. Another example for the full signature: .. code-block:: cmake find_package(Automoc4 QUIET NO_MODULE HINTS /opt/automoc4) find_package_handle_standard_args(Automoc4 CONFIG_MODE) In this case, a ``FindAutmoc4.cmake`` module wraps a call to ``find_package(Automoc4 NO_MODULE)`` and adds an additional search directory for ``automoc4``. Then the call to ``find_package_handle_standard_args`` produces a proper success/failure message. .. command:: find_package_check_version .. versionadded:: 3.19 Helper function which can be used to check if a ```` is valid against version-related arguments of :command:`find_package`. .. code-block:: cmake find_package_check_version( [HANDLE_VERSION_RANGE] [RESULT_MESSAGE_VARIABLE ] ) The ```` will hold a boolean value giving the result of the check. The options are: ``HANDLE_VERSION_RANGE`` Enable handling of a version range, if one is specified. Without this option, a developer warning will be displayed if a version range is specified. ``RESULT_MESSAGE_VARIABLE `` Specify a variable to get back a message describing the result of the check. Example for the usage: .. code-block:: cmake find_package_check_version(1.2.3 result HANDLE_VERSION_RANGE RESULT_MESSAGE_VARIABLE reason) if (result) message (STATUS "${reason}") else() message (FATAL_ERROR "${reason}") endif() #]=======================================================================] include(${CMAKE_CURRENT_LIST_DIR}/FindPackageMessage.cmake) cmake_policy(PUSH) # numbers and boolean constants cmake_policy (SET CMP0012 NEW) # IN_LIST operator cmake_policy (SET CMP0057 NEW) # internal helper macro macro(_FPHSA_FAILURE_MESSAGE _msg) set (__msg "${_msg}") if (FPHSA_REASON_FAILURE_MESSAGE) string(APPEND __msg "\n Reason given by package: ${FPHSA_REASON_FAILURE_MESSAGE}\n") elseif(NOT DEFINED PROJECT_NAME) string(APPEND __msg "\n" "Hint: The project() command has not yet been called. It sets up system-specific search paths.") endif() if (${_NAME}_FIND_REQUIRED) message(FATAL_ERROR "${__msg}") else () if (NOT ${_NAME}_FIND_QUIETLY) message(STATUS "${__msg}") endif () endif () endmacro() # internal helper macro to generate the failure message when used in CONFIG_MODE: macro(_FPHSA_HANDLE_FAILURE_CONFIG_MODE) # _CONFIG is set, but FOUND is false, this means that some other of the REQUIRED_VARS was not found: if(${_NAME}_CONFIG) _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE}: missing:${MISSING_VARS} (found ${${_NAME}_CONFIG} ${VERSION_MSG})") else() # If _CONSIDERED_CONFIGS is set, the config-file has been found, but no suitable version. # List them all in the error message: if(${_NAME}_CONSIDERED_CONFIGS) set(configsText "") list(LENGTH ${_NAME}_CONSIDERED_CONFIGS configsCount) math(EXPR configsCount "${configsCount} - 1") foreach(currentConfigIndex RANGE ${configsCount}) list(GET ${_NAME}_CONSIDERED_CONFIGS ${currentConfigIndex} filename) list(GET ${_NAME}_CONSIDERED_VERSIONS ${currentConfigIndex} version) string(APPEND configsText "\n ${filename} (version ${version})") endforeach() if (${_NAME}_NOT_FOUND_MESSAGE) if (FPHSA_REASON_FAILURE_MESSAGE) string(PREPEND FPHSA_REASON_FAILURE_MESSAGE "${${_NAME}_NOT_FOUND_MESSAGE}\n ") else() set(FPHSA_REASON_FAILURE_MESSAGE "${${_NAME}_NOT_FOUND_MESSAGE}") endif() else() string(APPEND configsText "\n") endif() _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE} ${VERSION_MSG}, checked the following files:${configsText}") else() # Simple case: No Config-file was found at all: _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE}: found neither ${_NAME}Config.cmake nor ${_NAME_LOWER}-config.cmake ${VERSION_MSG}") endif() endif() endmacro() function(FIND_PACKAGE_CHECK_VERSION version result) cmake_parse_arguments (PARSE_ARGV 2 FPCV "HANDLE_VERSION_RANGE;NO_AUTHOR_WARNING_VERSION_RANGE" "RESULT_MESSAGE_VARIABLE" "") if (FPCV_UNPARSED_ARGUMENTS) message (FATAL_ERROR "find_package_check_version(): ${FPCV_UNPARSED_ARGUMENTS}: unexpected arguments") endif() if ("RESULT_MESSAGE_VARIABLE" IN_LIST FPCV_KEYWORDS_MISSING_VALUES) message (FATAL_ERROR "find_package_check_version(): RESULT_MESSAGE_VARIABLE expects an argument") endif() set (${result} FALSE PARENT_SCOPE) if (FPCV_RESULT_MESSAGE_VARIABLE) unset (${FPCV_RESULT_MESSAGE_VARIABLE} PARENT_SCOPE) endif() if (_CMAKE_FPHSA_PACKAGE_NAME) set (package "${_CMAKE_FPHSA_PACKAGE_NAME}") elseif (CMAKE_FIND_PACKAGE_NAME) set (package "${CMAKE_FIND_PACKAGE_NAME}") else() message (FATAL_ERROR "find_package_check_version(): Cannot be used outside a 'Find Module'") endif() if (NOT FPCV_NO_AUTHOR_WARNING_VERSION_RANGE AND ${package}_FIND_VERSION_RANGE AND NOT FPCV_HANDLE_VERSION_RANGE) message(AUTHOR_WARNING "`find_package()` specify a version range but the option " "HANDLE_VERSION_RANGE` is not passed to `find_package_check_version()`. " "Only the lower endpoint of the range will be used.") endif() set (version_ok FALSE) unset (version_msg) if (FPCV_HANDLE_VERSION_RANGE AND ${package}_FIND_VERSION_RANGE) if ((${package}_FIND_VERSION_RANGE_MIN STREQUAL "INCLUDE" AND version VERSION_GREATER_EQUAL ${package}_FIND_VERSION_MIN) AND ((${package}_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND version VERSION_LESS_EQUAL ${package}_FIND_VERSION_MAX) OR (${package}_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND version VERSION_LESS ${package}_FIND_VERSION_MAX))) set (version_ok TRUE) set(version_msg "(found suitable version \"${version}\", required range is \"${${package}_FIND_VERSION_RANGE}\")") else() set(version_msg "Found unsuitable version \"${version}\", required range is \"${${package}_FIND_VERSION_RANGE}\"") endif() elseif (DEFINED ${package}_FIND_VERSION) if(${package}_FIND_VERSION_EXACT) # exact version required # count the dots in the version string string(REGEX REPLACE "[^.]" "" version_dots "${version}") # add one dot because there is one dot more than there are components string(LENGTH "${version_dots}." version_dots) if (version_dots GREATER ${package}_FIND_VERSION_COUNT) # Because of the C++ implementation of find_package() ${package}_FIND_VERSION_COUNT # is at most 4 here. Therefore a simple lookup table is used. if (${package}_FIND_VERSION_COUNT EQUAL 1) set(version_regex "[^.]*") elseif (${package}_FIND_VERSION_COUNT EQUAL 2) set(version_regex "[^.]*\\.[^.]*") elseif (${package}_FIND_VERSION_COUNT EQUAL 3) set(version_regex "[^.]*\\.[^.]*\\.[^.]*") else() set(version_regex "[^.]*\\.[^.]*\\.[^.]*\\.[^.]*") endif() string(REGEX REPLACE "^(${version_regex})\\..*" "\\1" version_head "${version}") if (NOT ${package}_FIND_VERSION VERSION_EQUAL version_head) set(version_msg "Found unsuitable version \"${version}\", but required is exact version \"${${package}_FIND_VERSION}\"") else () set(version_ok TRUE) set(version_msg "(found suitable exact version \"${version}\")") endif () else () if (NOT ${package}_FIND_VERSION VERSION_EQUAL version) set(version_msg "Found unsuitable version \"${version}\", but required is exact version \"${${package}_FIND_VERSION}\"") else () set(version_ok TRUE) set(version_msg "(found suitable exact version \"${version}\")") endif () endif () else() # minimum version if (${package}_FIND_VERSION VERSION_GREATER version) set(version_msg "Found unsuitable version \"${version}\", but required is at least \"${${package}_FIND_VERSION}\"") else() set(version_ok TRUE) set(version_msg "(found suitable version \"${version}\", minimum required is \"${${package}_FIND_VERSION}\")") endif() endif() else () set(version_ok TRUE) set(version_msg "(found version \"${version}\")") endif() set (${result} ${version_ok} PARENT_SCOPE) if (FPCV_RESULT_MESSAGE_VARIABLE) set (${FPCV_RESULT_MESSAGE_VARIABLE} "${version_msg}" PARENT_SCOPE) endif() endfunction() function(FIND_PACKAGE_HANDLE_STANDARD_ARGS _NAME _FIRST_ARG) # Set up the arguments for `cmake_parse_arguments`. set(options CONFIG_MODE HANDLE_COMPONENTS NAME_MISMATCHED HANDLE_VERSION_RANGE) set(oneValueArgs FAIL_MESSAGE REASON_FAILURE_MESSAGE VERSION_VAR FOUND_VAR) set(multiValueArgs REQUIRED_VARS) # Check whether we are in 'simple' or 'extended' mode: set(_KEYWORDS_FOR_EXTENDED_MODE ${options} ${oneValueArgs} ${multiValueArgs} ) list(FIND _KEYWORDS_FOR_EXTENDED_MODE "${_FIRST_ARG}" INDEX) unset(FPHSA_NAME_MISMATCHED_override) if (DEFINED FPHSA_NAME_MISMATCHED) # If the variable NAME_MISMATCHED variable is set, error if it is passed as # an argument. The former is for old signatures, the latter is for new # signatures. list(FIND ARGN "NAME_MISMATCHED" name_mismatched_idx) if (NOT name_mismatched_idx EQUAL "-1") message(FATAL_ERROR "The `NAME_MISMATCHED` argument may only be specified by the argument or " "the variable, not both.") endif () # But use the variable if it is not an argument to avoid forcing minimum # CMake version bumps for calling modules. set(FPHSA_NAME_MISMATCHED_override "${FPHSA_NAME_MISMATCHED}") endif () if(${INDEX} EQUAL -1) set(FPHSA_FAIL_MESSAGE ${_FIRST_ARG}) set(FPHSA_REQUIRED_VARS ${ARGN}) set(FPHSA_VERSION_VAR) else() cmake_parse_arguments(FPHSA "${options}" "${oneValueArgs}" "${multiValueArgs}" ${_FIRST_ARG} ${ARGN}) if(FPHSA_UNPARSED_ARGUMENTS) message(FATAL_ERROR "Unknown keywords given to FIND_PACKAGE_HANDLE_STANDARD_ARGS(): \"${FPHSA_UNPARSED_ARGUMENTS}\"") endif() if(NOT FPHSA_FAIL_MESSAGE) set(FPHSA_FAIL_MESSAGE "DEFAULT_MSG") endif() # In config-mode, we rely on the variable _CONFIG, which is set by find_package() # when it successfully found the config-file, including version checking: if(FPHSA_CONFIG_MODE) list(INSERT FPHSA_REQUIRED_VARS 0 ${_NAME}_CONFIG) list(REMOVE_DUPLICATES FPHSA_REQUIRED_VARS) set(FPHSA_VERSION_VAR ${_NAME}_VERSION) endif() if(NOT FPHSA_REQUIRED_VARS AND NOT FPHSA_HANDLE_COMPONENTS) message(FATAL_ERROR "No REQUIRED_VARS specified for FIND_PACKAGE_HANDLE_STANDARD_ARGS()") endif() endif() if (DEFINED FPHSA_NAME_MISMATCHED_override) set(FPHSA_NAME_MISMATCHED "${FPHSA_NAME_MISMATCHED_override}") endif () if (DEFINED CMAKE_FIND_PACKAGE_NAME AND NOT FPHSA_NAME_MISMATCHED AND NOT _NAME STREQUAL CMAKE_FIND_PACKAGE_NAME) message(AUTHOR_WARNING "The package name passed to `find_package_handle_standard_args` " "(${_NAME}) does not match the name of the calling package " "(${CMAKE_FIND_PACKAGE_NAME}). This can lead to problems in calling " "code that expects `find_package` result variables (e.g., `_FOUND`) " "to follow a certain pattern.") endif () if (${_NAME}_FIND_VERSION_RANGE AND NOT FPHSA_HANDLE_VERSION_RANGE) message(AUTHOR_WARNING "`find_package()` specify a version range but the module ${_NAME} does " "not support this capability. Only the lower endpoint of the range " "will be used.") endif() # to propagate package name to FIND_PACKAGE_CHECK_VERSION set(_CMAKE_FPHSA_PACKAGE_NAME "${_NAME}") # now that we collected all arguments, process them if("x${FPHSA_FAIL_MESSAGE}" STREQUAL "xDEFAULT_MSG") set(FPHSA_FAIL_MESSAGE "Could NOT find ${_NAME}") endif() if (FPHSA_REQUIRED_VARS) list(GET FPHSA_REQUIRED_VARS 0 _FIRST_REQUIRED_VAR) endif() string(TOUPPER ${_NAME} _NAME_UPPER) string(TOLOWER ${_NAME} _NAME_LOWER) if(FPHSA_FOUND_VAR) set(_FOUND_VAR_UPPER ${_NAME_UPPER}_FOUND) set(_FOUND_VAR_MIXED ${_NAME}_FOUND) if(FPHSA_FOUND_VAR STREQUAL _FOUND_VAR_MIXED OR FPHSA_FOUND_VAR STREQUAL _FOUND_VAR_UPPER) set(_FOUND_VAR ${FPHSA_FOUND_VAR}) else() message(FATAL_ERROR "The argument for FOUND_VAR is \"${FPHSA_FOUND_VAR}\", but only \"${_FOUND_VAR_MIXED}\" and \"${_FOUND_VAR_UPPER}\" are valid names.") endif() else() set(_FOUND_VAR ${_NAME_UPPER}_FOUND) endif() # collect all variables which were not found, so they can be printed, so the # user knows better what went wrong (#6375) set(MISSING_VARS "") set(DETAILS "") # check if all passed variables are valid set(FPHSA_FOUND_${_NAME} TRUE) foreach(_CURRENT_VAR ${FPHSA_REQUIRED_VARS}) if(NOT ${_CURRENT_VAR}) set(FPHSA_FOUND_${_NAME} FALSE) string(APPEND MISSING_VARS " ${_CURRENT_VAR}") else() string(APPEND DETAILS "[${${_CURRENT_VAR}}]") endif() endforeach() if(FPHSA_FOUND_${_NAME}) set(${_NAME}_FOUND TRUE) set(${_NAME_UPPER}_FOUND TRUE) else() set(${_NAME}_FOUND FALSE) set(${_NAME_UPPER}_FOUND FALSE) endif() # component handling unset(FOUND_COMPONENTS_MSG) unset(MISSING_COMPONENTS_MSG) if(FPHSA_HANDLE_COMPONENTS) foreach(comp ${${_NAME}_FIND_COMPONENTS}) if(${_NAME}_${comp}_FOUND) if(NOT DEFINED FOUND_COMPONENTS_MSG) set(FOUND_COMPONENTS_MSG "found components:") endif() string(APPEND FOUND_COMPONENTS_MSG " ${comp}") else() if(NOT DEFINED MISSING_COMPONENTS_MSG) set(MISSING_COMPONENTS_MSG "missing components:") endif() string(APPEND MISSING_COMPONENTS_MSG " ${comp}") if(${_NAME}_FIND_REQUIRED_${comp}) set(${_NAME}_FOUND FALSE) string(APPEND MISSING_VARS " ${comp}") endif() endif() endforeach() set(COMPONENT_MSG "${FOUND_COMPONENTS_MSG} ${MISSING_COMPONENTS_MSG}") string(APPEND DETAILS "[c${COMPONENT_MSG}]") endif() # version handling: set(VERSION_MSG "") set(VERSION_OK TRUE) # check that the version variable is not empty to avoid emitting a misleading # message (i.e. `Found unsuitable version ""`) if (DEFINED ${_NAME}_FIND_VERSION) if(DEFINED ${FPHSA_VERSION_VAR}) if(NOT "${${FPHSA_VERSION_VAR}}" STREQUAL "") set(_FOUND_VERSION ${${FPHSA_VERSION_VAR}}) if (FPHSA_HANDLE_VERSION_RANGE) set (FPCV_HANDLE_VERSION_RANGE HANDLE_VERSION_RANGE) else() set(FPCV_HANDLE_VERSION_RANGE NO_AUTHOR_WARNING_VERSION_RANGE) endif() find_package_check_version ("${_FOUND_VERSION}" VERSION_OK RESULT_MESSAGE_VARIABLE VERSION_MSG ${FPCV_HANDLE_VERSION_RANGE}) else() set(VERSION_OK FALSE) endif() endif() if("${${FPHSA_VERSION_VAR}}" STREQUAL "") # if the package was not found, but a version was given, add that to the output: if(${_NAME}_FIND_VERSION_EXACT) set(VERSION_MSG "(Required is exact version \"${${_NAME}_FIND_VERSION}\")") elseif (FPHSA_HANDLE_VERSION_RANGE AND ${_NAME}_FIND_VERSION_RANGE) set(VERSION_MSG "(Required is version range \"${${_NAME}_FIND_VERSION_RANGE}\")") else() set(VERSION_MSG "(Required is at least version \"${${_NAME}_FIND_VERSION}\")") endif() endif() else () # Check with DEFINED as the found version may be 0. if(DEFINED ${FPHSA_VERSION_VAR}) set(VERSION_MSG "(found version \"${${FPHSA_VERSION_VAR}}\")") endif() endif () if(VERSION_OK) string(APPEND DETAILS "[v${${FPHSA_VERSION_VAR}}(${${_NAME}_FIND_VERSION})]") else() set(${_NAME}_FOUND FALSE) endif() # print the result: if (${_NAME}_FOUND) FIND_PACKAGE_MESSAGE(${_NAME} "Found ${_NAME}: ${${_FIRST_REQUIRED_VAR}} ${VERSION_MSG} ${COMPONENT_MSG}" "${DETAILS}") else () if(FPHSA_CONFIG_MODE) _FPHSA_HANDLE_FAILURE_CONFIG_MODE() else() if(NOT VERSION_OK) set(RESULT_MSG) if (_FIRST_REQUIRED_VAR) string (APPEND RESULT_MSG "found ${${_FIRST_REQUIRED_VAR}}") endif() if (COMPONENT_MSG) if (RESULT_MSG) string (APPEND RESULT_MSG ", ") endif() string (APPEND RESULT_MSG "${FOUND_COMPONENTS_MSG}") endif() _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE}: ${VERSION_MSG} (${RESULT_MSG})") else() _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE} (missing:${MISSING_VARS}) ${VERSION_MSG}") endif() endif() endif () set(${_NAME}_FOUND ${${_NAME}_FOUND} PARENT_SCOPE) set(${_NAME_UPPER}_FOUND ${${_NAME}_FOUND} PARENT_SCOPE) endfunction() cmake_policy(POP) pgagent-pgagent-4.2.3/cmake/FindPackageMessage.cmake000066400000000000000000000033201473675055000223250ustar00rootroot00000000000000# Distributed under the OSI-approved BSD 3-Clause License. See accompanying # file Copyright.txt or https://cmake.org/licensing for details. #[=======================================================================[.rst: FindPackageMessage ------------------ .. code-block:: cmake find_package_message( "message for user" "find result details") This function is intended to be used in FindXXX.cmake modules files. It will print a message once for each unique find result. This is useful for telling the user where a package was found. The first argument specifies the name (XXX) of the package. The second argument specifies the message to display. The third argument lists details about the find result so that if they change the message will be displayed again. The macro also obeys the QUIET argument to the find_package command. Example: .. code-block:: cmake if(X11_FOUND) find_package_message(X11 "Found X11: ${X11_X11_LIB}" "[${X11_X11_LIB}][${X11_INCLUDE_DIR}]") else() ... endif() #]=======================================================================] function(find_package_message pkg msg details) # Avoid printing a message repeatedly for the same find result. if(NOT ${pkg}_FIND_QUIETLY) string(REPLACE "\n" "" details "${details}") set(DETAILS_VAR FIND_PACKAGE_MESSAGE_DETAILS_${pkg}) if(NOT "${details}" STREQUAL "${${DETAILS_VAR}}") # The message has not yet been printed. string(STRIP "${msg}" msg) message(STATUS "${msg}") # Save the find details in the cache to avoid printing the same # message again. set("${DETAILS_VAR}" "${details}" CACHE INTERNAL "Details about finding ${pkg}") endif() endif() endfunction() pgagent-pgagent-4.2.3/cmake/MakeExt.cmake000066400000000000000000000015501473675055000202250ustar00rootroot00000000000000####################################################################### # # pgAgent - PostgreSQL tools # Copyright (C) 2002 - 2024, The pgAdmin Development Team # This software is released under the PostgreSQL Licence # # MakeExt,cmake - Create the PG Extension # ####################################################################### FILE(READ ${PGAGENT_SOURCE_DIR}/sql/pgagent.sql PGAGENT_SQL) STRING(REPLACE "BEGIN TRANSACTION;" "" PGAGENT_SQL "${PGAGENT_SQL}") STRING(REPLACE "COMMIT TRANSACTION;" "" PGAGENT_SQL "${PGAGENT_SQL}") STRING(REPLACE "CREATE SCHEMA pgagent;" "" PGAGENT_SQL "${PGAGENT_SQL}") STRING(REPLACE "-- EXT SELECT" "SELECT" PGAGENT_SQL "${PGAGENT_SQL}") FILE(WRITE "${CMAKE_BINARY_DIR}/pgagent--${MAJOR_VERSION}.${MINOR_VERSION}.sql" "${PGAGENT_SQL}") CONFIGURE_FILE(${PGAGENT_SOURCE_DIR}/pgagent.control.in ${CMAKE_BINARY_DIR}/pgagent.control) pgagent-pgagent-4.2.3/connection.cpp000066400000000000000000000241061473675055000174520ustar00rootroot00000000000000////////////////////////////////////////////////////////////////////////// // // pgAgent - PostgreSQL Tools // // Copyright (C) 2002 - 2024, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // // connection.cpp - database connection // ////////////////////////////////////////////////////////////////////////// #include "pgAgent.h" #include namespace ip = boost::asio::ip; DBconn *DBconn::ms_primaryConn = NULL; CONNinfo DBconn::ms_basicConnInfo; static boost::mutex s_poolLock; DBconn::DBconn(const std::string &connectString) : m_inUse(false), m_next(NULL), m_prev(NULL), m_minorVersion(0), m_majorVersion(0) { m_connStr = connectString; Connect(connectString); } bool DBconn::Connect(const std::string &connStr) { LogMessage(("Creating DB connection: " + connStr), LOG_DEBUG); m_conn = PQconnectdb(connStr.c_str()); if (PQstatus(m_conn) != CONNECTION_OK) { m_lastError = (const char *)PQerrorMessage(m_conn); PQfinish(m_conn); m_conn = NULL; return false; } return (m_conn != NULL); } DBconn::~DBconn() { // clear a single connection if (m_conn) { PQfinish(m_conn); m_conn = NULL; } } std::string DBconn::qtDbString(const std::string &value) { std::string result = value; boost::replace_all(result, "\\", "\\\\"); boost::replace_all(result, "'", "''"); result.append("'"); if (BackendMinimumVersion(8, 1)) { if (result.find("\\") != std::string::npos) result.replace(0, 0, "E'"); else result.replace(0, 0, "'"); } else result.replace(0, 0, "'"); return result; } bool DBconn::BackendMinimumVersion(int major, int minor) { if (!m_majorVersion) { std::string ver = ExecuteScalar("SELECT version();"); sscanf(ver.c_str(), "%*s %d.%d", &m_majorVersion, &m_minorVersion); } return m_majorVersion > major || (m_majorVersion == major && m_minorVersion >= minor); } DBconn *DBconn::InitConnection(const std::string &connectString) { MutexLocker locker(&s_poolLock); if (!ms_basicConnInfo.Set(connectString)) { ms_primaryConn = NULL; // Unlock the mutex before logging error. locker = (boost::mutex *)NULL; LogMessage( "Primary connection string is not valid!\n" + ms_basicConnInfo.GetError(), LOG_ERROR ); } ms_primaryConn = new DBconn(ms_basicConnInfo.Get()); if (ms_primaryConn == NULL) { // Unlock the mutex before logging error. locker = (boost::mutex *)NULL; LogMessage( "Failed to create primary connection... out of memory?", LOG_ERROR ); } if (ms_primaryConn->m_conn == NULL) { std::string error = ms_primaryConn->GetLastError(); delete ms_primaryConn; ms_primaryConn = NULL; LogMessage( "Failed to create primary connection: " + error, LOG_WARNING ); return NULL; } ms_primaryConn->m_inUse = true; return ms_primaryConn; } DBconn *DBconn::Get(const std::string &_connStr, const std::string &db) { std::string connStr; if (!_connStr.empty()) { CONNinfo connInfo; if (!connInfo.Set(_connStr)) { LogMessage( "Failed to parse the connection string \"" + _connStr + "\" with error: " + connInfo.GetError(), LOG_WARNING ); return NULL; } connStr = connInfo.Get(); } else { connStr = DBconn::ms_basicConnInfo.Get(db); } MutexLocker locker(&s_poolLock); DBconn *thisConn = ms_primaryConn; DBconn *lastConn = NULL; // find an existing connection do { if (thisConn && !thisConn->m_inUse && thisConn->m_connStr == connStr) { LogMessage(( "Using the existing connection '" + CONNinfo::Parse(thisConn->m_connStr, NULL, NULL, true) + "'..."), LOG_DEBUG ); thisConn->m_inUse = true; return thisConn; } lastConn = thisConn; if (thisConn != NULL) thisConn = thisConn->m_next; } while (thisConn != NULL); // No suitable connection was found, so create a new one. DBconn *newConn = new DBconn(connStr); if (newConn && newConn->m_conn) { LogMessage(( "Allocating new connection for the database with connection string: " + CONNinfo::Parse(newConn->m_connStr, NULL, NULL, true) + "..." ), LOG_DEBUG); newConn->m_inUse = true; newConn->m_prev = lastConn; lastConn->m_next = newConn; } else { std::string warnMsg; if (connStr.empty()) warnMsg = ( "Failed to create new connection to database '" + db + "'" + (newConn != NULL ? ": " + newConn->GetLastError() : "") ); else warnMsg = ( "Failed to create new connection for connection string '" + connStr + "'" + (newConn != NULL ? ": " + newConn->GetLastError() : "") ); LogMessage(warnMsg, LOG_STARTUP); if (newConn != NULL) { delete newConn; newConn = NULL; } return NULL; } return newConn; } void DBconn::Return() { MutexLocker locker(&s_poolLock); // Cleanup ExecuteVoid("RESET ALL"); m_lastError.clear(); m_inUse = false; LogMessage(( "Returning the connection to the connection pool: '" + CONNinfo::Parse(m_connStr, NULL, NULL, true) + "'..." ), LOG_DEBUG); } void DBconn::ClearConnections(bool all) { MutexLocker locker(&s_poolLock); if (all) LogMessage("Clearing all connections", LOG_DEBUG); else LogMessage("Clearing inactive connections", LOG_DEBUG); DBconn *thisConn = ms_primaryConn, *deleteConn; int total = 0, free = 0, deleted = 0; if (thisConn) { total++; // Find the last connection while (thisConn->m_next != NULL) { total++; if (!thisConn->m_inUse) free++; thisConn = thisConn->m_next; } if (!thisConn->m_inUse) free++; // Delete connections as required // If a connection is not in use, delete it, and reset the next and previous // pointers appropriately. If it is in use, don't touch it. while (thisConn->m_prev != NULL) { if ((!thisConn->m_inUse) || all) { deleteConn = thisConn; thisConn = deleteConn->m_prev; thisConn->m_next = deleteConn->m_next; if (deleteConn->m_next) deleteConn->m_next->m_prev = deleteConn->m_prev; delete deleteConn; deleted++; } else { thisConn = thisConn->m_prev; } } if (all) { delete thisConn; ms_primaryConn = NULL; deleted++; } LogMessage((boost::format( "Connection stats: total - %d, free - %d, deleted - %d" ) % total % free % deleted).str(), LOG_DEBUG); } else LogMessage("No connections found!", LOG_DEBUG); } DBresult *DBconn::Execute(const std::string &query) { DBresult *res = new DBresult(this, query); if (!res->IsValid()) { // error handling here delete res; return 0; } return res; } std::string DBconn::ExecuteScalar(const std::string &query) { int rows = -1; DBresultPtr res = Execute(query); std::string data; if (res) { data = res->GetString(0); rows = res->RowsAffected(); return data; } return data; } int DBconn::ExecuteVoid(const std::string &query) { int rows = -1; DBresultPtr res = Execute(query); if (res) { rows = res->RowsAffected(); } return rows; } std::string DBconn::GetLastError() { boost::algorithm::trim(m_lastError); return m_lastError; } ///////////////////////////////////////////////////////7 DBresult::DBresult(DBconn *conn, const std::string &query) { m_currentRow = 0; m_maxRows = 0; m_result = PQexec(conn->m_conn, query.c_str()); if (m_result != nullptr) { int rc = PQresultStatus(m_result); conn->SetLastResult(rc); if (rc == PGRES_TUPLES_OK) m_maxRows = PQntuples(m_result); else if (rc != PGRES_COMMAND_OK) { conn->m_lastError = PQerrorMessage(conn->m_conn); LogMessage("Query error: " + conn->m_lastError, LOG_WARNING); PQclear(m_result); m_result = nullptr; } } else conn->m_lastError = PQerrorMessage(conn->m_conn); } DBresult::~DBresult() { if (m_result) { PQclear(m_result); m_result = NULL; } } std::string DBresult::GetString(int col) const { std::string str; if (m_result && m_currentRow < m_maxRows && col >= 0) { str = (const char *)PQgetvalue(m_result, m_currentRow, col); } return str; } std::string DBresult::GetString(const std::string &colname) const { // Below function will return -1 if string is NULL or does not match. int col = PQfnumber(m_result, colname.c_str()); if (col < 0) { // fatal: not found return ""; } return GetString(col); } const std::string CONNinfo::Parse( const std::string& connStr, std::string *error, std::string *dbName, bool forLogging ) { std::stringstream res; PQconninfoOption *opts, *opt; char *errmsg = nullptr; if (error != nullptr) *error = ""; if (connStr.empty()) { if (error != nullptr) *error = "Empty connection string"; return res.str(); } // Parse Keyword/Value Connection Strings and Connection URIs opts = PQconninfoParse(connStr.c_str(), &errmsg); if (errmsg != nullptr || opts == NULL) { if (errmsg != nullptr) { if (error != nullptr) *error = errmsg; PQfreemem(errmsg); } else if (error != nullptr) { *error = "Failed to parse the connection string"; } return res.str(); } std::string val; bool atleastOneParameter = false; LogMessage("Parsing connection information...", LOG_DEBUG); // Iterate over all options for (opt = opts; opt->keyword; opt++) { if (opt->val == NULL) continue; if (opt->dispchar[0] == 'D') continue; val = opt->val; if (forLogging) { LogMessage(( boost::format("%s: %s") % opt->keyword % (opt->dispchar[0] == '*' ? "*****" : val)).str(), LOG_DEBUG ); } // Create plain keyword=value connection string. used // to find pooled connections in DBconn::Get() and to // open the connection in DBconn::Connect. this works // because PQconninfoParse() always returns the // connection info options in the same order. if (atleastOneParameter) res << ' '; atleastOneParameter = true; if ( dbName != NULL && strncmp(opt->keyword, "dbname", strlen(opt->keyword)) == 0 ) *dbName = val; res << opt->keyword << "=" << val; } PQconninfoFree(opts); return res.str(); } bool CONNinfo::Set(const std::string& connStr) { m_connStr = CONNinfo::Parse(connStr, &m_error, &m_dbName); return !m_connStr.empty(); } const std::string CONNinfo::Get(const std::string &dbName) const { if (m_connStr.empty()) return m_connStr; return ( m_connStr + " dbname=" + "" + (dbName.empty() ? m_dbName : dbName) ); } pgagent-pgagent-4.2.3/include/000077500000000000000000000000001473675055000162275ustar00rootroot00000000000000pgagent-pgagent-4.2.3/include/connection.h000066400000000000000000000075511473675055000205470ustar00rootroot00000000000000////////////////////////////////////////////////////////////////////////// // // pgAgent - PostgreSQL Tools // // Copyright (C) 2002 - 2024, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // // connection.h - database connection // ////////////////////////////////////////////////////////////////////////// #ifndef CONNECTION_H #define CONNECTION_H #include class DBresult; class CONNinfo { public: bool Set(const std::string& connStr); const std::string Get(const std::string &dbName="") const; const std::string& GetError() const { return m_error; } static const std::string Parse( const std::string& connStr, std::string *error, std::string *dbName, bool forLogging=false ); operator bool() const { return m_connStr.empty(); } private: std::string m_connStr; std::string m_dbName; std::string m_error; }; class DBconn { protected: DBconn(const std::string& connStr); ~DBconn(); public: static DBconn *Get(const std::string &connStr="", const std::string &db=""); static DBconn *InitConnection(const std::string &connectString); static void ClearConnections(bool allIncludingPrimary = false); std::string qtDbString(const std::string &value); bool BackendMinimumVersion(int major, int minor); std::string GetLastError(); operator bool() const { return m_conn != NULL; } DBresult *Execute(const std::string &query); std::string ExecuteScalar(const std::string &query); int ExecuteVoid(const std::string &query); void Return(); const std::string &DebugConnectionStr() const; bool LastCommandOk() { return IsCommandOk((ExecStatusType)m_lastResult); } bool IsCommandOk(ExecStatusType ret) { switch (ret) { case PGRES_COMMAND_OK: case PGRES_TUPLES_OK: case PGRES_COPY_OUT: case PGRES_COPY_IN: #if (PG_VERSION_NUM >= 90100) case PGRES_COPY_BOTH: #endif return true; default: return false; }; } void SetLastResult(int res) { m_lastResult = res; } int GetLastResult() { return m_lastResult; } private: bool Connect(const std::string &connectString); int m_minorVersion, m_majorVersion; protected: static CONNinfo ms_basicConnInfo; static DBconn *ms_primaryConn; std::string m_lastError; std::string m_connStr; PGconn *m_conn; DBconn *m_next; DBconn *m_prev; bool m_remoteDatabase; bool m_inUse; int m_lastResult; friend class DBresult; }; class DBresult { protected: DBresult(DBconn *conn, const std::string &query); public: ~DBresult(); std::string GetString(int col) const; std::string GetString(const std::string &colname) const; bool IsValid() const { return m_result != NULL; } bool HasData() const { return m_currentRow < m_maxRows; } void MoveNext() { if (m_currentRow < m_maxRows) m_currentRow++; } long RowsAffected() const { return atol(PQcmdTuples(m_result)); } protected: PGresult *m_result; int m_currentRow, m_maxRows; friend class DBconn; }; class DBresultPtr { public: DBresultPtr(DBresult* in_ptr) : m_ptr(in_ptr) {} ~DBresultPtr() { if (m_ptr) { delete m_ptr; m_ptr = NULL; } } DBresultPtr& operator=(DBresult *other) { if (m_ptr) { delete m_ptr; } m_ptr = other; return *this; } const DBresult& operator*() const { return (*(const DBresult *)m_ptr); } const DBresult* operator->() const { return (const DBresult*)(m_ptr); } DBresult& operator*() { return (*(DBresult *)m_ptr); } DBresult* operator->() { return (DBresult *)m_ptr; } operator void*() const { return (DBresult *)m_ptr; } operator bool() const { return (m_ptr != NULL); } protected: DBresult* m_ptr; }; #endif // CONNECTION_H pgagent-pgagent-4.2.3/include/job.h000066400000000000000000000014141473675055000171520ustar00rootroot00000000000000////////////////////////////////////////////////////////////////////////// // // pgAgent - PostgreSQL Tools // // Copyright (C) 2002 - 2024, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // // job.h - agent job // ////////////////////////////////////////////////////////////////////////// #ifndef JOB_H #define JOB_H #include class Job { public: Job(DBconn *conn, const std::string &jid); ~Job(); int Execute(); bool Runnable() { return m_status == "r"; } protected: DBconn *m_threadConn; std::string m_jobid, m_logid; std::string m_status; }; class JobThread { public: JobThread(const std::string &jid); ~JobThread(); void operator()(); private: std::string m_jobid; }; #endif // JOB_H pgagent-pgagent-4.2.3/include/misc.h000066400000000000000000000030031473675055000173270ustar00rootroot00000000000000////////////////////////////////////////////////////////////////////////// // // pgAgent - PostgreSQL Tools // // Copyright (C) 2002 - 2024, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // // misc.h - misc functions // ////////////////////////////////////////////////////////////////////////// #ifndef MISC_H #define MISC_H void WaitAWhile(const bool waitLong = false); void setOptions(int argc, char **argv, const std::string &executable); std::string getArg(int &argc, char **&argv); std::string NumToStr(const long l); void printVersion(); #if BOOST_OS_WINDOWS std::wstring s2ws(const std::string &str); std::string ws2s(const std::wstring &wstr); #endif std::string generateRandomString(size_t length); bool createUniqueTemporaryDirectory(const std::string &prefix, boost::filesystem::path &tempDir); class MutexLocker { public: MutexLocker(boost::mutex *lock): m_lock(lock) { if (m_lock != NULL) m_lock->lock(); } ~MutexLocker() { if (m_lock != NULL) m_lock->unlock(); m_lock = NULL; } // When the exit(code) is being called, static object is being released // earlier. Hence - it is a good idea to set the current mutex object to // NULL to avoid ASSERTION in debug mode (specifically on OSX). MutexLocker& operator =(boost::mutex *lock) { if (m_lock != NULL) m_lock->unlock(); m_lock = lock; if (m_lock != NULL) m_lock->lock(); return *this; } private: boost::mutex *m_lock; }; #endif // MISC_H pgagent-pgagent-4.2.3/include/pgAgent.h000066400000000000000000000033701473675055000177700ustar00rootroot00000000000000////////////////////////////////////////////////////////////////////////// // // pgAgent - PostgreSQL Tools // // Copyright (C) 2002 - 2024, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // // pgAgent.h - main include // ////////////////////////////////////////////////////////////////////////// #ifndef PGAGENT_H #define PGAGENT_H #if BOOST_OS_WINDOWS #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "misc.h" #include "connection.h" #include "job.h" extern long longWait; extern long shortWait; extern long minLogLevel; extern std::string connectString; extern std::string backendPid; #if !BOOST_OS_WINDOWS extern bool runInForeground; extern std::string logFile; #endif // Log levels enum { LOG_ERROR = 0, LOG_WARNING, LOG_DEBUG, // NOTE: // "STARTUP" will be used to log messages for any LogLevel // Use it for logging database connection errors which we // don't want to abort the whole shebang. LOG_STARTUP = 15 }; // Prototypes void LogMessage(const std::string &msg, const int &level); void MainLoop(); #if BOOST_OS_WINDOWS void CheckForInterrupt(); HANDLE win32_popen_r(const WCHAR *command, HANDLE &handle); #endif #endif // PGAGENT_H pgagent-pgagent-4.2.3/include/pgAgent.ico000066400000000000000000000463161473675055000203220ustar00rootroot00000000000000hv ¨Þ00¨†h. ¨ – 00¨>- èæI( @iEˆa.Žb/‹g/Žf/‹h.‘e/ƒa2…a5ƒd4†g6ˆb2b2Šd2Žd1‰b5Œc5Žd6a8„b9…d9Šc9Še9‡i?‰h8h:‰h=b2‘e1”e0‘c4h?‰eA‰mFŠkHŠpKqH™rJˆvW˜vV–|Y˜}Xž}]œ€]£^’…`œƒa¢„a¦ˆa¡Žk Žnª–n¡u¥”w«—u­–u²}´¡|£•„®¡Š­¥‹¬£º©†½©…¶¤‹¸¦´ª½©Œ´¥“·«‘¸ª‘»¬‘¾¯•½­š¿°’¸°•¼²•À¬ŒÁ¯Ä±‘ĵ“À´•Å·”È´—µ˜Ä¸›Å· Ç¿§Çº¨Ç¾©È¿¯ÉÀ´ÉŶËźÏɼÓÉ»ÔÌ»ÊÆÁÓÏÂÕÏÀÜÔÃÙÑÇßÜÔáÙÎáÞ×åßÕæá×ãáÙîëæîîìñíçñîéóðêôòîc#)k6/0&- ej=]+oA_l=(Z;bg"_AMCnBD" Q Z^,Q 94'FjrN8!Q$:2cQ8MM.@JNQV9DZVeF8+60VjQ $F_F EC 2Q Jrrrm][rÿÿÿÿèÄ€€€€Îc( @€tY7~Z2z_2~_0}[4}]5t]:z_:}a3wb<|c;w_@kcGtbF}eArfLxdN}kK|mR~pRƒ^/ƒ_1†_1[5]6†\5Œ^3‹_6_7…^:^;^4ƒa-d+ˆ`/c,‰e.f.‰h.Œh-‘c.•c/‘e.–f.™d/h.”h/a2…a1„d2‚a6…a5†e7‰b1b1Šd2e1‰a5a5‰d4d4‚c9„b9ƒe9„d;„e>Šb8†h?‹h;‘b2”c2‘e1”e0’b5•b5d4•e6˜c1–h2b8ƒdC„kFˆkC„kL‰lIŽqAsLvI“sO“tNœzOoP‹vS‰qTŒqWtXƒ}^Š{[”{Uš~U”vY™~[¡\†u`ŠubŽ{`{iž`–Zœ‚[¡€V¥„Y“‚d™…c—†l™‚jœiš…j…iІs’‰pœ‹p˜Šu™‹yž“w˜}¡‡e¥‰g¨Œc¤Œk«‘n¢Žs¥Žx ’sª”t«˜q£’|ª—}¬™|¶¤~™’†¥–ƒ¢ž‡ª›ƒ¥™Š²œ‚®¤ƒ¬ Ž¶¥†¸£†¸¨…³£Œ»¦ˆ´¨¸¨‹¿®ˆ¼ª¾³¯¡‘°¦“µ¬“»¬’»ª”¼¯”¾¯œº³–¸³˜³¯«»´¢Ã¯‹Ä´•Ǻ•Á·œÎ»›Å¶¡È·¡Å½ ÇìÊÀ®ÑÃ¤ÕÆ«ÓÉ«Ùέ×ЬÛÒ­Ì±ËżÐɵÚ˲ÑɹÔ͹ÑȼÕʼÖοØÌ»ÚѲÜÓ¼ßÜ¿âÕ´áØ²áÕ¼åÜ¿æà¿ÍÎÅÔÑÂÛÔÃÙÓÇßÓÅÝ×ÇÔÒËÚÓËÜÚÉßÚÑßÝÙâ×ÃãÙÃèÚÀäÜÌáÞÑäßÒâÞÖèÜÑàßÛçàÂëàÂäáËìäËäàÓêäÓäãÞëåÛïéÚëèßíéßòìßßßàçäàéèæìêåîìäëëéíëèîíêðêáðïàñïäñíêõñêòóìúôëõóòøöñûøòùøôüüôóÎv]v¢öÝz2„êÁG=GGA¥­G==/ŠóA===(=ð™$G=( gùß–==G((GZÿóæðÒ•G-G( Uçû»©ÁÕÌ’ G-$$GSÕÞv•ð»‘{â’GG$$=ÝÿúúçrA³óÕÁ •G(($=Á h¨ó°A$=TÁæ²ÃlG=(/=¯¼”ÎææûÝS=GGÚçÒÁâ}=PPPGGG=¼Ö©A=/‚ØzcØPGGPP-/=råßÖÎv=*G=WÃ}AA½“$-=PGGGP¼ÃÒÒÖÉUG**=ØQ(’¶-==G-=ZØxÔ‘ÿó«AG**=ƒÅ2==A]ÅGGG-GA‹âiÁùA***=‚É ==T´QPGGGP]¹Q\à]…ÿåU=**/=fáD=GPQ´TG*P œ´mÈUTÒÁGG**=cáS===UµSP==E ň$rÅE† $=G*=nØSnj·P=PGTáb2bÈV(Qðí‰==GG*=nãq †Ø¥¸=PG=SÊ}žaã[*ÃÝvP$GG*=nãʶ¦©ØŒGGGG¬Í±åpGÝeG/=G*=cásˆ®®‡S=G====VœÅÃêp*=‚ÿãeP/=G==SÎr=====O*$Ž·E==[ðÚr=-===(¬ªA$$====P(G-G=bÃ=G=cçê•=PP=GPbà]G===P=G-PA æ=GPjçÁ A=GP=P ¶¦=P==G/A’¶TAP ÿ÷  =====AjΆ2==A\¬ÅE2AQ’ÿݱW2 QkÁð¦R222Q‹Å´n2\­÷öÔÁÁÓðêÁ²²Úöÿâz{†š¼öûÝÓðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿâýàøÀð`à8À?ÀÀ€€€€€€€€Àð<ÿÿü?(0`€ ^M8YS5y[,gS3mS3lY6dW8kU:fZ=n[9sV2}V4t[4z[3tV;{V9t];{\:}b,nc4vc6|`3ua={b;h;iXAw[Bk`GtaA|cC~iCwcI|kK|qNnjT|kUzqY|vd„],€\2‰[3€V9ƒ\:_:”_6ƒb.Œb-†i-‹i-’e-šc.”h.„b4‹d3„h1‹h1ƒc:‹c9Œi=’d2™d1“h2™i3“a9˜b9’j9™j8„jF‹pFpHŒpLgR‚lSŒoP„qT‹sRŒ|UŒu]‰|[’{Tœ~Q“|Z™~Z‡vd“|b^”‚]›ƒ^¢…]Še’‚e‚cžŠg“€o›‚j“Šj‰l’kˆ†y“„r”‹s›Œt‰|Ÿ{˜’y¡‹d¢Šm©nª“m£t©”s«›q£”zª”z¦š|«š|°–q´œ{¬ {°¥}‹ˆ†˜š”†¥™·Ÿ‰©œ’®¡‰²£†¿¤ƒµ¦¸¤ˆµ¨Šº¨‹¯¢–³ª“¼«“·®Ÿ¾¯š½°‘³³¼±œ¾¸¨¦¡²­¥¶³£½µ ²³ª·º¯¶´±¹´²¾»·Â«‰Á¯—IJ‘ɳ‘óœÎ¶œÂ¸ÊºœÑ¼žÈ»¥Ð½¦Á¿½Ë¼»ÎœÒÄŸÌèÖÂ¥ÓÅ©ÙÆªÓÈ­Û̬ÌɳļÍȹÓÃ²ÚÆ±Ô˲Ú̲ÖÇ¿ÓÌ»ÜÍ»ÓÑ·ÜÒµÐÑ»ÛѾÛÙ½âβàÓµäÚ´èÚ·âÕ»âÙ»êá¼ÆÂÅÎÆÃÌÊÄÎÏÊÑËÅÔÎËÜÓÃÔÑÌÛÕÊÚÙËÕÔÓÙÔÐÝÚÒÕרÞÜÛàÖÁäÚÂëÛÁã×ÈâÚËëÜÉóßÌâÝÓÞãÜëãÄéãÊïèÏóèÊääÔëäÒíéÓäãÛëåÛéëÝôæÐôëÔøîÓóìÜùíÞìðÕü÷ÚæäãêæâìíæëêéòìáñîëùïîìñãõòäúôãýúãôòëúõêóøèýýëôóòùõòöùòýûôýüúÏøˆUUqŠàøÇõf ‚ø÷Ž(6666( ˆ÷ø[ 6=6<<<5IÖÜK<266>>6°ìÕ!6=+:>46 ˆÇ¸6616684<(…˳:(80=<00<)vÌâ«+<60=108=)l䮫+@11=<08<)Xø“©+<<<3<08=)Rø÷ñÅÆ®Îÿ“«+A<4=408<)Jè¤ìíãËáúü–”§+<=>=601<@DàÄïòΖïÑ&#[ýüŽ:-=>3406<@Ëï÷¨Eoüïãñâµfiö¢,@>80041::°ôØåøúñÖD(1¡ï®Ëø §±:6<80421:¢ðdIITzÊÄüa)6(H²ï®÷i²+<>8804/(5œð%$^gcy£÷¬:12>JÕÊîè»ç\*:@<66<4>6(nøÕ¡ÖÿÆôX<22<5HÌàÙôù·t(@<<8@8><<85iàì÷÷’66<4>1!Óùœ&˜ùO:<<@::<<=><6 `è÷ï–üZ :566<1:¡ör QëŒ B=<>6@@<=?>6EÕüÇÐÐýÇØ668><6këŸ:;¼áF5336::B=4?<(ùáßýÆè‹<42426ŸøR666lùw43<76<4436EØÕgù{Ä÷—ø\624466DÔº566:Dا<=<5<<==>(jùn#ùTøËØD:<<4426Eا:666+¥Â6=>5==?<5¦¼#ê¡´üã¡:8224<69س561@:rÛF)==8==<:RÞk%ù^éér6<222<6:ÀÕ52<6:\Û!,@<<=<@: ¥ÂD Mö˜³íèP(<2224<5¨ÞD.?><6UÜK+A=426:!ç¥bëm+rüí¶62>224>6ŸçG62=<:*\Ø%+@<<>7 ˜öY((pëY+:F¹ý‚<2<<<416 åE+666/ qÛE6:::>B5Á¿8(töW.|üîf*<1<<<326¨åD((5:„ÂB6:@:>8Gç¥ (qöj.0(KðßN(@6<<>4155¦åD‹ÀZ§Á56<@@2<:Wö MZùv.21µîÚÖD,<0@><226:³ù…!M»á‡Ü¥(<<=A2<5EاgÞx#ù¦/2<ˆøâÕ -<0<<4425¬ùöÂbi{­öp'<<<=<>5‹ù’Žb%õ«/==*kø÷¡->0<<<426(Ÿç€½çööùö˜51<<<<61<6E¬ùÛÛßü«<<=4>>2446‹øKPuvQ(<68856<64.7p¦­ÕÿŸ126)6»úŠ*A>18<>486(tør 5((((,:<>68766>426~øk(261(¦ÿôŸ;33886567.Xç›502/>@:66861:·ÛD511228:<<6<:><==<@7­çY)<<>6‹üÏåK<6<<6<6<<töv5<3<666B6<643<<:: „ú…66<<6¡ýúq :6<<@<<<@"Þ¾:1<6<6666<2?>:: Rù³6><): ÕýÙÕ :6<6@<@>5tú‚:66;66666C<:FÓ¿I5:56:{øìüˆ6668868:ºèe58686566::YÁÞR(;5;nõÆøx'8866866:TäÜ`6866..6FtÞëm'0<.6}èÿÎú«jD  H^‚ÙùŽW55M€Óö¾M5([°øýëܧœ‡‡¡µÑøÿñüø¸˜||€Ÿ³äùúŽHEO|²÷ÿýÎñÿÿÿüÑâüüÿÿÿðËÕÕÜèúü÷ËÆÃñÿÿÿ€?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿþÿÿÿþÿÿÿÿÿýÿþ ÿþüøÀøà?ð`à¿à?À?ÀÀÀÀÀ€€€€€€€€ÀÀ ø0üøÿÿÿÿáÿ( @ÐɸŠkH–|YÛÓÅþþþ£’tŽc2f1œƒaöõôøøö¢„ae0‹g/™rJóòíööõïîìççä£^“f/‹g/h?ôïêóîêʾ°ïîíÚÖΪ¡ŒÈÄ´ž}]”f1‹h.Šc9êåÜü÷ðýýûõôò²ž„ˆb2Ÿ¥òñîßÜÓ­¥‹˜vVf3Žg/ˆb3Ǻ¨Ÿ~»¶°îìéÿÿÿØÔʆd9f0ŠnGÑȶ©›ƒ¸°•c5Žd6“d2e2µªããàðï︦e0Že1½©Œ‰mF†b7µ˜Žb/‹d3•e0h:Ǿ©ÊĹøöôõó]‘e/e0İ“„b6‹c4±~‹d:“d1‘c4ª–nˆvW¸ª‘ÙÏÁëæÝ‰c6‘e/e0À¬ŒŠf9Žc2¬•w‰eAe1‚c9ĵ“qH´¡|ŠiþþýÊĵŒb3’e2Že/IJiE«—u½³”Œc4“c4‰h=¼²—’…`½©…ˆb5ìé཭šf1’e2f0Á¯¿°’ĸ›³ž}Žd0d2‹d2º©†Ç¿§È´—b1Ñů¾¯•“d2Œe3Še2®—t˜}Xˆa.c6e3‹e3”d1d1¡u¦ˆad3¾®”ÙЀ`8c3c3‡i?À´•…`5d3Œd3e2e3ŠpK»¬‘†b5‰a5Ž­ôòñµ©Ž‰h8ƒb3„b9³¤’®¤‡†g6ƒa1…d8¡ŽkÅ·”ƒd4…f:¸¦‘ûûøúúúîêáæáÙîëåûùöø÷öëæÜâÜÒñïäôñéÀ¶§Ã¹¨êæÞþþþÿþþøÀ€€( @€ èæàÝ×Ç™‚j‰qTš…j»ª”ëä×ûúößÚÊœ‹p~Z2†_1„`2€_6¡qåàÕÐȱ~fB‘e2d0‘f1‘f1„d;¾¯œöõõþýûÁ·œ|d@d2ˆa4Œe4”h/„`2ª—}äáÝÿÿÿõôñ¼ª…a9Šf1c1‹f2Œh/`2¥‰gäãÙýýýïî黦ˆ‹_6‰g/“f/g/‰h/`3¡\ëìáþþþÿÿÿÿÿÿÿÿÿÿÿÿÓÒ͸£†Ž`7e.•f0g/Ši/‘`3“sOøóèýüüùùøãáßÝÛ×àßÜÿÿÿËÌÆ·¤†_7’g0–e/g/‰h._2ŒlHìáÖñííûúúúööìèåðïíúúùäâãëèäÌÄ»¹³¢ÑʳÜ×ÍÍÎŲŸ€^7–f0—e0g/‰h.“c6‡fBߨÎñïîñïíå×Ɇk®œ…÷õñèççèåÞÊÆ¼¤šˆ|mR’‰päáα›‚†];”e3g0‹h.Œh.‹b1„c;áÙËöóêöìÞûóåýûôûûùÿÿÿñïîåÝÒ˜‚e…`0ƒa7ø¥øõî÷÷÷ÝÜÙßÝÔÒʹkcG©Ÿ…·ž†‚^;‘e4Œg0‹h.h.‰b0„a6Ùʸ¯¡‘xdN†u`™’†³¯«ïììúø÷ûûûèæáÆ´œ„e>c,e2‡nOÒÆ±ù÷ðùùùææäåäÝÇìØÐµž`‡_8‘e4Œf2f0‘g.Že2ƒ_3Å· Ðȶ¬ ŽÐȺÔÏÈÎÉÆãáÝíéæÿÿÿíìêßÝÏŠlE‰d4’f-’g/„b5|hIåÜÉãßÔȶÔÌ»äâÌ¡”wˆ`7‘c6d4d6e3”e0‘e0‰c3oPÑʵòñíééæÿÿÿÙÕк°ž}]4ˆd5Žd1g1ˆb1¡ŒkäÛĘŠutbF•|WßÜ¿~pR’e6•d1d4Žb8“c5–d0–h2‹d0™‡céæÓÚÙÒØØÙÿÿÿíììßÓÅ›„g‹d2e0’g1‹d3ŽqJàÕ½£Žn~_7…c8…c:Ú˰®¤ƒ†a-™d/Œd2‰d6’e2—f.”f2ˆa6ÚȰÕÒÀÑÑÆÔÍÈïíìÿÿÿÖÔÒßÓ¾ˆmJ‘e0”f.’f.Šc3¡‡eãØÃ~fC‡b2‹e5€\6°šÒÊ«a.™c0Œe2‹d4”e1–f/Žc2“tNâ×ʆsÜÜ˦˜Œ÷ñëüüûÿÿÿéåßð–ƒc;‘f1“f.“f.Šc2«‘nÜηz_:Œe4Œc1…a7tXÛѰƒe9–c4e2“c0˜d2d3‚c7¿®ˆ®˜~rfLäàÈŠubÒĵúúõûûúóí㣋l‡c5e/’e-“f.Šc3¦‹häÖ½~b;Œc1d1Šd7ˆhJÒŧgC’`7’e1•c/“c3ˆc9ŒuOÛÒ­ƒiGoMçàÂŒqW¤vóðä÷öôêâÕ†iEc0e/‘d,“g/‹d3™~[èÛÀ†h?e0”f1‹c5‡dEЦjM^;”e1f.ˆd5z`;¶¦†ÓÄ }[4–€XáØ²mK†jJÐË·þþþùú÷ÑǶ[5e0‘e2’e/“f.Šc1–|VéÙÀ‰jE‰b2Žb0Œb1…iIÔÆ¨ˆjD‹a9d5f2‰h8{b=ÞÓ´«˜q„b3‡aÜÓ³ˆk@b2 ‘rüüùòõî³§’†\5Že0d4“e0’f.Šd1›‚ZèÛÀ|dB|_6‰lBŸƒ^Š|^ÚÏ­‰f8c5b8’d2ˆc3ˆnIëàˆz\}e8Œ}[àÖ»ŽqB‰d.‚fBêåÛúúøáäÚ£’|Œ`7Žg/e4“e0’f-Šd1ž„\îåÊ“ƒbr_; ’rãÚ¸³˜×Ь…`1“c4–b6”d0‹d1‰mEåܘ}¾³ƒ}^éâÉœzO’c/ƒ\6ÓʸúúõáßÒœia5h,d3“e0’f.Šd1šZîæÏåÜ¿Ùήº³–ºµ¡áÛÀ¶¤~ˆ`/’d1“d3‘c0Že1…b5Ƶ”Ùտž ¢ž‡ëåÒ¥„Y—d0Œ^3·¦ˆñïæäßÑ•u[”c5Žk-e3“e0“g.Œe2”{TçÙÅ“‚fª–tͺ˜Ï¼Ÿ«“tŠhBŽe1f0‹f2‹d3Že0f0qA¸¨…ØÍ²ÛÔÃïéÚ¡€V‘c.b2§ŽjôñææßÏ”wX•e6’g,e3Že1Žf0‰e2ŠnDáÔ½…h~^6ƒ_0ƒ^/ˆa8b8f2Žf1ˆg2ˆd4d2—g.‘e.„c1u\<¥–ƒ×Ưi>e1Šc1š~UèãÖæß͘…`c4™b0Œg4Še3‰d2Še3ˆe6Å·–ï‹b6‹d/d+‘e2d4e4e4e4e2“d2˜c2”c2Œe1ŠyYßÖº¨ŒcŒd4e4Œd3”|UäÝÐçãØ²¢ˆ}]6Œb2‘d4‘d4b2c3Šb3}Zæà¿‹vS…a6’c1c1Œe4d3d3d3“e/–f/d3ƒ`7d>Ù黬’~^5‹c2’d1Šc4Ž}`åßÕóðìØÌ½w_@‚c8c2c3‘d4d3c4wb<ÓË®¼¯”tY7‡a5‹d2Œe4‹d3Šc2‹c2”g3e3†e7w^9­œ}ÔÈ­„nMƒc7Šd4‹b6}\7®¤õóí÷öõìçâ´¦Žzg=ˆc1‹e3‹d2Œf3‹e4„`8Žy`àØÆž“wyg=„d2Še3‰e3‡c1‡c1‡`6`:ƒoGǺ•âÕ´ˆj@ƒb2‰e5†c8cD¬˜‚öóêüüûÿÿÿñïîâÞÐÄ»žvIc5z_2{`3}`5ƒcE{iÏÆ¹ìêÚ´­ˆoCb2~_0~_0a3†kGª™zÙаÕʨ–‚]€a1‚b.~`1ˆoKÄ´ ïéàýýüúúúññððéÚÛÕÄÑÈ·ÌóÙÑÂæãÝùõðôñìùùøïìêëäÔÓȴĹ¤È½©ÞÔÀìë×úûëÝÖÇ™‹y—†l¡‘q®¢†Ë°êçÜýýûþþþ÷õóöõôþýüþýüüúùþþþùùøóòñúúøþþüþþýþþýüýûþýüù÷õðìèàÚÐÙÒËåàÝüù÷ø÷öÿÿÿÿÿÿÿÿÿþ?ÿþ?ÿþ?ÿà?ÿÀ?ð?ðÀÀ€€Àð8(0`€ø÷÷ÛÚÏùõ軩–‘yd‘~f£’}¿°›ìáÒùõéüû÷ÐÎÅøòàœ‹soR3{X4|\4yZ3sV2zbC·§ùòãõóððíÞÁ·Ÿt\<€\2Ža2‹c0‹c0f5€^2tX5½¬•îìäøóá‘‚fuY3f5•e0e1‘f1“g1f2ƒa5…nPäÛÏúùùéãÍ„rT{^7e3”b-ˆb3Žf3“h1”g/‹d3|a>ÐñãáÞþû÷àÙ¿zjK}a:Žd3–e2`9‡d8’g2—i/Œd1tW0¾¯šÑËÅÿÿÿòðìÚе~dC‡b9g4Še.‹a5‹e5‹h1‘h/‘d2X0¹¥ˆÚÖÊüüüïíçṴ́…a>‹]0‹g0‚i-›d0‘f/†i.Šh/–e5‰X1µ›xÙÙÈùùùãâÚÕÈ­„`=a4Œe/ˆj/še1f0‡i.Œh0”d4‰Y3©nçéÙÂÁ¹ÔÈ­ƒ];”b6Œb-‹j/še1f0‡i.Œi0”c3‹[5š_öòáÿÿÿÿÿÿÿÿÿÿÿÿ³³ªÐÅ©ƒ^<”`5“f1Ži/™d0f0‡i.Œi0•e4‰Y3”}ZüòáíææèäáÍÊÆÐÍÇÇÅ¿×ÖÑÿÿÿ²´ªÒÉ­„];–`5•f1‘i0šd0f0‡i.‹h/’a1‹\5‹rOõêÛ˼»ÿÿÿüù÷øöóØÕÖðíëêåßìäÚÛ×ÉëæÔÿüçúõë¶´±·º¯Íħ„];–`5—e1“i0šd0f0‡i.‹h/”d4’b;„kHéßÐÑÈÇÿÿÿáÙÚøëêÙÓкµ²æåâêéèÔÓÕÿþü´®§{rfneRk`GŽ‚dº±Ÿõõïýÿó¸„]<–_5˜e1”h0™d0f0‡i.‹h/”d4a9eCÛÙÉßáÞíííóðç×§‹kEzY0©“vùõëâáÞæãÚðïåæãÙÓ̼™ŒwjW:dW8–‘uüöáм§y[B‡_<’d6•g1‰i0‰i.i-h/‰b,ˆd6‚b<×ųÿÿ÷ôíÝñâÍõçÐüòÝþùíúùôúùùùø÷ìçãâÙÉ‚iH]0ˆb3b>Æ»«üùìßÝÛÃÂ½ÚØÎüö审‹e\?VP2ÐÄ¬ÚÆ±zZ@…`;‘d6”f2ˆi0Ši.h-‘h/‹c.‰d6‚`9н©óêà•…r‚mWkTˆwb˜°¬£ÕÑÎûøøÿÿÿÎÆÃÿù윈o|]6‰b0Že0_4~iN×ʵøõëÜÜÛļøô锎v]W9Õ˰ζœ|Z;‰a9‘e6“g2‰g1‹h0h.h.Šb-‡a2‚`6ųœõîà|m[^M8xgQ‘ƒrˆ}…y‹ˆ†Á¿½ü÷ôëæäððïíìãÚȰz^=‰e7b+“e+i7yZ5ˆqTäÙÃýùìèçåÐÐÍðïèñîÝÛÙ½øîÓž€a‚\8Œa8‘e6‘f3‹f4Œf2f0“h/”i4‹e5€]2¡pùõäÞÖÃÆº©äÜÎýúðûùòôíéïêèäãßùöñæâÞÿÿÿÎÏÊóôæ™~[\2Žg5‘e,•g,‘g0‰e6y_;nSàÖÁçãÖïïëù÷ñàÛÒôðäúøæÓÑ·¬›}[6c9‘c7d5e4e6Že4’f2”f0‘e/Žf4†b4wa@›‘zìçÔÿÿþÜÚÕööôÿÿÿååäõðã½µ y[3Šf9Œe4’g2“g0‘g1e1x[3~lPá׿þøå½±Ÿ~s^l\B¿­þüâ~{bkX7Žf:”c3”d3e4d6Žc8‘d6•e2–e0–g1f2Šd3v_9“‰mòíÚðïåðïëÜÛÛÿÿÿ¹³²ÿýò‰xbxY2ˆf:†b4‹c2Že1h3‹b-„e;ͽ ÿú⤖gW@r`Cs[7œ~Qÿù׳³nY7“h:–c/–e1e3d7a;’b8–d3˜e0™j4“i3ˆa.ˆqFâÞÂÿÿòÌËÁÕרÓÓÖòññÖËÄëÜÉ{bE‡c4‹d3Žf2“h3f1‹d4€]0£‹kúóÞ˺žw_<|_:…f=ƒb9Œi?âΰìëÔ~nHƒa/—e0šd/d2‡e6Œc8‘d6–e0˜f.›j4Žc1‚\1¿¤†ûöáæäÒâ䨍¦¡÷òñåãâÿÿÿËÉÆ÷ñß¿­‘y\8’g2”f/•g-•h.e/‰d6{]6ƶŸúóà“`|^1‹c1‰a/‰c9{X5§uÿþå© xu^)•e/žc/d1‚g4Œe5‘f2–g.™g-”c.Œb6lHóßÌÚÓËŒzóøè•ˆÖÇ¿îêåÿÿÿ¾»·þöä…hy[4e1“f0”g.”g.Žc.‰d6†g@ëÚÁÜÓÀu`@`5Œe3Žg5Œf;|Y4€dIòäÍÌǦza0’c1d1’f2„f2’c5•e2—e0–f0“h5]2¡ŠeÿùãŸ{mmXô÷ä·®Ÿ‰wjûñæôóñÿÿÿ×ÓËóæÑeC„c:‘g2’f0”g.•h.d/‰c5ŠkDóàÇÏÄ®t^=„c7g4d0‰b6^6vW:ʼ¡çâ¼h;`1e5”g4†d/™c2šd2—e2d1‡c3{_4ÒÄŸâϵqZCnkQððØÉ¿«jRBÖôóôëîæÛλ£{^9‹g9f1’e/“f-•h/e0ˆc4‡hAîÝÃØÌµw_<„b4e2b.Že7ˆd;xW:¬•zíåÃ~jB‰_4™d6•f3‹e0œc1™d2“c5‹d7{\3’}UõíÈ¢ŒmsW;{sTûùÞĵpT?š€mõðßüüúîíë÷íÞ«•z~^5e3e0‘e.’e-•g.e0ˆc5ƒd<çÕºæÛÃ}d@†c4‘d/“f0Žd4‰d8~Z:šfë寀oM†\8˜a8•d2f1˜c.•c2d8ƒcˆa1”g/’g2d.’e,•h/‘f1‰d6xY2Í»žöíÒŒpKˆb1“e.–g0’e3Œd6€Y7ŸƒfëäÈpU‚[?•`<–d3“g/j0ˆh5}d9o[:·¨‹ÿüÞž…`ƒ^4€[1«›qÿüÔ…^\8‚_={gBÐÑ»üþüò÷춦|U3d4f/‘e5’e2”f0•g.Žd-ˆc2{^2ѼŸöçÐŒoP‚_7‰b2Œa0‹a0Œa0uX5 ’xðãÂpF‡b5Œd8Šd8Šc8’g1’l9‚b9eCãÙºëà¼}e:‰h5^/­~ÿû㕃_€b-…f/v\)¨˜€ûùôêðàŸŒvV3‘d5Œd-d5’e2”f0•g.f/‰e4~b5×Ã¥óçÏ„mLy\6‚`4`3„c7‚a8gQ2´¨îὌj>‰c3Žd7Žc8Žc9”f1f3}]4ŠrPþðÑËÁ£lZ6{`1x\-¡”xþøážŠg„c.‹h0}^*‡pUðêÞõõóêîéãåÕŒu]†\9“g6Žh.e5’e2”f0”g.Žd-‡c2~a4ÕäïèÏs`@v`;zb;lGñãÔ¸‚fÄĦäÚ´…c6c3’c5”c7”b8“d-d0ƒa7•~[ÿúÜ»±˜f[DŒ|Uvd7…€eÿû㵟~‚]+e0Žd2z\>Ö̼çèàÞãÜàÜË‚gN‹_:‘d1i.e5’e2”f0”g.f.‰e4c7ÝÍ­þúඨˆnMgV4ˆ{[àØ¾íéÖ³®–èæÈÎÜ\-‘f5–c4—c5—b7•f.‘g1„a3‰oHñæÌÊ­‘‹|ðêͰ§|onVùùåѽž‰_.“b-–f4|W5½­–õôääæÝÙѾ‚`F_:d.Šh*d4’e2”f0•h.Žd-‰e4€c7ׯ¨ÿÿêÿúÞëáÁ½°‘˜Žp™”|˜”„Ìɳÿþݪ•m\,‘e2•d2•d4•c5“e/f0Šd4`7Æ´“ûöÞ¶³£¾¸œ”oqp]ðïßØÊ­‹`/™e0—b1‚X1ŸŠjøõàëëéóôéŹ¥Z>’_7“h0l,e4’e2”f0•h/f.ˆd2y\/Ͼ¡ôìׯ¢‡àÓµýõÕÿüÜÿ÷ÜÿúåýõÜ«‰ƒe=‹d2f0’f1e2e4Œa.Žd/g4ˆd4‰mAÞϬþûäêâÇíãÄåãÕÿÿõÖǪe4—e0–e3‘e9ŽpJïèÐø÷ó÷÷ìÀ°›V9•a7•j2‹m,e4’e2”f0•h.f/‹f5}`4¯•ùíÞpWu^>uP°–q¿¤´›|”xX~[:†`7Že2g0Žg0‹g2‡f4Œe5Žf2f/g/…a,‚f8¨—sÏħΪÚÒÇÿÿ÷̸ˆ`/c,Žd0Š`1‡d8à×»ÿÿüøöìÀ¯š€V9˜c:–k1Šj)d4‘e2“f0”h/g0‰e4y\.¬˜~ÿ÷訓|rV6a:€]2~[1\3\8Šb?b9‘f3g0Œg0†i1ƒh2ˆc5c2“h/–i,g,‰f2x_3x^…a5–d4™a0Žg4Œg5‹e3Šd2g5Œf4‹b/f>×ϱóçÄkD`3Šd1Že.“e,’e0e5e4e4e4e4e4e4’f0“e1—d2˜c2”b3e6†d2weCÑȳúñÓ¢…]…`1’g5’f4“g5Šc2z`5ò˜ôòìÝÚÒóêÓ†qR{\6Œc5`/’f5‘e5c3Žb2d4d3’d2y^6¥œ|ÿÿÝ­œypV/…b7d3˜e1‘c0Œe4e4e4Že4Že4Že4e4—g-–f.–e0’d4‰a6†dgR“€o¯¢–áÛÏÿÿùøõ⾸ž“Z€b3|`2z]0x[.{^1|_2‚e9‹{[³¦‡äÛ½ÿûßÜÓ´“^v^9€c4~b+{^,z`1xa9”‚dÑİøóè÷÷ôêêéêëéûóßéãÌÌÅ­¿³›µª“³¨‘Ê¿¦ÍȹàÞØòìäÿúóèâÛæäâõñíýöåÜϲ¹¨Œ¨˜|§˜|±¡…ȹœÝÏ´ìðÕøûãüúèÁ·¤|mZmZAs`?wd=pH^§œ€Òκöóçüý÷þþþîéãÚÔÐíèåþúöÿûùýùöòìèþüøæäãÜÙÕßÞÚüùóýûõÿÿúþüùþüùÿÿûõ÷ï÷õììãÜÝÔËã×ÈåÙÃêàÍ÷ëßøóéüôíìäâíêéÿÿÿíìé×ÓÌÓÎÊÆÂÅèããÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿþÿÿÿþÿÿÿþÿÿÿþÿÿÿŒÿÿÿÿÿœÿþÿüøðÀð`à?À?À?À€€€À€€€€€€€€Àð üð?ÿÿÿÿÿ( @€€€€€€€€€€ÀÀÀ€€€ÿÿÿÿÿÿÿÿÿÿÿÿÿð÷ˆøˆˆðxˆˆðˆˆˆðˆˆˆøˆˆˆˆˆˆ€ˆÿÿˆˆˆ€ÿ÷ˆˆÿˆˆˆÿÿÿ÷ˆ÷ˆˆˆˆˆˆ‡ðxˆˆ‡xøˆˆˆ÷÷ÿðøˆˆ÷øˆˆˆˆø‡xˆˆøxˆˆˆ‡ÿ€ˆˆ‡xˆ÷ˆˆˆ‡øˆˆˆˆˆˆˆxˆˆˆˆˆˆˆøøˆˆˆˆˆˆˆøxˆˆˆˆˆˆˆˆðˆˆˆˆˆˆˆˆˆð€ˆˆˆˆ÷ˆˆˆˆˆˆ÷ˆøˆˆˆˆˆˆˆÿˆˆˆˆ÷÷ˆˆˆˆˆˆˆˆˆˆˆøˆˆˆˆ‡xˆˆˆˆˆˆøˆˆˆˆˆøˆˆˆˆˆˆˆøˆˆˆˆˆˆˆˆøˆˆ÷ˆˆˆˆxˆˆˆˆˆˆðxˆ‡ÿÿxˆøˆˆÿÿÿ÷ÿˆÿÿÿ€ˆÿÿ‡ÿÿÿÿÿþÿÿþÿÿüÿÿüÿÿüÿÿüÿ€øðàààÀÀ€€€€Ààð<0?pgagent-pgagent-4.2.3/job.cpp000066400000000000000000000241551473675055000160710ustar00rootroot00000000000000////////////////////////////////////////////////////////////////////////// // // pgAgent - PostgreSQL Tools // // Copyright (C) 2002 - 2024, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // // job.cpp - pgAgent job // ////////////////////////////////////////////////////////////////////////// #include "pgAgent.h" #include #if !BOOST_OS_WINDOWS #include #include #include #endif Job::Job(DBconn *conn, const std::string &jid) { m_threadConn = conn; m_jobid = jid; m_status = ""; LogMessage("Starting job: " + m_jobid, LOG_DEBUG); int rc = m_threadConn->ExecuteVoid( "UPDATE pgagent.pga_job SET jobagentid=" + backendPid + ", joblastrun=now() WHERE jobagentid IS NULL AND jobid=" + m_jobid ); if (rc == 1) { DBresultPtr id = m_threadConn->Execute( "SELECT nextval('pgagent.pga_joblog_jlgid_seq') AS id" ); if (id) { m_logid = id->GetString("id"); DBresultPtr res = m_threadConn->Execute( "INSERT INTO pgagent.pga_joblog(jlgid, jlgjobid, jlgstatus) " "VALUES (" + m_logid + ", " + m_jobid + ", 'r')"); if (res) { m_status = "r"; } } } } Job::~Job() { if (!m_status.empty()) { m_threadConn->ExecuteVoid( "UPDATE pgagent.pga_joblog " " SET jlgstatus='" + m_status + "', jlgduration=now() - jlgstart " + " WHERE jlgid=" + m_logid + ";\n" + "UPDATE pgagent.pga_job " + " SET jobagentid=NULL, jobnextrun=NULL " + " WHERE jobid=" + m_jobid ); } m_threadConn->Return(); LogMessage("Completed job: " + m_jobid, LOG_DEBUG); } int Job::Execute() { int rc = 0; bool succeeded = false; DBresultPtr steps = m_threadConn->Execute( "SELECT * " " FROM pgagent.pga_jobstep " " WHERE jstenabled " " AND jstjobid=" + m_jobid + " ORDER BY jstname, jstid"); if (!steps) { LogMessage("No steps found for jobid " + m_jobid, LOG_WARNING); m_status = "i"; return -1; } while (steps->HasData()) { DBconn *stepConn = nullptr; std::string jslid, stepid, jpecode, output; stepid = steps->GetString("jstid"); DBresultPtr id = m_threadConn->Execute( "SELECT nextval('pgagent.pga_jobsteplog_jslid_seq') AS id" ); if (id) { jslid = id->GetString("id"); DBresultPtr res = m_threadConn->Execute( "INSERT INTO pgagent.pga_jobsteplog(jslid, jsljlgid, jsljstid, jslstatus) " "SELECT " + jslid + ", " + m_logid + ", " + stepid + ", 'r'" + " FROM pgagent.pga_jobstep WHERE jstid=" + stepid); if (res) { rc = res->RowsAffected(); LogMessage("Number of rows affected for jobid " + m_jobid, LOG_DEBUG); } else rc = -1; } if (rc != 1) { LogMessage("Value of rc is " + std::to_string(rc) + " for job " + m_jobid, LOG_WARNING); m_status = "i"; return -1; } switch ((int)steps->GetString("jstkind")[0]) { case 's': { std::string jstdbname = steps->GetString("jstdbname"); std::string jstconnstr = steps->GetString("jstconnstr"); stepConn = DBconn::Get(jstconnstr, jstdbname); if (stepConn) { LogMessage( "Executing SQL step " + stepid + "(part of job " + m_jobid + ")", LOG_DEBUG ); rc = stepConn->ExecuteVoid(steps->GetString("jstcode")); succeeded = stepConn->LastCommandOk(); output = stepConn->GetLastError(); stepConn->Return(); } else { output = "Couldn't get a connection to the database!"; succeeded = false; } break; } case 'b': { // Batch jobs are more complex thank SQL, for obvious reasons... LogMessage( "Executing batch step" + stepid + "(part of job " + m_jobid + ")", LOG_DEBUG ); namespace fs = boost::filesystem; // Generate unique temporary directory std::string prefix = ( boost::format("pga_%s_%s_") % m_jobid % stepid ).str(); fs::path jobDir; fs::path filepath(( boost::format("%s_%s.%s") % m_jobid % stepid % #if BOOST_OS_WINDOWS ".bat" #else ".scr" #endif ).str()); fs::path errorFilePath( (boost::format("%s_%s_error.txt") % m_jobid % stepid).str() ); if (!createUniqueTemporaryDirectory(prefix, jobDir)) { output = "Couldn't get a temporary filename!"; LogMessage(output, LOG_WARNING); rc = -1; break; } filepath = jobDir / filepath; errorFilePath = jobDir / errorFilePath; std::string filename = filepath.string(); std::string errorFile = errorFilePath.string(); std::string code = steps->GetString("jstcode"); // Cleanup the code. If we're on Windows, we need to make all line ends \r\n, // If we're on Unix, we need \n boost::replace_all(code, "\r\n", "\n"); #if BOOST_OS_WINDOWS boost::replace_all(code, "\n", "\r\n"); #endif std::ofstream out_file; out_file.open((const char *)filename.c_str(), std::ios::out); if (out_file.fail()) { LogMessage( "Couldn't open temporary script file: " + filename, LOG_WARNING ); if (boost::filesystem::exists(jobDir)) boost::filesystem::remove_all(jobDir); rc = -1; break; } else { out_file << code; out_file.close(); #if !BOOST_OS_WINDOWS // Change file permission to 700 for executable in linux try { boost::filesystem::permissions( filepath, boost::filesystem::owner_all ); } catch (const fs::filesystem_error &ex) { LogMessage( "Error setting executable permission to file: " + filename, LOG_DEBUG ); } #endif } LogMessage("Executing script file: " + filename, LOG_DEBUG); // freopen function is used to redirect output of stream (stderr in our case) // into the specified file. FILE *fpError = freopen((const char *)errorFile.c_str(), "w", stderr); // Execute the file and capture the output #if BOOST_OS_WINDOWS // The Windows way HANDLE h_script, h_process; DWORD dwRead; char chBuf[4098]; h_script = win32_popen_r(s2ws(filename).c_str(), h_process); if (!h_script) { LogMessage((boost::format( "Couldn't execute script: %s, GetLastError() returned %d, errno = %d" ) % filename.c_str() % GetLastError() % errno).str(), LOG_WARNING); CloseHandle(h_process); rc = -1; if (fpError) fclose(fpError); break; } // Read output from the child process if (h_script) { for (;;) { if (!ReadFile(h_script, chBuf, 4096, &dwRead, NULL) || dwRead == 0) break; chBuf[dwRead] = 0; output += (const char *)chBuf; } } GetExitCodeProcess(h_process, (LPDWORD)&rc); CloseHandle(h_process); CloseHandle(h_script); #else // The *nix way. FILE *fp_script = nullptr; char buf[4098]; fp_script = popen((const char *)filename.c_str(), "r"); if (!fp_script) { LogMessage((boost::format( "Couldn't execute script: %s, errno = %d" ) % filename.c_str() % errno).str(), LOG_WARNING); rc = -1; if(fpError) fclose(fpError); break; } while(!feof(fp_script)) { if (fgets(buf, 4096, fp_script) != NULL) output += (const char *)buf; } rc = pclose(fp_script); if (WIFEXITED(rc)) rc = WEXITSTATUS(rc); else rc = -1; #endif // set success status for batch runs, be pessimistic by default LogMessage( (boost::format("Script return code: %d") % rc).str(), LOG_DEBUG ); succeeded = ((rc == 0) ? true : false); // If output is empty then either script did not return any output // or script threw some error into stderr. // Check script threw some error into stderr if (fpError) { fclose(fpError); FILE* fpErr = fopen((const char *)errorFile.c_str(), "r"); if (fpErr) { char buffer[4098]; std::string errorMsg = ""; while (!feof(fpErr)) { if (fgets(buffer, 4096, fpErr) != NULL) errorMsg += buffer; } if (errorMsg != "") { std::string errmsg = "Script Error: \n" + errorMsg + "\n"; LogMessage("Script Error: \n" + errorMsg + "\n", LOG_WARNING); output += "\n" + errmsg; } fclose(fpErr); } } // Delete the file/directory. If we fail, don't overwrite the script // output in the log, just throw warnings. try { if (boost::filesystem::exists(jobDir)) boost::filesystem::remove_all(jobDir); } catch (boost::filesystem::filesystem_error const & e) { //display error message LogMessage((const char *)e.what(), LOG_WARNING); break; } break; } default: { output = "Invalid step type!"; LogMessage("Invalid step type!", LOG_WARNING); m_status = "i"; return -1; } } std::string stepstatus; if (succeeded) stepstatus = "s"; else stepstatus = steps->GetString("jstonerror"); rc = m_threadConn->ExecuteVoid( "UPDATE pgagent.pga_jobsteplog " " SET jslduration = now() - jslstart, " " jslresult = " + NumToStr(rc) + ", jslstatus = '" + stepstatus + "', " + " jsloutput = " + m_threadConn->qtDbString(output) + " " + " WHERE jslid=" + jslid); if (rc != 1 || stepstatus == "f") { m_status = "f"; return -1; } steps->MoveNext(); } m_status = "s"; return 0; } JobThread::JobThread(const std::string &jid) : m_jobid(jid) { LogMessage("Creating job thread for job " + m_jobid, LOG_DEBUG); } JobThread::~JobThread() { LogMessage("Destroying job thread for job " + m_jobid, LOG_DEBUG); } void JobThread::operator()() { DBconn *threadConn = DBconn::Get(); if (threadConn) { Job job(threadConn, m_jobid); if (job.Runnable()) { job.Execute(); } else { LogMessage("Failed to launch the thread for job " + m_jobid + ". Inserting an entry to the joblog table with status 'i'", LOG_WARNING); // Failed to launch the thread. Insert an entry with // "internal error" status in the joblog table, to leave // a trace of fact that we tried to launch the job. DBresultPtr res = threadConn->Execute( "INSERT INTO pgagent.pga_joblog(jlgid, jlgjobid, jlgstatus) " "VALUES (nextval('pgagent.pga_joblog_jlgid_seq'), " + m_jobid + ", 'i')" ); if (res) res = NULL; } } } pgagent-pgagent-4.2.3/misc.cpp000066400000000000000000000101701473675055000162420ustar00rootroot00000000000000////////////////////////////////////////////////////////////////////////// // // pgAgent - PostgreSQL Tools // // Copyright (C) 2002 - 2024, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // // misc.cpp - misc functions // ////////////////////////////////////////////////////////////////////////// #include "pgAgent.h" #include "connection.h" #include #if !BOOST_OS_WINDOWS #include #include #endif #define APPVERSION_STR PGAGENT_VERSION namespace fs = boost::filesystem; // In unix.c or win32.c void usage(const std::string &executable); std::string getArg(int &argc, char **&argv) { std::string res; if (argv[0][2]) return (argv[0] + 2); if (argc >= 1) { argc--; argv++; return argv[0]; } // very bad! LogMessage("Invalid command line argument", LOG_ERROR); return res; } void printVersion() { printf("PostgreSQL Scheduling Agent\n"); printf("Version: %s\n", APPVERSION_STR); }; void setOptions(int argc, char **argv, const std::string &executable) { while (argc-- > 0) { if (argv[0][0] == '-') { switch (argv[0][1]) { case 't': { int val = atoi((const char*)getArg(argc, argv).c_str()); if (val > 0) shortWait = val; break; } case 'r': { int val = atoi((const char*)getArg(argc, argv).c_str()); if (val >= 10) longWait = val; break; } case 'l': { int val = atoi((const char*)getArg(argc, argv).c_str()); if (val >= 0 && val <= 2) minLogLevel = val; break; } case 'v': { printVersion(); exit(0); } #if !BOOST_OS_WINDOWS case 'f': { runInForeground = true; break; } case 's': { logFile = getArg(argc, argv); break; } #endif default: { usage(executable); exit(1); } } } else { if (!connectString.empty()) connectString += " "; connectString += *argv; if (**argv == '"') connectString = connectString.substr(1, connectString.length() - 2); } argv++; } } void WaitAWhile(const bool waitLong) { int count; if (waitLong) count = longWait; else count = shortWait; while (count--) { #ifdef WIN32 CheckForInterrupt(); Sleep(1000); #else sleep(1); #endif } } std::string NumToStr(const long l) { return boost::lexical_cast(l); } #if BOOST_OS_WINDOWS // This function is used to convert const std::str to std::wstring. std::wstring s2ws(const std::string &str) { using boost::locale::conv::utf_to_utf; return utf_to_utf(str.c_str(), str.c_str() + str.size()); } // This function is used to convert std::wstring to std::str std::string ws2s(const std::wstring &wstr) { using boost::locale::conv::utf_to_utf; return utf_to_utf(wstr.c_str(), wstr.c_str() + wstr.size()); } #endif // Below function will generate random string of given character. std::string generateRandomString(size_t length) { char *str = new char[length]; size_t i = 0; int r; str[length - 1] = '\0'; srand(time(NULL)); for(i = 0; i < length - 1; ++i) { for(;;) { // interval between 'A' and 'z' r = rand() % 57 + 65; if((r >= 65 && r <= 90) || (r >= 97 && r <= 122)) { str[i] = (char)r; break; } } } std::string result(str); if (str != NULL) { delete []str; str = NULL; } return result; } bool createUniqueTemporaryDirectory(const std::string &prefix, fs::path &uniqueDir) { const unsigned short MAX_ATTEMPTS = 100; unsigned short attempts = 0; try { fs::path tempDir = fs::temp_directory_path(); do { if (attempts++ >= MAX_ATTEMPTS) return false; uniqueDir = tempDir / fs::unique_path( prefix + "%%%%%%%%-%%%%-%%%%-%%%%-%%%%%%%%%%%%" ); // Check if exists if (boost::filesystem::is_directory(uniqueDir)) continue; // Create the directory securely if (!fs::create_directory(uniqueDir)) { return false; } // Set appropriate permissions (example: owner read/write/execute only) fs::permissions(uniqueDir, boost::filesystem::owner_all); return true; } while (true); } catch (const fs::filesystem_error &ex) { return false; } } pgagent-pgagent-4.2.3/pgAgent.cpp000066400000000000000000000145201473675055000166770ustar00rootroot00000000000000////////////////////////////////////////////////////////////////////////// // // pgAgent - PostgreSQL Tools // // Copyright (C) 2002 - 2024, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // // pgAgent.cpp - pgAgent main entry // ////////////////////////////////////////////////////////////////////////// #include "pgAgent.h" #if !BOOST_OS_WINDOWS #include #endif std::string connectString; std::string backendPid; long longWait = 30; long shortWait = 5; long minLogLevel = LOG_ERROR; using namespace std; #define MAXATTEMPTS 10 #if !BOOST_OS_WINDOWS bool runInForeground = false; std::string logFile; #else // pgAgent Initialized void Initialized(); #endif int MainRestartLoop(DBconn *serviceConn) { // clean up old jobs int rc; LogMessage("Clearing zombies", LOG_DEBUG); rc = serviceConn->ExecuteVoid("CREATE TEMP TABLE pga_tmp_zombies(jagpid int4)"); if (serviceConn->BackendMinimumVersion(9, 2)) { rc = serviceConn->ExecuteVoid( "INSERT INTO pga_tmp_zombies (jagpid) " "SELECT jagpid " " FROM pgagent.pga_jobagent AG " " LEFT JOIN pg_stat_activity PA ON jagpid=pid " " WHERE pid IS NULL" ); } else { rc = serviceConn->ExecuteVoid( "INSERT INTO pga_tmp_zombies (jagpid) " "SELECT jagpid " " FROM pgagent.pga_jobagent AG " " LEFT JOIN pg_stat_activity PA ON jagpid=procpid " " WHERE procpid IS NULL" ); } if (rc > 0) { // There are orphaned agent entries // mark the jobs as aborted rc = serviceConn->ExecuteVoid( "UPDATE pgagent.pga_joblog SET jlgstatus='d' WHERE jlgid IN (" "SELECT jlgid " "FROM pga_tmp_zombies z, pgagent.pga_job j, pgagent.pga_joblog l " "WHERE z.jagpid=j.jobagentid AND j.jobid = l.jlgjobid AND l.jlgstatus='r');\n" "UPDATE pgagent.pga_jobsteplog SET jslstatus='d' WHERE jslid IN ( " "SELECT jslid " "FROM pga_tmp_zombies z, pgagent.pga_job j, pgagent.pga_joblog l, pgagent.pga_jobsteplog s " "WHERE z.jagpid=j.jobagentid AND j.jobid = l.jlgjobid AND l.jlgid = s.jsljlgid AND s.jslstatus='r');\n" "UPDATE pgagent.pga_job SET jobagentid=NULL, jobnextrun=NULL " " WHERE jobagentid IN (SELECT jagpid FROM pga_tmp_zombies);\n" "DELETE FROM pgagent.pga_jobagent " " WHERE jagpid IN (SELECT jagpid FROM pga_tmp_zombies);\n" ); } rc = serviceConn->ExecuteVoid("DROP TABLE pga_tmp_zombies"); std::string host_name = boost::asio::ip::host_name(); rc = serviceConn->ExecuteVoid( "INSERT INTO pgagent.pga_jobagent (jagpid, jagstation) SELECT pg_backend_pid(), '" + host_name + "'" ); if (rc < 0) return rc; while (1) { bool foundJobToExecute = false; LogMessage("Checking for jobs to run", LOG_DEBUG); DBresultPtr res = serviceConn->Execute( "SELECT J.jobid " " FROM pgagent.pga_job J " " WHERE jobenabled " " AND jobagentid IS NULL " " AND jobnextrun <= now() " " AND (jobhostagent = '' OR jobhostagent = '" + host_name + "')" " ORDER BY jobnextrun" ); if (res) { while (res->HasData()) { std::string jobid = res->GetString("jobid"); boost::thread job_thread = boost::thread(JobThread(jobid)); job_thread.detach(); foundJobToExecute = true; res->MoveNext(); } res = NULL; LogMessage("Sleeping...", LOG_DEBUG); WaitAWhile(); } else LogMessage("Failed to query jobs table!", LOG_ERROR); if (!foundJobToExecute) DBconn::ClearConnections(); } return 0; } void MainLoop() { int attemptCount = 1; // OK, let's get down to business do { LogMessage("Creating primary connection", LOG_DEBUG); DBconn *serviceConn = DBconn::InitConnection(connectString); if (serviceConn) { // Basic sanity check, and a chance to get the serviceConn's PID LogMessage("Database sanity check", LOG_DEBUG); DBresultPtr res = serviceConn->Execute( "SELECT count(*) As count, pg_backend_pid() AS pid FROM pg_class cl JOIN pg_namespace ns ON ns.oid=relnamespace WHERE relname='pga_job' AND nspname='pgagent'" ); if (res) { std::string val = res->GetString("count"); if (val == "0") LogMessage( "Could not find the table 'pgagent.pga_job'. Have you run pgagent.sql on this database?", LOG_ERROR ); backendPid = res->GetString("pid"); res = NULL; } // Check for particular version bool hasSchemaVerFunc = false; std::string sqlCheckSchemaVersion = "SELECT COUNT(*) " \ "FROM pg_proc " \ "WHERE proname = 'pgagent_schema_version' AND " \ " pronamespace = (SELECT oid " \ " FROM pg_namespace " \ " WHERE nspname = 'pgagent') AND " \ " prorettype = (SELECT oid " \ " FROM pg_type " \ " WHERE typname = 'int2') AND " \ " proargtypes = '' "; res = serviceConn->Execute(sqlCheckSchemaVersion); if (res) { if (res->IsValid() && res->GetString(0) == "1") hasSchemaVerFunc = true; res = NULL; } if (!hasSchemaVerFunc) { LogMessage( "Couldn't find the function 'pgagent_schema_version' - please run ALTER EXTENSION \"pgagent\" UPDATE;.", LOG_ERROR ); } std::string strPgAgentSchemaVer = serviceConn->ExecuteScalar( "SELECT pgagent.pgagent_schema_version()" ); std::string currentPgAgentVersion = (boost::format("%d") % PGAGENT_VERSION_MAJOR).str(); if (strPgAgentSchemaVer != currentPgAgentVersion) { LogMessage( "Unsupported schema version: " + strPgAgentSchemaVer + ". Version " + currentPgAgentVersion + " is required - please run ALTER EXTENSION \"pgagent\" UPDATE;.", LOG_ERROR ); } #ifdef WIN32 Initialized(); #endif MainRestartLoop(serviceConn); } LogMessage((boost::format( "Couldn't create the primary connection [Attempt #%d]") % attemptCount ).str(), LOG_STARTUP); DBconn::ClearConnections(true); // Try establishing primary connection upto MAXATTEMPTS times if (attemptCount++ >= (int)MAXATTEMPTS) { LogMessage( "Stopping pgAgent: Couldn't establish the primary connection with the database server.", LOG_ERROR ); } WaitAWhile(); } while (1); } pgagent-pgagent-4.2.3/pgAgent.rc000066400000000000000000000026001473675055000165150ustar00rootroot00000000000000////////////////////////////////////////////////////////////////////////// // // pgAgent - PostgreSQL Tools // // Copyright (C) 2002 - 2024, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // // pgAgent.rc - win32 Resources // ////////////////////////////////////////////////////////////////////////// #include // Icon (Don't remove the aaa prefix - it makes it the default icon) aaaPGAGENT ICON DISCARDABLE "include/pgAgent.ico" VS_VERSION_INFO VERSIONINFO FILEVERSION 4,2,3,0 PRODUCTVERSION 4,2,3,0 FILEOS VOS__WINDOWS32 FILETYPE VFT_APP BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904E4" BEGIN VALUE "FileVersion", "4.2.3", "\0" VALUE "File Version", "4.2.3", "\0" VALUE "FileDescription", "pgAgent - PostgreSQL Scheduling Agent", "\0" VALUE "LegalCopyright", "\251 2002 - 2024, The pgAdmin Development Team", "\0" VALUE "LegalTrademarks", "This software is released under the PostgreSQL Licence.", "\0" VALUE "InternalName", "pgAgent", "\0" VALUE "OriginalFilename","pgagent.exe", "\0" VALUE "ProductName", "pgAgent", "\0" VALUE "ProductVersion", "4.2.3", "\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x0409, 0x04E4 END END pgagent-pgagent-4.2.3/pgaevent/000077500000000000000000000000001473675055000164155ustar00rootroot00000000000000pgagent-pgagent-4.2.3/pgaevent/CMakeLists.txt000066400000000000000000000013121473675055000211520ustar00rootroot00000000000000####################################################################### # # pgAgent - PostgreSQL tools # Copyright (C) 2002 - 2024, The pgAdmin Development Team # This software is released under the PostgreSQL Licence # # pgaevent/CMakeLists.txt - CMake build configuration # ####################################################################### ################################################################################ # Let's rock! ################################################################################ IF(WIN32) SET(_srcs pgaevent.c pgaevent.def pgamsgevent.rc) ADD_LIBRARY(pgaevent MODULE ${_srcs}) INSTALL(TARGETS pgaevent DESTINATION .) ENDIF(WIN32) pgagent-pgagent-4.2.3/pgaevent/MSG00001.bin000066400000000000000000000000341473675055000201130ustar00rootroot00000000000000 %1 pgagent-pgagent-4.2.3/pgaevent/README000066400000000000000000000014431473675055000172770ustar00rootroot00000000000000This whole directory is shamelessly adapted from PostgreSQL's src/bin/pgevent Note that to get the version resources etc. into the DLL, the only file generated by MC that we actually use is MSG00001.bin. ------------------------------------------------------------------------------- MSG000001.bin is a binary file, result of Microsoft MC compiler. MC compiler can be downloaded for free with MS Core SDK but it is not included with MSYS tools and I didn't find an alternative way to compile MC file. To summarize: the command "MC pgmsgevent.mc" generates pgmsgevent.h, pgmsgevent.rc, and MSG00001.bin files. In MC file, we declare a string with %s format, so we can write anything we want in the future without needing to change the definition of this string. Laurent Ballester pgagent-pgagent-4.2.3/pgaevent/pgaevent.c000066400000000000000000000014141473675055000203720ustar00rootroot00000000000000////////////////////////////////////////////////////////////////////////// // // pgAgent - PostgreSQL Tools // // Copyright (C) 2002 - 2024, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // // pgaevent.c - win32 message format dll // ////////////////////////////////////////////////////////////////////////// #include #include #include HANDLE g_module = NULL; BOOL WINAPI DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved); // // DllMain --- is an optional entry point into a DLL. // BOOL WINAPI DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { if (ul_reason_for_call == DLL_PROCESS_ATTACH) g_module = hModule; return TRUE; } pgagent-pgagent-4.2.3/pgaevent/pgaevent.def000066400000000000000000000007011473675055000207040ustar00rootroot00000000000000; //////////////////////////////////////////////////////////////////////// ; // ; // pgAgent - PostgreSQL Tools ; // ; // Copyright (C) 2002 - 2024, The pgAdmin Development Team ; // This software is released under the PostgreSQL Licence ; // ; // pgaeventdef - pgaevent.dll exports ; // ; //////////////////////////////////////////////////////////////////////// ; dlltool --output-def pgaevent.def pgaevent.o pgamsgevent.o EXPORTS pgagent-pgagent-4.2.3/pgaevent/pgamsgevent.h000066400000000000000000000006151473675055000211100ustar00rootroot00000000000000////////////////////////////////////////////////////////////////////////// // // pgAgent - PostgreSQL Tools // // Copyright (C) 2002 - 2024, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // // pgamsgevent.h - Message id declarations // ////////////////////////////////////////////////////////////////////////// #define PGADMIN_EVENTLOG_MSG 0x00000000L pgagent-pgagent-4.2.3/pgaevent/pgamsgevent.mc000066400000000000000000000001111473675055000212470ustar00rootroot00000000000000MessageId=0 SymbolicName=PGADMIN_EVENTLOG_MSG Language=English %1 . pgagent-pgagent-4.2.3/pgaevent/pgamsgevent.rc000066400000000000000000000027501473675055000212670ustar00rootroot00000000000000////////////////////////////////////////////////////////////////////////// // // pgAgent - PostgreSQL Tools // // Copyright (C) 2002 - 2024, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // // pgaevent.rc - win32 Resources // ////////////////////////////////////////////////////////////////////////// LANGUAGE 0x9,0x1 1 11 MSG00001.bin #include // Icon (Don't remove the aaa prefix - it makes it the default icon) aaaPGAEVENT ICON DISCARDABLE "../include/pgagent.ico" VS_VERSION_INFO VERSIONINFO FILEVERSION 4,2,3,0 PRODUCTVERSION 4,2,3,0 FILEOS VOS__WINDOWS32 FILETYPE VFT_APP BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904E4" BEGIN VALUE "FileVersion", "4.2.3", "\0" VALUE "File Version", "4.2.3", "\0" VALUE "FileDescription", "pgaevent - pgAgent Event Log Message DLL", "\0" VALUE "LegalCopyright", "\251 2002 - 2024, The pgAdmin Development Team", "\0" VALUE "LegalTrademarks", "This software is released under the PostgreSQL Licence.", "\0" VALUE "InternalName", "pgaevent", "\0" VALUE "OriginalFilename","pgaevent.dll", "\0" VALUE "ProductName", "pgAgent", "\0" VALUE "ProductVersion", "4.2.3", "\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x0409, 0x04E4 END END pgagent-pgagent-4.2.3/pgagent.control.in000066400000000000000000000002511473675055000202360ustar00rootroot00000000000000# pgagent extension comment = 'A PostgreSQL job scheduler' default_version = '${MAJOR_VERSION}.${MINOR_VERSION}' relocatable = false superuser = false schema = pgagent pgagent-pgagent-4.2.3/precomp.cpp000066400000000000000000000005701473675055000167570ustar00rootroot00000000000000////////////////////////////////////////////////////////////////////////// // // pgAgent - PostgreSQL Tools // // Copyright (C) 2002 - 2024, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // // precomp.cpp - pgAgent precompiled headers // ////////////////////////////////////////////////////////////////////////// #include "pgAgent.h" pgagent-pgagent-4.2.3/sql/000077500000000000000000000000001473675055000154035ustar00rootroot00000000000000pgagent-pgagent-4.2.3/sql/pgagent--3.4--4.2.sql000066400000000000000000000357611473675055000205220ustar00rootroot00000000000000/* // pgAgent - PostgreSQL Tools // // Copyright (C) 2002 - 2024, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // // pgagent--3.4--4.2.sql - Upgrade the pgAgent schema to 4.2 // */ \echo Use "ALTER EXTENSION pgagent UPDATE" to load this file. \quit CREATE OR REPLACE FUNCTION pgagent.pgagent_schema_version() RETURNS int2 AS ' BEGIN -- RETURNS PGAGENT MAJOR VERSION -- WE WILL CHANGE THE MAJOR VERSION, ONLY IF THERE IS A SCHEMA CHANGE RETURN 4; END; ' LANGUAGE 'plpgsql' VOLATILE; CREATE OR REPLACE FUNCTION pgagent.pga_next_schedule(int4, timestamptz, timestamptz, _bool, _bool, _bool, _bool, _bool) RETURNS timestamptz AS ' DECLARE jscid ALIAS FOR $1; jscstart ALIAS FOR $2; jscend ALIAS FOR $3; jscminutes ALIAS FOR $4; jschours ALIAS FOR $5; jscweekdays ALIAS FOR $6; jscmonthdays ALIAS FOR $7; jscmonths ALIAS FOR $8; nextrun timestamp := ''1970-01-01 00:00:00-00''; runafter timestamp := ''1970-01-01 00:00:00-00''; bingo bool := FALSE; gotit bool := FALSE; foundval bool := FALSE; daytweak bool := FALSE; minutetweak bool := FALSE; i int2 := 0; d int2 := 0; nextminute int2 := 0; nexthour int2 := 0; nextday int2 := 0; nextmonth int2 := 0; nextyear int2 := 0; BEGIN -- No valid start date has been specified IF jscstart IS NULL THEN RETURN NULL; END IF; -- The schedule is past its end date IF jscend IS NOT NULL AND jscend < now() THEN RETURN NULL; END IF; -- Get the time to find the next run after. It will just be the later of -- now() + 1m and the start date for the time being, however, we might want to -- do more complex things using this value in the future. IF date_trunc(''MINUTE'', jscstart) > date_trunc(''MINUTE'', (now() + ''1 Minute''::interval)) THEN runafter := date_trunc(''MINUTE'', jscstart); ELSE runafter := date_trunc(''MINUTE'', (now() + ''1 Minute''::interval)); END IF; -- -- Enter a loop, generating next run timestamps until we find one -- that falls on the required weekday, and is not matched by an exception -- WHILE bingo = FALSE LOOP -- -- Get the next run year -- nextyear := date_part(''YEAR'', runafter); -- -- Get the next run month -- nextmonth := date_part(''MONTH'', runafter); gotit := FALSE; FOR i IN (nextmonth) .. 12 LOOP IF jscmonths[i] = TRUE THEN nextmonth := i; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; IF gotit = FALSE THEN FOR i IN 1 .. (nextmonth - 1) LOOP IF jscmonths[i] = TRUE THEN nextmonth := i; -- Wrap into next year nextyear := nextyear + 1; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; END IF; -- -- Get the next run day -- -- If the year, or month have incremented, get the lowest day, -- otherwise look for the next day matching or after today. IF (nextyear > date_part(''YEAR'', runafter) OR nextmonth > date_part(''MONTH'', runafter)) THEN nextday := 1; FOR i IN 1 .. 32 LOOP IF jscmonthdays[i] = TRUE THEN nextday := i; foundval := TRUE; EXIT; END IF; END LOOP; ELSE nextday := date_part(''DAY'', runafter); gotit := FALSE; FOR i IN nextday .. 32 LOOP IF jscmonthdays[i] = TRUE THEN nextday := i; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; IF gotit = FALSE THEN FOR i IN 1 .. (nextday - 1) LOOP IF jscmonthdays[i] = TRUE THEN nextday := i; -- Wrap into next month IF nextmonth = 12 THEN nextyear := nextyear + 1; nextmonth := 1; ELSE nextmonth := nextmonth + 1; END IF; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; END IF; END IF; -- Was the last day flag selected? IF nextday = 32 THEN IF nextmonth = 1 THEN nextday := 31; ELSIF nextmonth = 2 THEN IF pgagent.pga_is_leap_year(nextyear) = TRUE THEN nextday := 29; ELSE nextday := 28; END IF; ELSIF nextmonth = 3 THEN nextday := 31; ELSIF nextmonth = 4 THEN nextday := 30; ELSIF nextmonth = 5 THEN nextday := 31; ELSIF nextmonth = 6 THEN nextday := 30; ELSIF nextmonth = 7 THEN nextday := 31; ELSIF nextmonth = 8 THEN nextday := 31; ELSIF nextmonth = 9 THEN nextday := 30; ELSIF nextmonth = 10 THEN nextday := 31; ELSIF nextmonth = 11 THEN nextday := 30; ELSIF nextmonth = 12 THEN nextday := 31; END IF; END IF; -- -- Get the next run hour -- -- If the year, month or day have incremented, get the lowest hour, -- otherwise look for the next hour matching or after the current one. IF (nextyear > date_part(''YEAR'', runafter) OR nextmonth > date_part(''MONTH'', runafter) OR nextday > date_part(''DAY'', runafter) OR daytweak = TRUE) THEN nexthour := 0; FOR i IN 1 .. 24 LOOP IF jschours[i] = TRUE THEN nexthour := i - 1; foundval := TRUE; EXIT; END IF; END LOOP; ELSE nexthour := date_part(''HOUR'', runafter); gotit := FALSE; FOR i IN (nexthour + 1) .. 24 LOOP IF jschours[i] = TRUE THEN nexthour := i - 1; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; IF gotit = FALSE THEN FOR i IN 1 .. nexthour LOOP IF jschours[i] = TRUE THEN nexthour := i - 1; -- Wrap into next month IF (nextmonth = 1 OR nextmonth = 3 OR nextmonth = 5 OR nextmonth = 7 OR nextmonth = 8 OR nextmonth = 10 OR nextmonth = 12) THEN d = 31; ELSIF (nextmonth = 4 OR nextmonth = 6 OR nextmonth = 9 OR nextmonth = 11) THEN d = 30; ELSE IF pgagent.pga_is_leap_year(nextyear) = TRUE THEN d := 29; ELSE d := 28; END IF; END IF; IF nextday = d THEN nextday := 1; IF nextmonth = 12 THEN nextyear := nextyear + 1; nextmonth := 1; ELSE nextmonth := nextmonth + 1; END IF; ELSE nextday := nextday + 1; END IF; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; END IF; END IF; -- -- Get the next run minute -- -- If the year, month day or hour have incremented, get the lowest minute, -- otherwise look for the next minute matching or after the current one. IF (nextyear > date_part(''YEAR'', runafter) OR nextmonth > date_part(''MONTH'', runafter) OR nextday > date_part(''DAY'', runafter) OR nexthour > date_part(''HOUR'', runafter) OR daytweak = TRUE) THEN nextminute := 0; IF minutetweak = TRUE THEN d := 1; ELSE d := date_part(''MINUTE'', runafter)::int2; END IF; FOR i IN d .. 60 LOOP IF jscminutes[i] = TRUE THEN nextminute := i - 1; foundval := TRUE; EXIT; END IF; END LOOP; ELSE nextminute := date_part(''MINUTE'', runafter); gotit := FALSE; FOR i IN (nextminute + 1) .. 60 LOOP IF jscminutes[i] = TRUE THEN nextminute := i - 1; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; IF gotit = FALSE THEN FOR i IN 1 .. nextminute LOOP IF jscminutes[i] = TRUE THEN nextminute := i - 1; -- Wrap into next hour IF (nextmonth = 1 OR nextmonth = 3 OR nextmonth = 5 OR nextmonth = 7 OR nextmonth = 8 OR nextmonth = 10 OR nextmonth = 12) THEN d = 31; ELSIF (nextmonth = 4 OR nextmonth = 6 OR nextmonth = 9 OR nextmonth = 11) THEN d = 30; ELSE IF pgagent.pga_is_leap_year(nextyear) = TRUE THEN d := 29; ELSE d := 28; END IF; END IF; IF nexthour = 23 THEN nexthour = 0; IF nextday = d THEN nextday := 1; IF nextmonth = 12 THEN nextyear := nextyear + 1; nextmonth := 1; ELSE nextmonth := nextmonth + 1; END IF; ELSE nextday := nextday + 1; END IF; ELSE nexthour := nexthour + 1; END IF; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; END IF; END IF; -- Build the result, and check it is not the same as runafter - this may -- happen if all array entries are set to false. In this case, add a minute. nextrun := (nextyear::varchar || ''-''::varchar || nextmonth::varchar || ''-'' || nextday::varchar || '' '' || nexthour::varchar || '':'' || nextminute::varchar)::timestamptz; IF nextrun = runafter AND foundval = FALSE THEN nextrun := nextrun + INTERVAL ''1 Minute''; END IF; -- If the result is past the end date, exit. IF nextrun > jscend THEN RETURN NULL; END IF; -- Check to ensure that the nextrun time is actually still valid. Its -- possible that wrapped values may have carried the nextrun onto an -- invalid time or date. IF ((jscminutes = ''{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f}'' OR jscminutes[date_part(''MINUTE'', nextrun) + 1] = TRUE) AND (jschours = ''{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f}'' OR jschours[date_part(''HOUR'', nextrun) + 1] = TRUE) AND (jscmonthdays = ''{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f}'' OR jscmonthdays[date_part(''DAY'', nextrun)] = TRUE OR (jscmonthdays = ''{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,t}'' AND ((date_part(''MONTH'', nextrun) IN (1,3,5,7,8,10,12) AND date_part(''DAY'', nextrun) = 31) OR (date_part(''MONTH'', nextrun) IN (4,6,9,11) AND date_part(''DAY'', nextrun) = 30) OR (date_part(''MONTH'', nextrun) = 2 AND ((pgagent.pga_is_leap_year(date_part(''YEAR'', nextrun)::int2) AND date_part(''DAY'', nextrun) = 29) OR date_part(''DAY'', nextrun) = 28))))) AND (jscmonths = ''{f,f,f,f,f,f,f,f,f,f,f,f}'' OR jscmonths[date_part(''MONTH'', nextrun)] = TRUE)) THEN -- Now, check to see if the nextrun time found is a) on an acceptable -- weekday, and b) not matched by an exception. If not, set -- runafter = nextrun and try again. -- Check for a wildcard weekday gotit := FALSE; FOR i IN 1 .. 7 LOOP IF jscweekdays[i] = TRUE THEN gotit := TRUE; EXIT; END IF; END LOOP; -- OK, is the correct weekday selected, or a wildcard? IF (jscweekdays[date_part(''DOW'', nextrun) + 1] = TRUE OR gotit = FALSE) THEN -- Check for exceptions SELECT INTO d jexid FROM pgagent.pga_exception WHERE jexscid = jscid AND ((jexdate = nextrun::date AND jextime = nextrun::time) OR (jexdate = nextrun::date AND jextime IS NULL) OR (jexdate IS NULL AND jextime = nextrun::time)); IF FOUND THEN -- Nuts - found an exception. Increment the time and try again runafter := nextrun + INTERVAL ''1 Minute''; bingo := FALSE; minutetweak := TRUE; daytweak := FALSE; ELSE bingo := TRUE; END IF; ELSE -- We''re on the wrong week day - increment a day and try again. runafter := nextrun + INTERVAL ''1 Day''; bingo := FALSE; minutetweak := FALSE; daytweak := TRUE; END IF; ELSE runafter := nextrun + INTERVAL ''1 Minute''; bingo := FALSE; minutetweak := TRUE; daytweak := FALSE; END IF; END LOOP; RETURN nextrun; END; ' LANGUAGE 'plpgsql' VOLATILE; COMMENT ON FUNCTION pgagent.pga_next_schedule(int4, timestamptz, timestamptz, _bool, _bool, _bool, _bool, _bool) IS 'Calculates the next runtime for a given schedule'; pgagent-pgagent-4.2.3/sql/pgagent--4.0--4.2.sql000066400000000000000000000353731473675055000205160ustar00rootroot00000000000000/* // pgAgent - PostgreSQL Tools // // Copyright (C) 2002 - 2024, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // // pgagent--4.0--4.2.sql - Upgrade the pgAgent schema to 4.2 // */ \echo Use "ALTER EXTENSION pgagent UPDATE" to load this file. \quit CREATE OR REPLACE FUNCTION pgagent.pga_next_schedule(int4, timestamptz, timestamptz, _bool, _bool, _bool, _bool, _bool) RETURNS timestamptz AS ' DECLARE jscid ALIAS FOR $1; jscstart ALIAS FOR $2; jscend ALIAS FOR $3; jscminutes ALIAS FOR $4; jschours ALIAS FOR $5; jscweekdays ALIAS FOR $6; jscmonthdays ALIAS FOR $7; jscmonths ALIAS FOR $8; nextrun timestamp := ''1970-01-01 00:00:00-00''; runafter timestamp := ''1970-01-01 00:00:00-00''; bingo bool := FALSE; gotit bool := FALSE; foundval bool := FALSE; daytweak bool := FALSE; minutetweak bool := FALSE; i int2 := 0; d int2 := 0; nextminute int2 := 0; nexthour int2 := 0; nextday int2 := 0; nextmonth int2 := 0; nextyear int2 := 0; BEGIN -- No valid start date has been specified IF jscstart IS NULL THEN RETURN NULL; END IF; -- The schedule is past its end date IF jscend IS NOT NULL AND jscend < now() THEN RETURN NULL; END IF; -- Get the time to find the next run after. It will just be the later of -- now() + 1m and the start date for the time being, however, we might want to -- do more complex things using this value in the future. IF date_trunc(''MINUTE'', jscstart) > date_trunc(''MINUTE'', (now() + ''1 Minute''::interval)) THEN runafter := date_trunc(''MINUTE'', jscstart); ELSE runafter := date_trunc(''MINUTE'', (now() + ''1 Minute''::interval)); END IF; -- -- Enter a loop, generating next run timestamps until we find one -- that falls on the required weekday, and is not matched by an exception -- WHILE bingo = FALSE LOOP -- -- Get the next run year -- nextyear := date_part(''YEAR'', runafter); -- -- Get the next run month -- nextmonth := date_part(''MONTH'', runafter); gotit := FALSE; FOR i IN (nextmonth) .. 12 LOOP IF jscmonths[i] = TRUE THEN nextmonth := i; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; IF gotit = FALSE THEN FOR i IN 1 .. (nextmonth - 1) LOOP IF jscmonths[i] = TRUE THEN nextmonth := i; -- Wrap into next year nextyear := nextyear + 1; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; END IF; -- -- Get the next run day -- -- If the year, or month have incremented, get the lowest day, -- otherwise look for the next day matching or after today. IF (nextyear > date_part(''YEAR'', runafter) OR nextmonth > date_part(''MONTH'', runafter)) THEN nextday := 1; FOR i IN 1 .. 32 LOOP IF jscmonthdays[i] = TRUE THEN nextday := i; foundval := TRUE; EXIT; END IF; END LOOP; ELSE nextday := date_part(''DAY'', runafter); gotit := FALSE; FOR i IN nextday .. 32 LOOP IF jscmonthdays[i] = TRUE THEN nextday := i; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; IF gotit = FALSE THEN FOR i IN 1 .. (nextday - 1) LOOP IF jscmonthdays[i] = TRUE THEN nextday := i; -- Wrap into next month IF nextmonth = 12 THEN nextyear := nextyear + 1; nextmonth := 1; ELSE nextmonth := nextmonth + 1; END IF; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; END IF; END IF; -- Was the last day flag selected? IF nextday = 32 THEN IF nextmonth = 1 THEN nextday := 31; ELSIF nextmonth = 2 THEN IF pgagent.pga_is_leap_year(nextyear) = TRUE THEN nextday := 29; ELSE nextday := 28; END IF; ELSIF nextmonth = 3 THEN nextday := 31; ELSIF nextmonth = 4 THEN nextday := 30; ELSIF nextmonth = 5 THEN nextday := 31; ELSIF nextmonth = 6 THEN nextday := 30; ELSIF nextmonth = 7 THEN nextday := 31; ELSIF nextmonth = 8 THEN nextday := 31; ELSIF nextmonth = 9 THEN nextday := 30; ELSIF nextmonth = 10 THEN nextday := 31; ELSIF nextmonth = 11 THEN nextday := 30; ELSIF nextmonth = 12 THEN nextday := 31; END IF; END IF; -- -- Get the next run hour -- -- If the year, month or day have incremented, get the lowest hour, -- otherwise look for the next hour matching or after the current one. IF (nextyear > date_part(''YEAR'', runafter) OR nextmonth > date_part(''MONTH'', runafter) OR nextday > date_part(''DAY'', runafter) OR daytweak = TRUE) THEN nexthour := 0; FOR i IN 1 .. 24 LOOP IF jschours[i] = TRUE THEN nexthour := i - 1; foundval := TRUE; EXIT; END IF; END LOOP; ELSE nexthour := date_part(''HOUR'', runafter); gotit := FALSE; FOR i IN (nexthour + 1) .. 24 LOOP IF jschours[i] = TRUE THEN nexthour := i - 1; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; IF gotit = FALSE THEN FOR i IN 1 .. nexthour LOOP IF jschours[i] = TRUE THEN nexthour := i - 1; -- Wrap into next month IF (nextmonth = 1 OR nextmonth = 3 OR nextmonth = 5 OR nextmonth = 7 OR nextmonth = 8 OR nextmonth = 10 OR nextmonth = 12) THEN d = 31; ELSIF (nextmonth = 4 OR nextmonth = 6 OR nextmonth = 9 OR nextmonth = 11) THEN d = 30; ELSE IF pgagent.pga_is_leap_year(nextyear) = TRUE THEN d := 29; ELSE d := 28; END IF; END IF; IF nextday = d THEN nextday := 1; IF nextmonth = 12 THEN nextyear := nextyear + 1; nextmonth := 1; ELSE nextmonth := nextmonth + 1; END IF; ELSE nextday := nextday + 1; END IF; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; END IF; END IF; -- -- Get the next run minute -- -- If the year, month day or hour have incremented, get the lowest minute, -- otherwise look for the next minute matching or after the current one. IF (nextyear > date_part(''YEAR'', runafter) OR nextmonth > date_part(''MONTH'', runafter) OR nextday > date_part(''DAY'', runafter) OR nexthour > date_part(''HOUR'', runafter) OR daytweak = TRUE) THEN nextminute := 0; IF minutetweak = TRUE THEN d := 1; ELSE d := date_part(''MINUTE'', runafter)::int2; END IF; FOR i IN d .. 60 LOOP IF jscminutes[i] = TRUE THEN nextminute := i - 1; foundval := TRUE; EXIT; END IF; END LOOP; ELSE nextminute := date_part(''MINUTE'', runafter); gotit := FALSE; FOR i IN (nextminute + 1) .. 60 LOOP IF jscminutes[i] = TRUE THEN nextminute := i - 1; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; IF gotit = FALSE THEN FOR i IN 1 .. nextminute LOOP IF jscminutes[i] = TRUE THEN nextminute := i - 1; -- Wrap into next hour IF (nextmonth = 1 OR nextmonth = 3 OR nextmonth = 5 OR nextmonth = 7 OR nextmonth = 8 OR nextmonth = 10 OR nextmonth = 12) THEN d = 31; ELSIF (nextmonth = 4 OR nextmonth = 6 OR nextmonth = 9 OR nextmonth = 11) THEN d = 30; ELSE IF pgagent.pga_is_leap_year(nextyear) = TRUE THEN d := 29; ELSE d := 28; END IF; END IF; IF nexthour = 23 THEN nexthour = 0; IF nextday = d THEN nextday := 1; IF nextmonth = 12 THEN nextyear := nextyear + 1; nextmonth := 1; ELSE nextmonth := nextmonth + 1; END IF; ELSE nextday := nextday + 1; END IF; ELSE nexthour := nexthour + 1; END IF; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; END IF; END IF; -- Build the result, and check it is not the same as runafter - this may -- happen if all array entries are set to false. In this case, add a minute. nextrun := (nextyear::varchar || ''-''::varchar || nextmonth::varchar || ''-'' || nextday::varchar || '' '' || nexthour::varchar || '':'' || nextminute::varchar)::timestamptz; IF nextrun = runafter AND foundval = FALSE THEN nextrun := nextrun + INTERVAL ''1 Minute''; END IF; -- If the result is past the end date, exit. IF nextrun > jscend THEN RETURN NULL; END IF; -- Check to ensure that the nextrun time is actually still valid. Its -- possible that wrapped values may have carried the nextrun onto an -- invalid time or date. IF ((jscminutes = ''{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f}'' OR jscminutes[date_part(''MINUTE'', nextrun) + 1] = TRUE) AND (jschours = ''{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f}'' OR jschours[date_part(''HOUR'', nextrun) + 1] = TRUE) AND (jscmonthdays = ''{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f}'' OR jscmonthdays[date_part(''DAY'', nextrun)] = TRUE OR (jscmonthdays = ''{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,t}'' AND ((date_part(''MONTH'', nextrun) IN (1,3,5,7,8,10,12) AND date_part(''DAY'', nextrun) = 31) OR (date_part(''MONTH'', nextrun) IN (4,6,9,11) AND date_part(''DAY'', nextrun) = 30) OR (date_part(''MONTH'', nextrun) = 2 AND ((pgagent.pga_is_leap_year(date_part(''YEAR'', nextrun)::int2) AND date_part(''DAY'', nextrun) = 29) OR date_part(''DAY'', nextrun) = 28))))) AND (jscmonths = ''{f,f,f,f,f,f,f,f,f,f,f,f}'' OR jscmonths[date_part(''MONTH'', nextrun)] = TRUE)) THEN -- Now, check to see if the nextrun time found is a) on an acceptable -- weekday, and b) not matched by an exception. If not, set -- runafter = nextrun and try again. -- Check for a wildcard weekday gotit := FALSE; FOR i IN 1 .. 7 LOOP IF jscweekdays[i] = TRUE THEN gotit := TRUE; EXIT; END IF; END LOOP; -- OK, is the correct weekday selected, or a wildcard? IF (jscweekdays[date_part(''DOW'', nextrun) + 1] = TRUE OR gotit = FALSE) THEN -- Check for exceptions SELECT INTO d jexid FROM pgagent.pga_exception WHERE jexscid = jscid AND ((jexdate = nextrun::date AND jextime = nextrun::time) OR (jexdate = nextrun::date AND jextime IS NULL) OR (jexdate IS NULL AND jextime = nextrun::time)); IF FOUND THEN -- Nuts - found an exception. Increment the time and try again runafter := nextrun + INTERVAL ''1 Minute''; bingo := FALSE; minutetweak := TRUE; daytweak := FALSE; ELSE bingo := TRUE; END IF; ELSE -- We''re on the wrong week day - increment a day and try again. runafter := nextrun + INTERVAL ''1 Day''; bingo := FALSE; minutetweak := FALSE; daytweak := TRUE; END IF; ELSE runafter := nextrun + INTERVAL ''1 Minute''; bingo := FALSE; minutetweak := TRUE; daytweak := FALSE; END IF; END LOOP; RETURN nextrun; END; ' LANGUAGE 'plpgsql' VOLATILE; COMMENT ON FUNCTION pgagent.pga_next_schedule(int4, timestamptz, timestamptz, _bool, _bool, _bool, _bool, _bool) IS 'Calculates the next runtime for a given schedule'; pgagent-pgagent-4.2.3/sql/pgagent--4.1--4.2.sql000066400000000000000000000353731473675055000205170ustar00rootroot00000000000000/* // pgAgent - PostgreSQL Tools // // Copyright (C) 2002 - 2024, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // // pgagent--4.1--4.2.sql - Upgrade the pgAgent schema to 4.2 // */ \echo Use "ALTER EXTENSION pgagent UPDATE" to load this file. \quit CREATE OR REPLACE FUNCTION pgagent.pga_next_schedule(int4, timestamptz, timestamptz, _bool, _bool, _bool, _bool, _bool) RETURNS timestamptz AS ' DECLARE jscid ALIAS FOR $1; jscstart ALIAS FOR $2; jscend ALIAS FOR $3; jscminutes ALIAS FOR $4; jschours ALIAS FOR $5; jscweekdays ALIAS FOR $6; jscmonthdays ALIAS FOR $7; jscmonths ALIAS FOR $8; nextrun timestamp := ''1970-01-01 00:00:00-00''; runafter timestamp := ''1970-01-01 00:00:00-00''; bingo bool := FALSE; gotit bool := FALSE; foundval bool := FALSE; daytweak bool := FALSE; minutetweak bool := FALSE; i int2 := 0; d int2 := 0; nextminute int2 := 0; nexthour int2 := 0; nextday int2 := 0; nextmonth int2 := 0; nextyear int2 := 0; BEGIN -- No valid start date has been specified IF jscstart IS NULL THEN RETURN NULL; END IF; -- The schedule is past its end date IF jscend IS NOT NULL AND jscend < now() THEN RETURN NULL; END IF; -- Get the time to find the next run after. It will just be the later of -- now() + 1m and the start date for the time being, however, we might want to -- do more complex things using this value in the future. IF date_trunc(''MINUTE'', jscstart) > date_trunc(''MINUTE'', (now() + ''1 Minute''::interval)) THEN runafter := date_trunc(''MINUTE'', jscstart); ELSE runafter := date_trunc(''MINUTE'', (now() + ''1 Minute''::interval)); END IF; -- -- Enter a loop, generating next run timestamps until we find one -- that falls on the required weekday, and is not matched by an exception -- WHILE bingo = FALSE LOOP -- -- Get the next run year -- nextyear := date_part(''YEAR'', runafter); -- -- Get the next run month -- nextmonth := date_part(''MONTH'', runafter); gotit := FALSE; FOR i IN (nextmonth) .. 12 LOOP IF jscmonths[i] = TRUE THEN nextmonth := i; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; IF gotit = FALSE THEN FOR i IN 1 .. (nextmonth - 1) LOOP IF jscmonths[i] = TRUE THEN nextmonth := i; -- Wrap into next year nextyear := nextyear + 1; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; END IF; -- -- Get the next run day -- -- If the year, or month have incremented, get the lowest day, -- otherwise look for the next day matching or after today. IF (nextyear > date_part(''YEAR'', runafter) OR nextmonth > date_part(''MONTH'', runafter)) THEN nextday := 1; FOR i IN 1 .. 32 LOOP IF jscmonthdays[i] = TRUE THEN nextday := i; foundval := TRUE; EXIT; END IF; END LOOP; ELSE nextday := date_part(''DAY'', runafter); gotit := FALSE; FOR i IN nextday .. 32 LOOP IF jscmonthdays[i] = TRUE THEN nextday := i; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; IF gotit = FALSE THEN FOR i IN 1 .. (nextday - 1) LOOP IF jscmonthdays[i] = TRUE THEN nextday := i; -- Wrap into next month IF nextmonth = 12 THEN nextyear := nextyear + 1; nextmonth := 1; ELSE nextmonth := nextmonth + 1; END IF; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; END IF; END IF; -- Was the last day flag selected? IF nextday = 32 THEN IF nextmonth = 1 THEN nextday := 31; ELSIF nextmonth = 2 THEN IF pgagent.pga_is_leap_year(nextyear) = TRUE THEN nextday := 29; ELSE nextday := 28; END IF; ELSIF nextmonth = 3 THEN nextday := 31; ELSIF nextmonth = 4 THEN nextday := 30; ELSIF nextmonth = 5 THEN nextday := 31; ELSIF nextmonth = 6 THEN nextday := 30; ELSIF nextmonth = 7 THEN nextday := 31; ELSIF nextmonth = 8 THEN nextday := 31; ELSIF nextmonth = 9 THEN nextday := 30; ELSIF nextmonth = 10 THEN nextday := 31; ELSIF nextmonth = 11 THEN nextday := 30; ELSIF nextmonth = 12 THEN nextday := 31; END IF; END IF; -- -- Get the next run hour -- -- If the year, month or day have incremented, get the lowest hour, -- otherwise look for the next hour matching or after the current one. IF (nextyear > date_part(''YEAR'', runafter) OR nextmonth > date_part(''MONTH'', runafter) OR nextday > date_part(''DAY'', runafter) OR daytweak = TRUE) THEN nexthour := 0; FOR i IN 1 .. 24 LOOP IF jschours[i] = TRUE THEN nexthour := i - 1; foundval := TRUE; EXIT; END IF; END LOOP; ELSE nexthour := date_part(''HOUR'', runafter); gotit := FALSE; FOR i IN (nexthour + 1) .. 24 LOOP IF jschours[i] = TRUE THEN nexthour := i - 1; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; IF gotit = FALSE THEN FOR i IN 1 .. nexthour LOOP IF jschours[i] = TRUE THEN nexthour := i - 1; -- Wrap into next month IF (nextmonth = 1 OR nextmonth = 3 OR nextmonth = 5 OR nextmonth = 7 OR nextmonth = 8 OR nextmonth = 10 OR nextmonth = 12) THEN d = 31; ELSIF (nextmonth = 4 OR nextmonth = 6 OR nextmonth = 9 OR nextmonth = 11) THEN d = 30; ELSE IF pgagent.pga_is_leap_year(nextyear) = TRUE THEN d := 29; ELSE d := 28; END IF; END IF; IF nextday = d THEN nextday := 1; IF nextmonth = 12 THEN nextyear := nextyear + 1; nextmonth := 1; ELSE nextmonth := nextmonth + 1; END IF; ELSE nextday := nextday + 1; END IF; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; END IF; END IF; -- -- Get the next run minute -- -- If the year, month day or hour have incremented, get the lowest minute, -- otherwise look for the next minute matching or after the current one. IF (nextyear > date_part(''YEAR'', runafter) OR nextmonth > date_part(''MONTH'', runafter) OR nextday > date_part(''DAY'', runafter) OR nexthour > date_part(''HOUR'', runafter) OR daytweak = TRUE) THEN nextminute := 0; IF minutetweak = TRUE THEN d := 1; ELSE d := date_part(''MINUTE'', runafter)::int2; END IF; FOR i IN d .. 60 LOOP IF jscminutes[i] = TRUE THEN nextminute := i - 1; foundval := TRUE; EXIT; END IF; END LOOP; ELSE nextminute := date_part(''MINUTE'', runafter); gotit := FALSE; FOR i IN (nextminute + 1) .. 60 LOOP IF jscminutes[i] = TRUE THEN nextminute := i - 1; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; IF gotit = FALSE THEN FOR i IN 1 .. nextminute LOOP IF jscminutes[i] = TRUE THEN nextminute := i - 1; -- Wrap into next hour IF (nextmonth = 1 OR nextmonth = 3 OR nextmonth = 5 OR nextmonth = 7 OR nextmonth = 8 OR nextmonth = 10 OR nextmonth = 12) THEN d = 31; ELSIF (nextmonth = 4 OR nextmonth = 6 OR nextmonth = 9 OR nextmonth = 11) THEN d = 30; ELSE IF pgagent.pga_is_leap_year(nextyear) = TRUE THEN d := 29; ELSE d := 28; END IF; END IF; IF nexthour = 23 THEN nexthour = 0; IF nextday = d THEN nextday := 1; IF nextmonth = 12 THEN nextyear := nextyear + 1; nextmonth := 1; ELSE nextmonth := nextmonth + 1; END IF; ELSE nextday := nextday + 1; END IF; ELSE nexthour := nexthour + 1; END IF; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; END IF; END IF; -- Build the result, and check it is not the same as runafter - this may -- happen if all array entries are set to false. In this case, add a minute. nextrun := (nextyear::varchar || ''-''::varchar || nextmonth::varchar || ''-'' || nextday::varchar || '' '' || nexthour::varchar || '':'' || nextminute::varchar)::timestamptz; IF nextrun = runafter AND foundval = FALSE THEN nextrun := nextrun + INTERVAL ''1 Minute''; END IF; -- If the result is past the end date, exit. IF nextrun > jscend THEN RETURN NULL; END IF; -- Check to ensure that the nextrun time is actually still valid. Its -- possible that wrapped values may have carried the nextrun onto an -- invalid time or date. IF ((jscminutes = ''{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f}'' OR jscminutes[date_part(''MINUTE'', nextrun) + 1] = TRUE) AND (jschours = ''{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f}'' OR jschours[date_part(''HOUR'', nextrun) + 1] = TRUE) AND (jscmonthdays = ''{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f}'' OR jscmonthdays[date_part(''DAY'', nextrun)] = TRUE OR (jscmonthdays = ''{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,t}'' AND ((date_part(''MONTH'', nextrun) IN (1,3,5,7,8,10,12) AND date_part(''DAY'', nextrun) = 31) OR (date_part(''MONTH'', nextrun) IN (4,6,9,11) AND date_part(''DAY'', nextrun) = 30) OR (date_part(''MONTH'', nextrun) = 2 AND ((pgagent.pga_is_leap_year(date_part(''YEAR'', nextrun)::int2) AND date_part(''DAY'', nextrun) = 29) OR date_part(''DAY'', nextrun) = 28))))) AND (jscmonths = ''{f,f,f,f,f,f,f,f,f,f,f,f}'' OR jscmonths[date_part(''MONTH'', nextrun)] = TRUE)) THEN -- Now, check to see if the nextrun time found is a) on an acceptable -- weekday, and b) not matched by an exception. If not, set -- runafter = nextrun and try again. -- Check for a wildcard weekday gotit := FALSE; FOR i IN 1 .. 7 LOOP IF jscweekdays[i] = TRUE THEN gotit := TRUE; EXIT; END IF; END LOOP; -- OK, is the correct weekday selected, or a wildcard? IF (jscweekdays[date_part(''DOW'', nextrun) + 1] = TRUE OR gotit = FALSE) THEN -- Check for exceptions SELECT INTO d jexid FROM pgagent.pga_exception WHERE jexscid = jscid AND ((jexdate = nextrun::date AND jextime = nextrun::time) OR (jexdate = nextrun::date AND jextime IS NULL) OR (jexdate IS NULL AND jextime = nextrun::time)); IF FOUND THEN -- Nuts - found an exception. Increment the time and try again runafter := nextrun + INTERVAL ''1 Minute''; bingo := FALSE; minutetweak := TRUE; daytweak := FALSE; ELSE bingo := TRUE; END IF; ELSE -- We''re on the wrong week day - increment a day and try again. runafter := nextrun + INTERVAL ''1 Day''; bingo := FALSE; minutetweak := FALSE; daytweak := TRUE; END IF; ELSE runafter := nextrun + INTERVAL ''1 Minute''; bingo := FALSE; minutetweak := TRUE; daytweak := FALSE; END IF; END LOOP; RETURN nextrun; END; ' LANGUAGE 'plpgsql' VOLATILE; COMMENT ON FUNCTION pgagent.pga_next_schedule(int4, timestamptz, timestamptz, _bool, _bool, _bool, _bool, _bool) IS 'Calculates the next runtime for a given schedule'; pgagent-pgagent-4.2.3/sql/pgagent--unpackaged--4.2.sql000066400000000000000000000420031473675055000223030ustar00rootroot00000000000000/* // pgAgent - PostgreSQL Tools // // Copyright (C) 2002 - 2024, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // // pgagent--unpackaged--4.2.sql - Convert pgAgent existing tables and functions to an extension // */ \echo Use "CREATE EXTENSION pgagent FROM unpackaged" to load this file. \quit ALTER EXTENSION pgagent ADD TABLE pgagent.pga_jobagent; ALTER EXTENSION pgagent ADD TABLE pgagent.pga_jobclass; ALTER EXTENSION pgagent ADD TABLE pgagent.pga_job; ALTER EXTENSION pgagent ADD TABLE pgagent.pga_jobstep; ALTER EXTENSION pgagent ADD TABLE pgagent.pga_schedule; ALTER EXTENSION pgagent ADD TABLE pgagent.pga_exception; ALTER EXTENSION pgagent ADD TABLE pgagent.pga_joblog; ALTER EXTENSION pgagent ADD TABLE pgagent.pga_jobsteplog; ALTER EXTENSION pgagent ADD SEQUENCE pgagent.pga_exception_jexid_seq; ALTER EXTENSION pgagent ADD SEQUENCE pgagent.pga_job_jobid_seq; ALTER EXTENSION pgagent ADD SEQUENCE pgagent.pga_jobclass_jclid_seq; ALTER EXTENSION pgagent ADD SEQUENCE pgagent.pga_joblog_jlgid_seq; ALTER EXTENSION pgagent ADD SEQUENCE pgagent.pga_jobstep_jstid_seq; ALTER EXTENSION pgagent ADD SEQUENCE pgagent.pga_jobsteplog_jslid_seq; ALTER EXTENSION pgagent ADD SEQUENCE pgagent.pga_schedule_jscid_seq; ALTER EXTENSION pgagent ADD FUNCTION pgagent.pgagent_schema_version(); ALTER EXTENSION pgagent ADD FUNCTION pgagent.pga_next_schedule(int4, timestamptz, timestamptz, _bool, _bool, _bool, _bool, _bool); ALTER EXTENSION pgagent ADD FUNCTION pgagent.pga_is_leap_year(int2); ALTER EXTENSION pgagent ADD FUNCTION pgagent.pga_job_trigger(); ALTER EXTENSION pgagent ADD FUNCTION pgagent.pga_schedule_trigger(); ALTER EXTENSION pgagent ADD FUNCTION pgagent.pga_exception_trigger(); SELECT pg_catalog.pg_extension_config_dump('pga_jobagent', ''); SELECT pg_catalog.pg_extension_config_dump('pga_jobclass', $$WHERE jclname NOT IN ('Routine Maintenance', 'Data Import', 'Data Export', 'Data Summarisation', 'Miscellaneous')$$); SELECT pg_catalog.pg_extension_config_dump('pga_job', ''); SELECT pg_catalog.pg_extension_config_dump('pga_jobstep', ''); SELECT pg_catalog.pg_extension_config_dump('pga_schedule', ''); SELECT pg_catalog.pg_extension_config_dump('pga_exception', ''); SELECT pg_catalog.pg_extension_config_dump('pga_joblog', ''); SELECT pg_catalog.pg_extension_config_dump('pga_jobsteplog', ''); CREATE OR REPLACE FUNCTION pgagent.pgagent_schema_version() RETURNS int2 AS ' BEGIN -- RETURNS PGAGENT MAJOR VERSION -- WE WILL CHANGE THE MAJOR VERSION, ONLY IF THERE IS A SCHEMA CHANGE RETURN 4; END; ' LANGUAGE 'plpgsql' VOLATILE; CREATE OR REPLACE FUNCTION pgagent.pga_next_schedule(int4, timestamptz, timestamptz, _bool, _bool, _bool, _bool, _bool) RETURNS timestamptz AS ' DECLARE jscid ALIAS FOR $1; jscstart ALIAS FOR $2; jscend ALIAS FOR $3; jscminutes ALIAS FOR $4; jschours ALIAS FOR $5; jscweekdays ALIAS FOR $6; jscmonthdays ALIAS FOR $7; jscmonths ALIAS FOR $8; nextrun timestamp := ''1970-01-01 00:00:00-00''; runafter timestamp := ''1970-01-01 00:00:00-00''; bingo bool := FALSE; gotit bool := FALSE; foundval bool := FALSE; daytweak bool := FALSE; minutetweak bool := FALSE; i int2 := 0; d int2 := 0; nextminute int2 := 0; nexthour int2 := 0; nextday int2 := 0; nextmonth int2 := 0; nextyear int2 := 0; BEGIN -- No valid start date has been specified IF jscstart IS NULL THEN RETURN NULL; END IF; -- The schedule is past its end date IF jscend IS NOT NULL AND jscend < now() THEN RETURN NULL; END IF; -- Get the time to find the next run after. It will just be the later of -- now() + 1m and the start date for the time being, however, we might want to -- do more complex things using this value in the future. IF date_trunc(''MINUTE'', jscstart) > date_trunc(''MINUTE'', (now() + ''1 Minute''::interval)) THEN runafter := date_trunc(''MINUTE'', jscstart); ELSE runafter := date_trunc(''MINUTE'', (now() + ''1 Minute''::interval)); END IF; -- -- Enter a loop, generating next run timestamps until we find one -- that falls on the required weekday, and is not matched by an exception -- WHILE bingo = FALSE LOOP -- -- Get the next run year -- nextyear := date_part(''YEAR'', runafter); -- -- Get the next run month -- nextmonth := date_part(''MONTH'', runafter); gotit := FALSE; FOR i IN (nextmonth) .. 12 LOOP IF jscmonths[i] = TRUE THEN nextmonth := i; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; IF gotit = FALSE THEN FOR i IN 1 .. (nextmonth - 1) LOOP IF jscmonths[i] = TRUE THEN nextmonth := i; -- Wrap into next year nextyear := nextyear + 1; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; END IF; -- -- Get the next run day -- -- If the year, or month have incremented, get the lowest day, -- otherwise look for the next day matching or after today. IF (nextyear > date_part(''YEAR'', runafter) OR nextmonth > date_part(''MONTH'', runafter)) THEN nextday := 1; FOR i IN 1 .. 32 LOOP IF jscmonthdays[i] = TRUE THEN nextday := i; foundval := TRUE; EXIT; END IF; END LOOP; ELSE nextday := date_part(''DAY'', runafter); gotit := FALSE; FOR i IN nextday .. 32 LOOP IF jscmonthdays[i] = TRUE THEN nextday := i; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; IF gotit = FALSE THEN FOR i IN 1 .. (nextday - 1) LOOP IF jscmonthdays[i] = TRUE THEN nextday := i; -- Wrap into next month IF nextmonth = 12 THEN nextyear := nextyear + 1; nextmonth := 1; ELSE nextmonth := nextmonth + 1; END IF; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; END IF; END IF; -- Was the last day flag selected? IF nextday = 32 THEN IF nextmonth = 1 THEN nextday := 31; ELSIF nextmonth = 2 THEN IF pgagent.pga_is_leap_year(nextyear) = TRUE THEN nextday := 29; ELSE nextday := 28; END IF; ELSIF nextmonth = 3 THEN nextday := 31; ELSIF nextmonth = 4 THEN nextday := 30; ELSIF nextmonth = 5 THEN nextday := 31; ELSIF nextmonth = 6 THEN nextday := 30; ELSIF nextmonth = 7 THEN nextday := 31; ELSIF nextmonth = 8 THEN nextday := 31; ELSIF nextmonth = 9 THEN nextday := 30; ELSIF nextmonth = 10 THEN nextday := 31; ELSIF nextmonth = 11 THEN nextday := 30; ELSIF nextmonth = 12 THEN nextday := 31; END IF; END IF; -- -- Get the next run hour -- -- If the year, month or day have incremented, get the lowest hour, -- otherwise look for the next hour matching or after the current one. IF (nextyear > date_part(''YEAR'', runafter) OR nextmonth > date_part(''MONTH'', runafter) OR nextday > date_part(''DAY'', runafter) OR daytweak = TRUE) THEN nexthour := 0; FOR i IN 1 .. 24 LOOP IF jschours[i] = TRUE THEN nexthour := i - 1; foundval := TRUE; EXIT; END IF; END LOOP; ELSE nexthour := date_part(''HOUR'', runafter); gotit := FALSE; FOR i IN (nexthour + 1) .. 24 LOOP IF jschours[i] = TRUE THEN nexthour := i - 1; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; IF gotit = FALSE THEN FOR i IN 1 .. nexthour LOOP IF jschours[i] = TRUE THEN nexthour := i - 1; -- Wrap into next month IF (nextmonth = 1 OR nextmonth = 3 OR nextmonth = 5 OR nextmonth = 7 OR nextmonth = 8 OR nextmonth = 10 OR nextmonth = 12) THEN d = 31; ELSIF (nextmonth = 4 OR nextmonth = 6 OR nextmonth = 9 OR nextmonth = 11) THEN d = 30; ELSE IF pgagent.pga_is_leap_year(nextyear) = TRUE THEN d := 29; ELSE d := 28; END IF; END IF; IF nextday = d THEN nextday := 1; IF nextmonth = 12 THEN nextyear := nextyear + 1; nextmonth := 1; ELSE nextmonth := nextmonth + 1; END IF; ELSE nextday := nextday + 1; END IF; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; END IF; END IF; -- -- Get the next run minute -- -- If the year, month day or hour have incremented, get the lowest minute, -- otherwise look for the next minute matching or after the current one. IF (nextyear > date_part(''YEAR'', runafter) OR nextmonth > date_part(''MONTH'', runafter) OR nextday > date_part(''DAY'', runafter) OR nexthour > date_part(''HOUR'', runafter) OR daytweak = TRUE) THEN nextminute := 0; IF minutetweak = TRUE THEN d := 1; ELSE d := date_part(''MINUTE'', runafter)::int2; END IF; FOR i IN d .. 60 LOOP IF jscminutes[i] = TRUE THEN nextminute := i - 1; foundval := TRUE; EXIT; END IF; END LOOP; ELSE nextminute := date_part(''MINUTE'', runafter); gotit := FALSE; FOR i IN (nextminute + 1) .. 60 LOOP IF jscminutes[i] = TRUE THEN nextminute := i - 1; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; IF gotit = FALSE THEN FOR i IN 1 .. nextminute LOOP IF jscminutes[i] = TRUE THEN nextminute := i - 1; -- Wrap into next hour IF (nextmonth = 1 OR nextmonth = 3 OR nextmonth = 5 OR nextmonth = 7 OR nextmonth = 8 OR nextmonth = 10 OR nextmonth = 12) THEN d = 31; ELSIF (nextmonth = 4 OR nextmonth = 6 OR nextmonth = 9 OR nextmonth = 11) THEN d = 30; ELSE IF pgagent.pga_is_leap_year(nextyear) = TRUE THEN d := 29; ELSE d := 28; END IF; END IF; IF nexthour = 23 THEN nexthour = 0; IF nextday = d THEN nextday := 1; IF nextmonth = 12 THEN nextyear := nextyear + 1; nextmonth := 1; ELSE nextmonth := nextmonth + 1; END IF; ELSE nextday := nextday + 1; END IF; ELSE nexthour := nexthour + 1; END IF; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; END IF; END IF; -- Build the result, and check it is not the same as runafter - this may -- happen if all array entries are set to false. In this case, add a minute. nextrun := (nextyear::varchar || ''-''::varchar || nextmonth::varchar || ''-'' || nextday::varchar || '' '' || nexthour::varchar || '':'' || nextminute::varchar)::timestamptz; IF nextrun = runafter AND foundval = FALSE THEN nextrun := nextrun + INTERVAL ''1 Minute''; END IF; -- If the result is past the end date, exit. IF nextrun > jscend THEN RETURN NULL; END IF; -- Check to ensure that the nextrun time is actually still valid. Its -- possible that wrapped values may have carried the nextrun onto an -- invalid time or date. IF ((jscminutes = ''{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f}'' OR jscminutes[date_part(''MINUTE'', nextrun) + 1] = TRUE) AND (jschours = ''{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f}'' OR jschours[date_part(''HOUR'', nextrun) + 1] = TRUE) AND (jscmonthdays = ''{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f}'' OR jscmonthdays[date_part(''DAY'', nextrun)] = TRUE OR (jscmonthdays = ''{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,t}'' AND ((date_part(''MONTH'', nextrun) IN (1,3,5,7,8,10,12) AND date_part(''DAY'', nextrun) = 31) OR (date_part(''MONTH'', nextrun) IN (4,6,9,11) AND date_part(''DAY'', nextrun) = 30) OR (date_part(''MONTH'', nextrun) = 2 AND ((pgagent.pga_is_leap_year(date_part(''YEAR'', nextrun)::int2) AND date_part(''DAY'', nextrun) = 29) OR date_part(''DAY'', nextrun) = 28))))) AND (jscmonths = ''{f,f,f,f,f,f,f,f,f,f,f,f}'' OR jscmonths[date_part(''MONTH'', nextrun)] = TRUE)) THEN -- Now, check to see if the nextrun time found is a) on an acceptable -- weekday, and b) not matched by an exception. If not, set -- runafter = nextrun and try again. -- Check for a wildcard weekday gotit := FALSE; FOR i IN 1 .. 7 LOOP IF jscweekdays[i] = TRUE THEN gotit := TRUE; EXIT; END IF; END LOOP; -- OK, is the correct weekday selected, or a wildcard? IF (jscweekdays[date_part(''DOW'', nextrun) + 1] = TRUE OR gotit = FALSE) THEN -- Check for exceptions SELECT INTO d jexid FROM pgagent.pga_exception WHERE jexscid = jscid AND ((jexdate = nextrun::date AND jextime = nextrun::time) OR (jexdate = nextrun::date AND jextime IS NULL) OR (jexdate IS NULL AND jextime = nextrun::time)); IF FOUND THEN -- Nuts - found an exception. Increment the time and try again runafter := nextrun + INTERVAL ''1 Minute''; bingo := FALSE; minutetweak := TRUE; daytweak := FALSE; ELSE bingo := TRUE; END IF; ELSE -- We''re on the wrong week day - increment a day and try again. runafter := nextrun + INTERVAL ''1 Day''; bingo := FALSE; minutetweak := FALSE; daytweak := TRUE; END IF; ELSE runafter := nextrun + INTERVAL ''1 Minute''; bingo := FALSE; minutetweak := TRUE; daytweak := FALSE; END IF; END LOOP; RETURN nextrun; END; ' LANGUAGE 'plpgsql' VOLATILE; COMMENT ON FUNCTION pgagent.pga_next_schedule(int4, timestamptz, timestamptz, _bool, _bool, _bool, _bool, _bool) IS 'Calculates the next runtime for a given schedule'; pgagent-pgagent-4.2.3/sql/pgagent.sql000066400000000000000000000662001473675055000175550ustar00rootroot00000000000000/* // pgAgent - PostgreSQL Tools // // Copyright (C) 2002 - 2024, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // // pgagent.sql - pgAgent tables and functions // */ BEGIN TRANSACTION; CREATE SCHEMA pgagent; COMMENT ON SCHEMA pgagent IS 'pgAgent system tables'; CREATE TABLE pgagent.pga_jobagent ( jagpid int4 NOT NULL PRIMARY KEY, jaglogintime timestamptz NOT NULL DEFAULT current_timestamp, jagstation text NOT NULL ) WITHOUT OIDS; COMMENT ON TABLE pgagent.pga_jobagent IS 'Active job agents'; CREATE TABLE pgagent.pga_jobclass ( jclid serial NOT NULL PRIMARY KEY, jclname text NOT NULL ) WITHOUT OIDS; CREATE UNIQUE INDEX pga_jobclass_name ON pgagent.pga_jobclass(jclname); COMMENT ON TABLE pgagent.pga_jobclass IS 'Job classification'; INSERT INTO pgagent.pga_jobclass (jclname) VALUES ('Routine Maintenance'); INSERT INTO pgagent.pga_jobclass (jclname) VALUES ('Data Import'); INSERT INTO pgagent.pga_jobclass (jclname) VALUES ('Data Export'); INSERT INTO pgagent.pga_jobclass (jclname) VALUES ('Data Summarisation'); INSERT INTO pgagent.pga_jobclass (jclname) VALUES ('Miscellaneous'); -- Be sure to update pg_extension_config_dump() below and in -- upgrade scripts etc, when adding new classes. CREATE TABLE pgagent.pga_job ( jobid serial NOT NULL PRIMARY KEY, jobjclid int4 NOT NULL REFERENCES pgagent.pga_jobclass (jclid) ON DELETE RESTRICT ON UPDATE RESTRICT, jobname text NOT NULL, jobdesc text NOT NULL DEFAULT '', jobhostagent text NOT NULL DEFAULT '', jobenabled bool NOT NULL DEFAULT true, jobcreated timestamptz NOT NULL DEFAULT current_timestamp, jobchanged timestamptz NOT NULL DEFAULT current_timestamp, jobagentid int4 NULL REFERENCES pgagent.pga_jobagent(jagpid) ON DELETE SET NULL ON UPDATE RESTRICT, jobnextrun timestamptz NULL, joblastrun timestamptz NULL ) WITHOUT OIDS; COMMENT ON TABLE pgagent.pga_job IS 'Job main entry'; COMMENT ON COLUMN pgagent.pga_job.jobagentid IS 'Agent that currently executes this job.'; CREATE TABLE pgagent.pga_jobstep ( jstid serial NOT NULL PRIMARY KEY, jstjobid int4 NOT NULL REFERENCES pgagent.pga_job (jobid) ON DELETE CASCADE ON UPDATE RESTRICT, jstname text NOT NULL, jstdesc text NOT NULL DEFAULT '', jstenabled bool NOT NULL DEFAULT true, jstkind char NOT NULL CHECK (jstkind IN ('b', 's')), -- batch, sql jstcode text NOT NULL, jstconnstr text NOT NULL DEFAULT '' CHECK ((jstconnstr != '' AND jstkind = 's' ) OR (jstconnstr = '' AND (jstkind = 'b' OR jstdbname != ''))), jstdbname name NOT NULL DEFAULT '' CHECK ((jstdbname != '' AND jstkind = 's' ) OR (jstdbname = '' AND (jstkind = 'b' OR jstconnstr != ''))), jstonerror char NOT NULL CHECK (jstonerror IN ('f', 's', 'i')) DEFAULT 'f', -- fail, success, ignore jscnextrun timestamptz NULL ) WITHOUT OIDS; CREATE INDEX pga_jobstep_jobid ON pgagent.pga_jobstep(jstjobid); COMMENT ON TABLE pgagent.pga_jobstep IS 'Job step to be executed'; COMMENT ON COLUMN pgagent.pga_jobstep.jstkind IS 'Kind of jobstep: s=sql, b=batch'; COMMENT ON COLUMN pgagent.pga_jobstep.jstonerror IS 'What to do if step returns an error: f=fail the job, s=mark step as succeeded and continue, i=mark as fail but ignore it and proceed'; CREATE TABLE pgagent.pga_schedule ( jscid serial NOT NULL PRIMARY KEY, jscjobid int4 NOT NULL REFERENCES pgagent.pga_job (jobid) ON DELETE CASCADE ON UPDATE RESTRICT, jscname text NOT NULL, jscdesc text NOT NULL DEFAULT '', jscenabled bool NOT NULL DEFAULT true, jscstart timestamptz NOT NULL DEFAULT current_timestamp, jscend timestamptz NULL, jscminutes bool[60] NOT NULL DEFAULT '{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f}', jschours bool[24] NOT NULL DEFAULT '{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f}', jscweekdays bool[7] NOT NULL DEFAULT '{f,f,f,f,f,f,f}', jscmonthdays bool[32] NOT NULL DEFAULT '{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f}', jscmonths bool[12] NOT NULL DEFAULT '{f,f,f,f,f,f,f,f,f,f,f,f}', CONSTRAINT pga_schedule_jscminutes_size CHECK (array_upper(jscminutes, 1) = 60), CONSTRAINT pga_schedule_jschours_size CHECK (array_upper(jschours, 1) = 24), CONSTRAINT pga_schedule_jscweekdays_size CHECK (array_upper(jscweekdays, 1) = 7), CONSTRAINT pga_schedule_jscmonthdays_size CHECK (array_upper(jscmonthdays, 1) = 32), CONSTRAINT pga_schedule_jscmonths_size CHECK (array_upper(jscmonths, 1) = 12) ) WITHOUT OIDS; CREATE INDEX pga_jobschedule_jobid ON pgagent.pga_schedule(jscjobid); COMMENT ON TABLE pgagent.pga_schedule IS 'Schedule for a job'; CREATE TABLE pgagent.pga_exception ( jexid serial NOT NULL PRIMARY KEY, jexscid int4 NOT NULL REFERENCES pgagent.pga_schedule (jscid) ON DELETE CASCADE ON UPDATE RESTRICT, jexdate date NULL, jextime time NULL ) WITHOUT OIDS; CREATE INDEX pga_exception_jexscid ON pgagent.pga_exception (jexscid); CREATE UNIQUE INDEX pga_exception_datetime ON pgagent.pga_exception (jexdate, jextime); COMMENT ON TABLE pgagent.pga_schedule IS 'Job schedule exceptions'; CREATE TABLE pgagent.pga_joblog ( jlgid serial NOT NULL PRIMARY KEY, jlgjobid int4 NOT NULL REFERENCES pgagent.pga_job (jobid) ON DELETE CASCADE ON UPDATE RESTRICT, jlgstatus char NOT NULL CHECK (jlgstatus IN ('r', 's', 'f', 'i', 'd')) DEFAULT 'r', -- running, success, failed, internal failure, aborted jlgstart timestamptz NOT NULL DEFAULT current_timestamp, jlgduration interval NULL ) WITHOUT OIDS; CREATE INDEX pga_joblog_jobid ON pgagent.pga_joblog(jlgjobid); COMMENT ON TABLE pgagent.pga_joblog IS 'Job run logs.'; COMMENT ON COLUMN pgagent.pga_joblog.jlgstatus IS 'Status of job: r=running, s=successfully finished, f=failed, i=no steps to execute, d=aborted'; CREATE TABLE pgagent.pga_jobsteplog ( jslid serial NOT NULL PRIMARY KEY, jsljlgid int4 NOT NULL REFERENCES pgagent.pga_joblog (jlgid) ON DELETE CASCADE ON UPDATE RESTRICT, jsljstid int4 NOT NULL REFERENCES pgagent.pga_jobstep (jstid) ON DELETE CASCADE ON UPDATE RESTRICT, jslstatus char NOT NULL CHECK (jslstatus IN ('r', 's', 'i', 'f', 'd')) DEFAULT 'r', -- running, success, ignored, failed, aborted jslresult int4 NULL, jslstart timestamptz NOT NULL DEFAULT current_timestamp, jslduration interval NULL, jsloutput text ) WITHOUT OIDS; CREATE INDEX pga_jobsteplog_jslid ON pgagent.pga_jobsteplog(jsljlgid); COMMENT ON TABLE pgagent.pga_jobsteplog IS 'Job step run logs.'; COMMENT ON COLUMN pgagent.pga_jobsteplog.jslstatus IS 'Status of job step: r=running, s=successfully finished, f=failed stopping job, i=ignored failure, d=aborted'; COMMENT ON COLUMN pgagent.pga_jobsteplog.jslresult IS 'Return code of job step'; CREATE OR REPLACE FUNCTION pgagent.pgagent_schema_version() RETURNS int2 AS ' BEGIN -- RETURNS PGAGENT MAJOR VERSION -- WE WILL CHANGE THE MAJOR VERSION, ONLY IF THERE IS A SCHEMA CHANGE RETURN 4; END; ' LANGUAGE 'plpgsql' VOLATILE; CREATE OR REPLACE FUNCTION pgagent.pga_next_schedule(int4, timestamptz, timestamptz, _bool, _bool, _bool, _bool, _bool) RETURNS timestamptz AS ' DECLARE jscid ALIAS FOR $1; jscstart ALIAS FOR $2; jscend ALIAS FOR $3; jscminutes ALIAS FOR $4; jschours ALIAS FOR $5; jscweekdays ALIAS FOR $6; jscmonthdays ALIAS FOR $7; jscmonths ALIAS FOR $8; nextrun timestamp := ''1970-01-01 00:00:00-00''; runafter timestamp := ''1970-01-01 00:00:00-00''; bingo bool := FALSE; gotit bool := FALSE; foundval bool := FALSE; daytweak bool := FALSE; minutetweak bool := FALSE; i int2 := 0; d int2 := 0; nextminute int2 := 0; nexthour int2 := 0; nextday int2 := 0; nextmonth int2 := 0; nextyear int2 := 0; BEGIN -- No valid start date has been specified IF jscstart IS NULL THEN RETURN NULL; END IF; -- The schedule is past its end date IF jscend IS NOT NULL AND jscend < now() THEN RETURN NULL; END IF; -- Get the time to find the next run after. It will just be the later of -- now() + 1m and the start date for the time being, however, we might want to -- do more complex things using this value in the future. IF date_trunc(''MINUTE'', jscstart) > date_trunc(''MINUTE'', (now() + ''1 Minute''::interval)) THEN runafter := date_trunc(''MINUTE'', jscstart); ELSE runafter := date_trunc(''MINUTE'', (now() + ''1 Minute''::interval)); END IF; -- -- Enter a loop, generating next run timestamps until we find one -- that falls on the required weekday, and is not matched by an exception -- WHILE bingo = FALSE LOOP -- -- Get the next run year -- nextyear := date_part(''YEAR'', runafter); -- -- Get the next run month -- nextmonth := date_part(''MONTH'', runafter); gotit := FALSE; FOR i IN (nextmonth) .. 12 LOOP IF jscmonths[i] = TRUE THEN nextmonth := i; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; IF gotit = FALSE THEN FOR i IN 1 .. (nextmonth - 1) LOOP IF jscmonths[i] = TRUE THEN nextmonth := i; -- Wrap into next year nextyear := nextyear + 1; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; END IF; -- -- Get the next run day -- -- If the year, or month have incremented, get the lowest day, -- otherwise look for the next day matching or after today. IF (nextyear > date_part(''YEAR'', runafter) OR nextmonth > date_part(''MONTH'', runafter)) THEN nextday := 1; FOR i IN 1 .. 32 LOOP IF jscmonthdays[i] = TRUE THEN nextday := i; foundval := TRUE; EXIT; END IF; END LOOP; ELSE nextday := date_part(''DAY'', runafter); gotit := FALSE; FOR i IN nextday .. 32 LOOP IF jscmonthdays[i] = TRUE THEN nextday := i; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; IF gotit = FALSE THEN FOR i IN 1 .. (nextday - 1) LOOP IF jscmonthdays[i] = TRUE THEN nextday := i; -- Wrap into next month IF nextmonth = 12 THEN nextyear := nextyear + 1; nextmonth := 1; ELSE nextmonth := nextmonth + 1; END IF; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; END IF; END IF; -- Was the last day flag selected? IF nextday = 32 THEN IF nextmonth = 1 THEN nextday := 31; ELSIF nextmonth = 2 THEN IF pgagent.pga_is_leap_year(nextyear) = TRUE THEN nextday := 29; ELSE nextday := 28; END IF; ELSIF nextmonth = 3 THEN nextday := 31; ELSIF nextmonth = 4 THEN nextday := 30; ELSIF nextmonth = 5 THEN nextday := 31; ELSIF nextmonth = 6 THEN nextday := 30; ELSIF nextmonth = 7 THEN nextday := 31; ELSIF nextmonth = 8 THEN nextday := 31; ELSIF nextmonth = 9 THEN nextday := 30; ELSIF nextmonth = 10 THEN nextday := 31; ELSIF nextmonth = 11 THEN nextday := 30; ELSIF nextmonth = 12 THEN nextday := 31; END IF; END IF; -- -- Get the next run hour -- -- If the year, month or day have incremented, get the lowest hour, -- otherwise look for the next hour matching or after the current one. IF (nextyear > date_part(''YEAR'', runafter) OR nextmonth > date_part(''MONTH'', runafter) OR nextday > date_part(''DAY'', runafter) OR daytweak = TRUE) THEN nexthour := 0; FOR i IN 1 .. 24 LOOP IF jschours[i] = TRUE THEN nexthour := i - 1; foundval := TRUE; EXIT; END IF; END LOOP; ELSE nexthour := date_part(''HOUR'', runafter); gotit := FALSE; FOR i IN (nexthour + 1) .. 24 LOOP IF jschours[i] = TRUE THEN nexthour := i - 1; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; IF gotit = FALSE THEN FOR i IN 1 .. nexthour LOOP IF jschours[i] = TRUE THEN nexthour := i - 1; -- Wrap into next month IF (nextmonth = 1 OR nextmonth = 3 OR nextmonth = 5 OR nextmonth = 7 OR nextmonth = 8 OR nextmonth = 10 OR nextmonth = 12) THEN d = 31; ELSIF (nextmonth = 4 OR nextmonth = 6 OR nextmonth = 9 OR nextmonth = 11) THEN d = 30; ELSE IF pgagent.pga_is_leap_year(nextyear) = TRUE THEN d := 29; ELSE d := 28; END IF; END IF; IF nextday = d THEN nextday := 1; IF nextmonth = 12 THEN nextyear := nextyear + 1; nextmonth := 1; ELSE nextmonth := nextmonth + 1; END IF; ELSE nextday := nextday + 1; END IF; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; END IF; END IF; -- -- Get the next run minute -- -- If the year, month day or hour have incremented, get the lowest minute, -- otherwise look for the next minute matching or after the current one. IF (nextyear > date_part(''YEAR'', runafter) OR nextmonth > date_part(''MONTH'', runafter) OR nextday > date_part(''DAY'', runafter) OR nexthour > date_part(''HOUR'', runafter) OR daytweak = TRUE) THEN nextminute := 0; IF minutetweak = TRUE THEN d := 1; ELSE d := date_part(''MINUTE'', runafter)::int2; END IF; FOR i IN d .. 60 LOOP IF jscminutes[i] = TRUE THEN nextminute := i - 1; foundval := TRUE; EXIT; END IF; END LOOP; ELSE nextminute := date_part(''MINUTE'', runafter); gotit := FALSE; FOR i IN (nextminute + 1) .. 60 LOOP IF jscminutes[i] = TRUE THEN nextminute := i - 1; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; IF gotit = FALSE THEN FOR i IN 1 .. nextminute LOOP IF jscminutes[i] = TRUE THEN nextminute := i - 1; -- Wrap into next hour IF (nextmonth = 1 OR nextmonth = 3 OR nextmonth = 5 OR nextmonth = 7 OR nextmonth = 8 OR nextmonth = 10 OR nextmonth = 12) THEN d = 31; ELSIF (nextmonth = 4 OR nextmonth = 6 OR nextmonth = 9 OR nextmonth = 11) THEN d = 30; ELSE IF pgagent.pga_is_leap_year(nextyear) = TRUE THEN d := 29; ELSE d := 28; END IF; END IF; IF nexthour = 23 THEN nexthour = 0; IF nextday = d THEN nextday := 1; IF nextmonth = 12 THEN nextyear := nextyear + 1; nextmonth := 1; ELSE nextmonth := nextmonth + 1; END IF; ELSE nextday := nextday + 1; END IF; ELSE nexthour := nexthour + 1; END IF; gotit := TRUE; foundval := TRUE; EXIT; END IF; END LOOP; END IF; END IF; -- Build the result, and check it is not the same as runafter - this may -- happen if all array entries are set to false. In this case, add a minute. nextrun := (nextyear::varchar || ''-''::varchar || nextmonth::varchar || ''-'' || nextday::varchar || '' '' || nexthour::varchar || '':'' || nextminute::varchar)::timestamptz; IF nextrun = runafter AND foundval = FALSE THEN nextrun := nextrun + INTERVAL ''1 Minute''; END IF; -- If the result is past the end date, exit. IF nextrun > jscend THEN RETURN NULL; END IF; -- Check to ensure that the nextrun time is actually still valid. Its -- possible that wrapped values may have carried the nextrun onto an -- invalid time or date. IF ((jscminutes = ''{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f}'' OR jscminutes[date_part(''MINUTE'', nextrun) + 1] = TRUE) AND (jschours = ''{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f}'' OR jschours[date_part(''HOUR'', nextrun) + 1] = TRUE) AND (jscmonthdays = ''{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f}'' OR jscmonthdays[date_part(''DAY'', nextrun)] = TRUE OR (jscmonthdays = ''{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,t}'' AND ((date_part(''MONTH'', nextrun) IN (1,3,5,7,8,10,12) AND date_part(''DAY'', nextrun) = 31) OR (date_part(''MONTH'', nextrun) IN (4,6,9,11) AND date_part(''DAY'', nextrun) = 30) OR (date_part(''MONTH'', nextrun) = 2 AND ((pgagent.pga_is_leap_year(date_part(''YEAR'', nextrun)::int2) AND date_part(''DAY'', nextrun) = 29) OR date_part(''DAY'', nextrun) = 28))))) AND (jscmonths = ''{f,f,f,f,f,f,f,f,f,f,f,f}'' OR jscmonths[date_part(''MONTH'', nextrun)] = TRUE)) THEN -- Now, check to see if the nextrun time found is a) on an acceptable -- weekday, and b) not matched by an exception. If not, set -- runafter = nextrun and try again. -- Check for a wildcard weekday gotit := FALSE; FOR i IN 1 .. 7 LOOP IF jscweekdays[i] = TRUE THEN gotit := TRUE; EXIT; END IF; END LOOP; -- OK, is the correct weekday selected, or a wildcard? IF (jscweekdays[date_part(''DOW'', nextrun) + 1] = TRUE OR gotit = FALSE) THEN -- Check for exceptions SELECT INTO d jexid FROM pgagent.pga_exception WHERE jexscid = jscid AND ((jexdate = nextrun::date AND jextime = nextrun::time) OR (jexdate = nextrun::date AND jextime IS NULL) OR (jexdate IS NULL AND jextime = nextrun::time)); IF FOUND THEN -- Nuts - found an exception. Increment the time and try again runafter := nextrun + INTERVAL ''1 Minute''; bingo := FALSE; minutetweak := TRUE; daytweak := FALSE; ELSE bingo := TRUE; END IF; ELSE -- We''re on the wrong week day - increment a day and try again. runafter := nextrun + INTERVAL ''1 Day''; bingo := FALSE; minutetweak := FALSE; daytweak := TRUE; END IF; ELSE runafter := nextrun + INTERVAL ''1 Minute''; bingo := FALSE; minutetweak := TRUE; daytweak := FALSE; END IF; END LOOP; RETURN nextrun; END; ' LANGUAGE 'plpgsql' VOLATILE; COMMENT ON FUNCTION pgagent.pga_next_schedule(int4, timestamptz, timestamptz, _bool, _bool, _bool, _bool, _bool) IS 'Calculates the next runtime for a given schedule'; -- -- Test code -- -- SELECT pgagent.pga_next_schedule( -- 2, -- Schedule ID -- '2005-01-01 00:00:00', -- Start date -- '2006-10-01 00:00:00', -- End date -- '{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f}', -- Minutes -- '{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f}', -- Hours -- '{f,f,f,f,f,f,f}', -- Weekdays -- '{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f}', -- Monthdays -- '{f,f,f,f,f,f,f,f,f,f,f,f}' -- Months -- ); CREATE OR REPLACE FUNCTION pgagent.pga_is_leap_year(int2) RETURNS bool AS ' BEGIN IF $1 % 4 != 0 THEN RETURN FALSE; END IF; IF $1 % 100 != 0 THEN RETURN TRUE; END IF; RETURN $1 % 400 = 0; END; ' LANGUAGE 'plpgsql' IMMUTABLE; COMMENT ON FUNCTION pgagent.pga_is_leap_year(int2) IS 'Returns TRUE if $1 is a leap year'; CREATE OR REPLACE FUNCTION pgagent.pga_job_trigger() RETURNS "trigger" AS ' BEGIN IF NEW.jobenabled THEN IF NEW.jobnextrun IS NULL THEN SELECT INTO NEW.jobnextrun MIN(pgagent.pga_next_schedule(jscid, jscstart, jscend, jscminutes, jschours, jscweekdays, jscmonthdays, jscmonths)) FROM pgagent.pga_schedule WHERE jscenabled AND jscjobid=OLD.jobid; END IF; ELSE NEW.jobnextrun := NULL; END IF; RETURN NEW; END; ' LANGUAGE 'plpgsql' VOLATILE; COMMENT ON FUNCTION pgagent.pga_job_trigger() IS 'Update the job''s next run time.'; CREATE TRIGGER pga_job_trigger BEFORE UPDATE ON pgagent.pga_job FOR EACH ROW EXECUTE PROCEDURE pgagent.pga_job_trigger(); COMMENT ON TRIGGER pga_job_trigger ON pgagent.pga_job IS 'Update the job''s next run time.'; CREATE OR REPLACE FUNCTION pgagent.pga_schedule_trigger() RETURNS trigger AS ' BEGIN IF TG_OP = ''DELETE'' THEN -- update pga_job from remaining schedules -- the actual calculation of jobnextrun will be performed in the trigger UPDATE pgagent.pga_job SET jobnextrun = NULL WHERE jobenabled AND jobid=OLD.jscjobid; RETURN OLD; ELSE UPDATE pgagent.pga_job SET jobnextrun = NULL WHERE jobenabled AND jobid=NEW.jscjobid; RETURN NEW; END IF; END; ' LANGUAGE 'plpgsql'; COMMENT ON FUNCTION pgagent.pga_schedule_trigger() IS 'Update the job''s next run time whenever a schedule changes'; CREATE TRIGGER pga_schedule_trigger AFTER INSERT OR UPDATE OR DELETE ON pgagent.pga_schedule FOR EACH ROW EXECUTE PROCEDURE pgagent.pga_schedule_trigger(); COMMENT ON TRIGGER pga_schedule_trigger ON pgagent.pga_schedule IS 'Update the job''s next run time whenever a schedule changes'; CREATE OR REPLACE FUNCTION pgagent.pga_exception_trigger() RETURNS "trigger" AS ' DECLARE v_jobid int4 := 0; BEGIN IF TG_OP = ''DELETE'' THEN SELECT INTO v_jobid jscjobid FROM pgagent.pga_schedule WHERE jscid = OLD.jexscid; -- update pga_job from remaining schedules -- the actual calculation of jobnextrun will be performed in the trigger UPDATE pgagent.pga_job SET jobnextrun = NULL WHERE jobenabled AND jobid = v_jobid; RETURN OLD; ELSE SELECT INTO v_jobid jscjobid FROM pgagent.pga_schedule WHERE jscid = NEW.jexscid; UPDATE pgagent.pga_job SET jobnextrun = NULL WHERE jobenabled AND jobid = v_jobid; RETURN NEW; END IF; END; ' LANGUAGE 'plpgsql' VOLATILE; COMMENT ON FUNCTION pgagent.pga_exception_trigger() IS 'Update the job''s next run time whenever an exception changes'; CREATE TRIGGER pga_exception_trigger AFTER INSERT OR UPDATE OR DELETE ON pgagent.pga_exception FOR EACH ROW EXECUTE PROCEDURE pgagent.pga_exception_trigger(); COMMENT ON TRIGGER pga_exception_trigger ON pgagent.pga_exception IS 'Update the job''s next run time whenever an exception changes'; -- Extension dump support. -- EXT SELECT pg_catalog.pg_extension_config_dump('pga_jobagent', ''); -- EXT SELECT pg_catalog.pg_extension_config_dump('pga_jobclass', $$WHERE jclname NOT IN ('Routine Maintenance', 'Data Import', 'Data Export', 'Data Summarisation', 'Miscellaneous')$$); -- EXT SELECT pg_catalog.pg_extension_config_dump('pga_job', ''); -- EXT SELECT pg_catalog.pg_extension_config_dump('pga_jobstep', ''); -- EXT SELECT pg_catalog.pg_extension_config_dump('pga_schedule', ''); -- EXT SELECT pg_catalog.pg_extension_config_dump('pga_exception', ''); -- EXT SELECT pg_catalog.pg_extension_config_dump('pga_joblog', ''); -- EXT SELECT pg_catalog.pg_extension_config_dump('pga_jobsteplog', ''); COMMIT TRANSACTION; pgagent-pgagent-4.2.3/test/000077500000000000000000000000001473675055000155635ustar00rootroot00000000000000pgagent-pgagent-4.2.3/test/Makefile000066400000000000000000000001351473675055000172220ustar00rootroot00000000000000PG_CONFIG = pg_config REGRESS = init job PGXS = $(shell $(PG_CONFIG) --pgxs) include $(PGXS) pgagent-pgagent-4.2.3/test/expected/000077500000000000000000000000001473675055000173645ustar00rootroot00000000000000pgagent-pgagent-4.2.3/test/expected/init.out000066400000000000000000000000321473675055000210530ustar00rootroot00000000000000CREATE EXTENSION pgagent; pgagent-pgagent-4.2.3/test/expected/job.out000066400000000000000000000023461473675055000206740ustar00rootroot00000000000000CREATE TABLE t ( t timestamptz ); WITH id AS (INSERT INTO pgagent.pga_job (jobjclid, jobname, jobdesc, jobenabled, jobhostagent) SELECT jcl.jclid, 'job1', '', true, '' FROM pgagent.pga_jobclass jcl WHERE jclname='Routine Maintenance' RETURNING jobid), insertstep AS (INSERT INTO pgagent.pga_jobstep (jstjobid, jstname, jstdesc, jstenabled, jstkind, jstonerror, jstcode, jstdbname, jstconnstr) SELECT id.jobid, 'step1', '', true, 's', 'f', 'INSERT INTO t VALUES (now())', 'contrib_regression', '' FROM id) INSERT INTO pgagent.pga_schedule (jscjobid, jscname, jscdesc, jscminutes, jschours, jscweekdays, jscmonthdays, jscmonths, jscenabled, jscstart, jscend) SELECT id.jobid, 'schedule1', '', '{t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t}', '{t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t}', '{t,t,t,t,t,t,t}', '{t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t}', '{t,t,t,t,t,t,t,t,t,t,t,t}', true, '2000-01-01 00:00:00', '2099-12-31 00:00:00' FROM id; \! ( pgagent -f -l 2 -s pgagent.out "user=$PGUSER dbname=contrib_regression" ) & sleep 80; kill $! 2> /dev/null || : SELECT count(*) > 0 AS job_run FROM t; job_run --------- t (1 row) pgagent-pgagent-4.2.3/test/sql/000077500000000000000000000000001473675055000163625ustar00rootroot00000000000000pgagent-pgagent-4.2.3/test/sql/init.sql000066400000000000000000000000321473675055000200410ustar00rootroot00000000000000CREATE EXTENSION pgagent; pgagent-pgagent-4.2.3/test/sql/job.sql000066400000000000000000000023061473675055000176560ustar00rootroot00000000000000CREATE TABLE t ( t timestamptz ); WITH id AS (INSERT INTO pgagent.pga_job (jobjclid, jobname, jobdesc, jobenabled, jobhostagent) SELECT jcl.jclid, 'job1', '', true, '' FROM pgagent.pga_jobclass jcl WHERE jclname='Routine Maintenance' RETURNING jobid), insertstep AS (INSERT INTO pgagent.pga_jobstep (jstjobid, jstname, jstdesc, jstenabled, jstkind, jstonerror, jstcode, jstdbname, jstconnstr) SELECT id.jobid, 'step1', '', true, 's', 'f', 'INSERT INTO t VALUES (now())', 'contrib_regression', '' FROM id) INSERT INTO pgagent.pga_schedule (jscjobid, jscname, jscdesc, jscminutes, jschours, jscweekdays, jscmonthdays, jscmonths, jscenabled, jscstart, jscend) SELECT id.jobid, 'schedule1', '', '{t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t}', '{t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t}', '{t,t,t,t,t,t,t}', '{t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t}', '{t,t,t,t,t,t,t,t,t,t,t,t}', true, '2000-01-01 00:00:00', '2099-12-31 00:00:00' FROM id; \! ( pgagent -f -l 2 -s pgagent.out "user=$PGUSER dbname=contrib_regression" ) & sleep 80; kill $! 2> /dev/null || : SELECT count(*) > 0 AS job_run FROM t; pgagent-pgagent-4.2.3/unix.cpp000066400000000000000000000101521473675055000162720ustar00rootroot00000000000000////////////////////////////////////////////////////////////////////////// // // pgAgent - PostgreSQL Tools // // Copyright (C) 2002 - 2024, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // // unix.cpp - pgAgent unix specific functions // ////////////////////////////////////////////////////////////////////////// #include "pgAgent.h" // *nix only!! #ifndef WIN32 #include #include #include static boost::mutex s_loggerLock; bool printFileErrorMsg = true; using namespace std; void printVersion(); void usage(const std::string &appName) { printVersion(); fprintf(stdout, "Usage:\n"); fprintf(stdout, "%s [options] \n", appName.c_str()); fprintf(stdout, "options:\n"); fprintf(stdout, "-v (display version info and then exit)\n"); fprintf(stdout, "-f run in the foreground (do not detach from the terminal)\n"); fprintf(stdout, "-t \n"); fprintf(stdout, "-r =10, default 30)>\n"); fprintf(stdout, "-s \n"); fprintf(stdout, "-l \n"); } void LogMessage(const std::string &msg, const int &level) { std::ofstream out; bool writeToStdOut = false; MutexLocker locker(&s_loggerLock); if (!logFile.empty()) { std::string log_file(logFile.begin(), logFile.end()); out.open((const char *)log_file.c_str(), ios::out | ios::app); if (!out.is_open()) { if (printFileErrorMsg) { fprintf(stderr, "Can not open the logfile '%s'", log_file.c_str()); printFileErrorMsg = false; } return; } else { printFileErrorMsg = true; } } else writeToStdOut = true; boost::gregorian::date current_date(boost::gregorian::day_clock::local_day()); std::string day_week = boost::lexical_cast(current_date.day_of_week()); std::string year = boost::lexical_cast(current_date.year()); std::string month = boost::lexical_cast(current_date.month()); std::string day = boost::lexical_cast(current_date.day()); boost::posix_time::ptime pt = boost::posix_time::second_clock::local_time(); std::string time_day = boost::lexical_cast(pt.time_of_day()); std::string logTimeString = ""; logTimeString = day_week + " " + month + " " + day + " " + time_day + " " + year + " "; switch (level) { case LOG_DEBUG: if (minLogLevel >= LOG_DEBUG) (writeToStdOut ? std::cout : out) << logTimeString << "DEBUG: " << msg << std::endl; break; case LOG_WARNING: if (minLogLevel >= LOG_WARNING) (writeToStdOut ? std::cout : out) << logTimeString << "WARNING: " << msg << std::endl; break; case LOG_ERROR: (writeToStdOut ? std::cout : out) << logTimeString << "ERROR: " << msg << std::endl; // On system exit, boost::mutex object calls // pthread_mutex_destroy(...) on the underlying native mutex object. // But - it returns errorcode 'BUSY' instead of 0. Because - it was // still keeping the lock on the resource. And, that results into // an assertion in debug mode. // // Hence - we need to unlock the mutex before calling system exit. locker = (boost::mutex *)NULL; exit(1); break; case LOG_STARTUP: (writeToStdOut ? std::cout : out) << logTimeString << "WARNING: " << msg << std::endl; break; } if (!logFile.empty()) { out.close(); } } // Shamelessly lifted from pg_autovacuum... static void daemonize(void) { pid_t pid; pid = fork(); if (pid == (pid_t)-1) { LogMessage("Cannot disassociate from controlling TTY", LOG_ERROR); exit(1); } else if (pid) exit(0); #ifdef HAVE_SETSID if (setsid() < 0) { LogMessage("Cannot disassociate from controlling TTY", LOG_ERROR); exit(1); } #endif } int main(int argc, char **argv) { setlocale(LC_ALL, ""); std::string executable; executable.assign(argv[0]); if (argc < 2) { usage(executable); return 1; } argc--; argv++; setOptions(argc, argv, executable); if (!runInForeground) daemonize(); MainLoop(); return 0; } #endif // !WIN32 pgagent-pgagent-4.2.3/win32.cpp000066400000000000000000000330701473675055000162550ustar00rootroot00000000000000////////////////////////////////////////////////////////////////////////// // // pgAgent - PostgreSQL Tools // // Copyright (C) 2002 - 2024, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // // win32.cpp - pgAgent win32 specific functions // ////////////////////////////////////////////////////////////////////////// #include "pgAgent.h" // This is for Win32 only!! #ifdef WIN32 #include using namespace std; // for debugging purposes, we can start the service paused #define START_SUSPENDED 0 static SERVICE_STATUS serviceStatus; static SERVICE_STATUS_HANDLE serviceStatusHandle; static std::wstring serviceName; static std::wstring user = L".\\Administrator", password; static HANDLE threadHandle = 0; static bool serviceIsRunning; static bool pgagentInitialized; static HANDLE serviceSync; static HANDLE eventHandle; bool stopService(); void printVersion(); // This will be called from MainLoop, if pgagent is initialized properly void Initialized() { pgagentInitialized = true; } // This will be called periodically to check if the service is to be paused. void CheckForInterrupt() { serviceIsRunning = false; long prevCount; ReleaseSemaphore(serviceSync, 1, &prevCount); // if prevCount is zero, the service should be paused. // We're waiting for the semaphore to get signaled again. if (!prevCount) WaitForSingleObject(serviceSync, INFINITE); serviceIsRunning = true; } void LogMessage(const std::string &_msg, const int &level) { const std::wstring msg = s2ws(_msg); if (eventHandle) { LPCWSTR tmp; tmp = _wcsdup(msg.c_str()); switch (level) { case LOG_DEBUG: if (minLogLevel >= LOG_DEBUG) ReportEventW(eventHandle, EVENTLOG_INFORMATION_TYPE, 0, 0, NULL, 1, 0, &tmp, NULL); break; case LOG_WARNING: if (minLogLevel >= LOG_WARNING) ReportEventW(eventHandle, EVENTLOG_WARNING_TYPE, 0, 0, NULL, 1, 0, &tmp, NULL); break; case LOG_ERROR: ReportEventW(eventHandle, EVENTLOG_ERROR_TYPE, 0, 0, NULL, 1, 0, &tmp, NULL); stopService(); // Set pgagent initialized to true, as initService // is waiting for it to be intialized pgagentInitialized = true; // Change service status serviceStatus.dwCheckPoint = 0; serviceStatus.dwCurrentState = SERVICE_STOPPED; SetServiceStatus(serviceStatusHandle, &serviceStatus); break; // Log startup/connection warnings (valid for any log level) case LOG_STARTUP: ReportEventW(eventHandle, EVENTLOG_WARNING_TYPE, 0, 0, NULL, 1, 0, &tmp, NULL); break; } if (tmp) { free((void *)tmp); tmp = NULL; } } else { switch (level) { case LOG_DEBUG: if (minLogLevel >= LOG_DEBUG) wprintf(L"DEBUG: %s\n", msg.c_str()); break; case LOG_WARNING: if (minLogLevel >= LOG_WARNING) wprintf(L"WARNING: %s\n", msg.c_str()); break; case LOG_ERROR: wprintf(L"ERROR: %s\n", msg.c_str()); pgagentInitialized = true; exit(1); break; // Log startup/connection warnings (valid for any log level) case LOG_STARTUP: wprintf(L"WARNING: %s\n", msg.c_str()); break; } } } // The main working thread of the service unsigned int __stdcall threadProcedure(void *unused) { MainLoop(); return 0; } //////////////////////////////////////////////////////////// // a replacement popen for windows. // // _popen doesn't work in Win2K from a service so we have to // do it the fun way :-) HANDLE win32_popen_r(const WCHAR *command, HANDLE &handle) { HANDLE hWrite, hRead; SECURITY_ATTRIBUTES saAttr; BOOL ret = FALSE; PROCESS_INFORMATION piProcInfo; STARTUPINFOW siStartInfo; WCHAR *cmd; cmd = _wcsdup(command); // Set the bInheritHandle flag so pipe handles are inherited. saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; // Create a pipe for the child process's STDOUT. if (!CreatePipe(&hRead, &hWrite, &saAttr, 0)) return NULL; // Ensure the read handle to the pipe for STDOUT is not inherited. SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0); // Now create the child process. ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); siStartInfo.cb = sizeof(STARTUPINFO); siStartInfo.hStdError = hWrite; siStartInfo.hStdOutput = hWrite; siStartInfo.dwFlags |= STARTF_USESTDHANDLES; ret = CreateProcessW(NULL, cmd, // command line NULL, // process security attributes NULL, // primary thread security attributes TRUE, // handles are inherited 0, // creation flags NULL, // use parent's environment NULL, // use parent's current directory &siStartInfo, // STARTUPINFO pointer &piProcInfo); // receives PROCESS_INFORMATION if (!ret) return NULL; else CloseHandle(piProcInfo.hThread); // Close the write end of the pipe and return the read end. if (!CloseHandle(hWrite)) return NULL; handle = piProcInfo.hProcess; return hRead; } //////////////////////////////////////////////////////////// // service control functions bool pauseService() { WaitForSingleObject(serviceSync, shortWait * 1000 - 30); if (!serviceIsRunning) { SuspendThread(threadHandle); return true; } return false; } bool continueService() { ReleaseSemaphore(serviceSync, 1, 0); ResumeThread(threadHandle); return true; } bool stopService() { pauseService(); CloseHandle(threadHandle); threadHandle = 0; return true; } bool initService() { serviceSync = CreateSemaphore(0, 1, 1, 0); unsigned int tid; pgagentInitialized = false; threadHandle = (HANDLE)_beginthreadex(0, 0, threadProcedure, 0, 0, &tid); while (!pgagentInitialized) { if (eventHandle) { serviceStatus.dwWaitHint += 1000; serviceStatus.dwCheckPoint++; SetServiceStatus(serviceStatusHandle, (LPSERVICE_STATUS)&serviceStatus); } Sleep(1000); } return (threadHandle != 0); } void CALLBACK serviceHandler(DWORD ctl) { switch (ctl) { case SERVICE_CONTROL_STOP: { serviceStatus.dwCheckPoint++; serviceStatus.dwCurrentState = SERVICE_STOP_PENDING; SetServiceStatus(serviceStatusHandle, &serviceStatus); stopService(); serviceStatus.dwCheckPoint = 0; serviceStatus.dwCurrentState = SERVICE_STOPPED; SetServiceStatus(serviceStatusHandle, &serviceStatus); break; } case SERVICE_CONTROL_PAUSE: { pauseService(); serviceStatus.dwCurrentState = SERVICE_PAUSED; SetServiceStatus(serviceStatusHandle, &serviceStatus); break; } case SERVICE_CONTROL_CONTINUE: { continueService(); serviceStatus.dwCurrentState = SERVICE_RUNNING; SetServiceStatus(serviceStatusHandle, &serviceStatus); break; } default: { break; } } } void CALLBACK serviceMain(DWORD argc, LPTSTR *argv) { serviceName = s2ws((const char *)argv[0]); serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; serviceStatus.dwCurrentState = SERVICE_START_PENDING; serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE; serviceStatus.dwWin32ExitCode = 0; serviceStatus.dwCheckPoint = 0; serviceStatus.dwWaitHint = 15000; serviceStatusHandle = RegisterServiceCtrlHandlerW(serviceName.c_str(), serviceHandler); if (serviceStatusHandle) { SetServiceStatus(serviceStatusHandle, &serviceStatus); if (initService()) { serviceStatus.dwCurrentState = SERVICE_RUNNING; serviceStatus.dwWaitHint = 1000; } else serviceStatus.dwCurrentState = SERVICE_STOPPED; SetServiceStatus(serviceStatusHandle, &serviceStatus); } } //////////////////////////////////////////////////////////// // installation and removal bool installService(const std::wstring &serviceName, const std::wstring &executable, const std::wstring &args, const std::wstring &displayname, const std::wstring &user, const std::wstring &password) { bool done = false; SC_HANDLE manager = OpenSCManager(0, 0, SC_MANAGER_ALL_ACCESS); if (manager) { std::wstring cmd = executable + L" " + args; std::wstring quser; if (user.find(L"\\") == std::string::npos) quser = L".\\" + user; else quser = user; SC_HANDLE service = CreateServiceW(manager, serviceName.c_str(), displayname.c_str(), SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, cmd.c_str(), 0, 0, 0, quser.c_str(), password.c_str()); if (service) { done = true; CloseServiceHandle(service); } else { LPVOID lpMsgBuf; DWORD dw = GetLastError(); FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL ); LogMessage(ws2s((boost::wformat(L"%s") % lpMsgBuf).str()), LOG_ERROR); } CloseServiceHandle(manager); } // Setup the event message DLL const std::wstring key_path(L"SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\" + serviceName); HKEY key; DWORD last_error = RegCreateKeyExW(HKEY_LOCAL_MACHINE, key_path.c_str(), 0, 0, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, 0, &key, 0); if (ERROR_SUCCESS == last_error) { std::size_t found = executable.find_last_of(L"/\\"); std::wstring path = executable.substr(0, found) + L"\\pgaevent.dll"; DWORD last_error; const DWORD types_supported = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE; last_error = RegSetValueExW(key, L"EventMessageFile", 0, REG_SZ, (unsigned char *)(path.c_str()), (path.length() + 1)*sizeof(std::wstring)); if (ERROR_SUCCESS == last_error) { last_error = RegSetValueExW(key, L"TypesSupported", 0, REG_DWORD, (LPBYTE)&types_supported, sizeof(types_supported)); } if (ERROR_SUCCESS != last_error) { LogMessage( "Could not set the event message file registry value.", LOG_WARNING ); } RegCloseKey(key); } else { LogMessage( "Could not open the message source registry key.", LOG_WARNING ); } return done; } bool removeService(const std::wstring &serviceName) { bool done = false; SC_HANDLE manager = OpenSCManager(0, 0, SC_MANAGER_ALL_ACCESS); if (manager) { SC_HANDLE service = OpenServiceW(manager, serviceName.c_str(), SERVICE_ALL_ACCESS); if (service) { SERVICE_STATUS serviceStatus; ControlService(service, SERVICE_CONTROL_STOP, &serviceStatus); int retries; for (retries = 0; retries < 5; retries++) { if (QueryServiceStatus(service, &serviceStatus)) { if (serviceStatus.dwCurrentState == SERVICE_STOPPED) { DeleteService(service); done = true; break; } Sleep(1000L); } } CloseServiceHandle(service); } CloseServiceHandle(manager); } // Remove the event message DLL const std::wstring key_path(L"SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\" + serviceName); DWORD last_error = RegDeleteKeyW(HKEY_LOCAL_MACHINE, key_path.c_str()); if (ERROR_SUCCESS != last_error) { LogMessage("Failed to uninstall source", LOG_ERROR); } return done; } void usage(const std::string &executable) { printVersion(); printf("Usage:\n"); printf("%s REMOVE \n", executable.c_str()); printf("%s INSTALL [options] \n", executable.c_str()); printf("%s DEBUG [options] \n", executable.c_str()); printf("options:\n"); printf("-v (display version info and then exit)\n"); printf("-u \n"); printf("-p \n"); printf("-d \n"); printf("-t \n"); printf("-r =10, default 30)>\n"); printf("-l \n"); } //////////////////////////////////////////////////////////// void setupForRun(int argc, char **argv, bool debug, const std::wstring &executable) { if (!debug) { eventHandle = RegisterEventSourceW(0, serviceName.c_str()); if (!eventHandle) LogMessage("Couldn't register event handle.", LOG_ERROR); } setOptions(argc, argv, ws2s(executable)); } void main(int argc, char **argv) { std::string executable; executable.assign(*argv++); if (argc < 3) { usage(executable); return; } std::wstring command; command.assign(s2ws(*argv++)); if (command != L"DEBUG") { serviceName.assign(s2ws(*argv++)); argc -= 3; } else argc -= 2; if (command == L"INSTALL") { std::wstring displayname = L"PostgreSQL Scheduling Agent - " + serviceName; std::wstring args = L"RUN " + serviceName; while (argc-- > 0) { if (argv[0][0] == '-') { switch (argv[0][1]) { case 'u': { user = s2ws(getArg(argc, argv)); break; } case 'p': { password = s2ws(getArg(argc, argv)); break; } case 'd': { displayname = s2ws(getArg(argc, argv)); break; } default: { args += L" " + s2ws(*argv); break; } } } else { args += L" " + s2ws(*argv); } argv++; } bool rc = installService(serviceName, s2ws(executable), args, displayname, user, password); } else if (command == L"REMOVE") { bool rc = removeService(serviceName); } else if (command == L"DEBUG") { setupForRun(argc, argv, true, s2ws(executable)); initService(); #if START_SUSPENDED continueService(); #endif WaitForSingleObject(threadHandle, INFINITE); } else if (command == L"RUN") { std::string app = "pgAgent Service"; SERVICE_TABLE_ENTRY serviceTable[] = { (LPSTR)app.c_str(), serviceMain, 0, 0 }; setupForRun(argc, argv, false, s2ws(executable)); if (!StartServiceCtrlDispatcher(serviceTable)) { DWORD rc = GetLastError(); if (rc) { } } } else { usage(executable); } return; } #endif // WIN32