pax_global_header00006660000000000000000000000064144376633660014534gustar00rootroot0000000000000052 comment=4264398fab01b94e36eb58f53cd89ede4ead6d63 timestamp9-timestamp9-1.4.0/000077500000000000000000000000001443766336600157245ustar00rootroot00000000000000timestamp9-timestamp9-1.4.0/CMakeLists.txt000066400000000000000000000127131443766336600204700ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.4) configure_file("version.config" "version.config" COPYONLY) file(READ version.config VERSION_CONFIG) set(VERSION_REGEX "version[\t ]*=[\t ]*([0-9]+\\.[0-9]+\\.*[0-9]*)([-]([a-z]+))*\r?\nupdate_from_version[\t ]*=[\t ]*([0-9]+\\.[0-9]+\\.*[0-9]*)(\r?\n)*$") if (NOT (${VERSION_CONFIG} MATCHES ${VERSION_REGEX})) message(FATAL_ERROR "Cannot read version from version.config") endif () set(VERSION ${CMAKE_MATCH_1}) set(VERSION_MOD ${CMAKE_MATCH_3}) set(UPDATE_FROM_VERSION ${CMAKE_MATCH_4}) if (VERSION_MOD) set(PROJECT_VERSION_MOD ${VERSION}-${VERSION_MOD}) else () set(PROJECT_VERSION_MOD ${VERSION}) endif () if (NOT CMAKE_BUILD_TYPE) # Default to Release builds set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel" FORCE) endif () if (WIN32) if (NOT CMAKE_CONFIGURATION_TYPES) # Default to only include Release builds so MSBuild.exe 'just works' set(CMAKE_CONFIGURATION_TYPES Release CACHE STRING "Semicolon separated list of supported configuration types, only supports Debug, Release, MinSizeRel, and RelWithDebInfo, anything else will be ignored." FORCE) endif () endif (WIN32) project(timestamp9 VERSION ${VERSION}) message(STATUS "Timestamp9 version ${PROJECT_VERSION_MOD}. Can be updated from version ${UPDATE_FROM_VERSION}") message(STATUS "Build type is ${CMAKE_BUILD_TYPE}") # Search paths for Postgres binaries if (WIN32) find_path(PG_PATH bin/postgres HINTS "C:/PostgreSQL" "C:/Program Files/PostgreSQL" PATH_SUFFIXES bin 10/bin 96/bin pg96/bin DOC "The path to a PostgreSQL installation") endif (WIN32) if (UNIX) find_path(PG_PATH bin/postgres HINTS $ENV{HOME} /opt/local/pgsql /usr/local/pgsql /usr/lib/postgresql PATH_SUFFIXES bin 10/bin 9.6/bin 96/bin pg96/bin DOC "The path to a PostgreSQL installation") endif (UNIX) find_program(PG_CONFIG pg_config HINTS ${PG_PATH} PATH_SUFFIXES bin DOC "The path to the pg_config of the PostgreSQL version to compile against" REQUIRED) if (NOT PG_CONFIG) message(FATAL_ERROR "Unable to find 'pg_config'") endif () message(STATUS "Using pg_config ${PG_CONFIG}") # Check PostgreSQL version execute_process( COMMAND ${PG_CONFIG} --version OUTPUT_VARIABLE PG_VERSION_STRING OUTPUT_STRIP_TRAILING_WHITESPACE) if (NOT ${PG_VERSION_STRING} MATCHES "^PostgreSQL[ ]+([0-9]+)\\.?([0-9]*)(\\.([0-9]+))*(devel)?") message(FATAL_ERROR "Could not parse PostgreSQL version ${PG_VERSION_STRING}") endif () set(PG_VERSION_MAJOR ${CMAKE_MATCH_1}) set(PG_VERSION_MINOR ${CMAKE_MATCH_2}) set(PG_VERSION_PATCH ${CMAKE_MATCH_4}) if (NOT ${PG_VERSION_PATCH} OR ${PG_VERSION_PATCH} EQUAL "") set(PG_VERSION "${PG_VERSION_MAJOR}.${PG_VERSION_MINOR}") else () set(PG_VERSION "${PG_VERSION_MAJOR}.${PG_VERSION_MINOR}.${PG_VERSION_PATCH}") endif () message(STATUS "Compiling against PostgreSQL version ${PG_VERSION}") # Get PostgreSQL configuration from pg_config execute_process( COMMAND ${PG_CONFIG} --includedir OUTPUT_VARIABLE PG_INCLUDEDIR OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process( COMMAND ${PG_CONFIG} --includedir-server OUTPUT_VARIABLE PG_INCLUDEDIR_SERVER OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process( COMMAND ${PG_CONFIG} --libdir OUTPUT_VARIABLE PG_LIBDIR OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process( COMMAND ${PG_CONFIG} --pkglibdir OUTPUT_VARIABLE PG_PKGLIBDIR OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process( COMMAND ${PG_CONFIG} --sharedir OUTPUT_VARIABLE PG_SHAREDIR OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process( COMMAND ${PG_CONFIG} --bindir OUTPUT_VARIABLE PG_BINDIR OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process( COMMAND ${PG_CONFIG} --cppflags OUTPUT_VARIABLE PG_CPPFLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process( COMMAND ${PG_CONFIG} --cflags OUTPUT_VARIABLE PG_CFLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process( COMMAND ${PG_CONFIG} --ldflags OUTPUT_VARIABLE PG_LDFLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process( COMMAND ${PG_CONFIG} --libs OUTPUT_VARIABLE PG_LIBS OUTPUT_STRIP_TRAILING_WHITESPACE) find_path(PG_SOURCE_DIR src/include/pg_config.h.in HINTS $ENV{HOME} $ENV{HOME}/projects $ENV{HOME}/Projects $ENV{HOME}/development $ENV{HOME}/Development $ENV{HOME}/workspace PATH_SUFFIXES postgres postgresql pgsql DOC "The path to the PostgreSQL source tree") if (PG_SOURCE_DIR) message(STATUS "Found PostgreSQL source in ${PG_SOURCE_DIR}") endif (PG_SOURCE_DIR) add_custom_target(controlfile ALL DEPENDS timestamp9.control) add_subdirectory(src) add_subdirectory(tests) install( FILES timestamp9.control DESTINATION "${PG_SHAREDIR}/extension") macro(get_parents DIR DIR_LIST) list(APPEND DIR_LIST ${DIR}) set(PDIR ${DIR}) while (NOT (${PDIR} STREQUAL "/")) list(APPEND DIR_LIST ${PDIR}) get_filename_component(PDIR ${PDIR} DIRECTORY) endwhile () endmacro() set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION_MOD}) set(CPACK_GENERATOR "RPM") set(CPACK_PACKAGE_NAME ${PROJECT_NAME}-PG${PG_VERSION_MAJOR}) set(CPACK_PACKAGE_RELEASE 1) set(CPACK_PACKAGE_CONTACT "Optiver") set(CPACK_PACKAGE_VENDOR "Optiver") set(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}) set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CPACK_PACKAGE_RELEASE}.${CMAKE_SYSTEM_PROCESSOR}") get_parents(${PG_SHAREDIR}/extension DIR_LIST) get_parents(${PG_PKGLIBDIR} DIR_LIST) set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION ${DIR_LIST} "/usr/local") include(CPack) timestamp9-timestamp9-1.4.0/LICENSE000066400000000000000000000020571443766336600167350ustar00rootroot00000000000000MIT License Copyright (c) 2018 Floris van Nee Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. timestamp9-timestamp9-1.4.0/README.md000066400000000000000000000050341443766336600172050ustar00rootroot00000000000000## Extension timestamp9 An efficient nanosecond precision timestamp type for Postgres ## Build & install ``` git clone https://github.com/fvannee/timestamp9.git cd timestamp9 mkdir build cd build cmake .. # or: cmake .. -DPG_CONFIG=/path/to/pg_config make sudo make install ``` ## Usage Internally, timestamp9 is stored in a 64-bit number as the number of nanoseconds since the UNIX epoch. This means the minimum and maximum representable time is: ``` postgres=# select 0::bigint::timestamp9; timestamp9 ------------------------------------- 1970-01-01 01:00:00.000000000 +0100 (1 row) postgres=# select 9223372036854775807::timestamp9; timestamp9 ------------------------------------- 2262-04-12 01:47:16.854775807 +0200 (1 row) ``` Timestamp input can be given either as the number of nanoseconds since Jan 1st 1970, which can be casted to timestamp9 as above, or it can be casted from text format. Both regular Postgres timestamptz text format, as well as a custom nanosecond text format are supported as inputs. ``` postgres=# select '2019-09-19 08:30:05.123456789 +0200'::timestamp9; timestamp9 ------------------------------------- 2019-09-19 08:30:05.123456789 +0200 (1 row) postgres=# select '2019-09-19 08:30:05'::timestamp9; timestamp9 ------------------------------------- 2019-09-19 08:30:05.000000000 +0200 (1 row) ``` A subset of the default operators and conversions is supported for timestamp9 types: - Cast from/to timestamp(tz) ``` postgres=# select now()::timestamp9::timestamptz::timestamp::timestamp9; now ------------------------------------- 2019-09-19 23:22:07.973781000 +0200 (1 row) ``` - Cast from/to date ``` postgres=# select current_date::timestamp9; current_date ------------------------------------- 2019-09-19 00:00:00.000000000 +0200 (1 row) ``` - Comparisons like greater than, less than etc. as well as use in btree/hash indices ``` postgres=# select '2019-09-19'::timestamp9 < '2019-09-20'::timestamp9, greatest(now()::timestamp9, '2019-01-01'::timestamp9); ?column? | greatest ----------+------------------------------------- t | 2019-09-19 23:27:21.364791000 +0200 (1 row) ``` - Addition and subtraction of intervals ``` postgres=# select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 + interval '1d'; ?column? ------------------------------------- 2019-09-20 23:00:00.123456789 +0200 (1 row) ``` timestamp9-timestamp9-1.4.0/build.sh000077500000000000000000000004461443766336600173660ustar00rootroot00000000000000set -e mkdir build cd build supported_versions=( 11 12 ) for version in "${supported_versions[@]}" do rm -rf * cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo -DPG_CONFIG=/usr/pgsql-${version}/bin/pg_config make -j4 cpack cp *rpm ../ done cp ../*rpm /mnt/releases/postgresql timestamp9-timestamp9-1.4.0/src/000077500000000000000000000000001443766336600165135ustar00rootroot00000000000000timestamp9-timestamp9-1.4.0/src/CMakeLists.txt000066400000000000000000000056501443766336600212610ustar00rootroot00000000000000set(CMAKE_C_FLAGS_DEBUG "-DUSE_ASSERT_CHECKING=1") if (UNIX) set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -L${PG_LIBDIR}") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -L${PG_LIBDIR}") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${PG_CFLAGS}") set(CMAKE_CPP_FLAGS "${CMAKE_CPP_FLAGS} ${PG_CPPFLAGS}") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g") endif (UNIX) if (APPLE) set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -multiply_defined suppress") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -multiply_defined suppress -Wl,-undefined,dynamic_lookup -Wl,-dead_strip_dylibs -bundle_loader ${PG_BINDIR}/postgres") elseif (WIN32) set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /MANIFEST:NO") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} /MANIFEST:NO") endif (APPLE) include_directories (${CMAKE_CURRENT_BINARY_DIR} ${PG_INCLUDEDIR} ${PG_INCLUDEDIR_SERVER}) if (WIN32) set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${PG_LIBDIR}/postgres.lib ws2_32.lib") set(CMAKE_C_FLAGS "-D_CRT_SECURE_NO_WARNINGS") include_directories(${PG_INCLUDEDIR_SERVER}/port/win32) if (MSVC) include_directories(${PG_INCLUDEDIR_SERVER}/port/win32_msvc) endif (MSVC) endif (WIN32) set(HEADERS timestamp9.h) set(SOURCES timestamp9.c) add_library(${PROJECT_NAME} MODULE ${SOURCES} ${HEADERS}) set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${PROJECT_NAME} PREFIX "") # Function to concatenate all files in SRC_FILE_LIST into file OUTPUT_FILE function(cat_files SRC_FILE_LIST OUTPUT_FILE) if (WIN32) # Make list of files into string of files separated by "+" # to make Windows copy concatenate them file(TO_NATIVE_PATH "${SRC_FILE_LIST}" SRC_FILE_LIST_NATIVE) string(REPLACE ";" ";+" SQL_LIST_JOINED "${SRC_FILE_LIST_NATIVE}") file(TO_NATIVE_PATH "${OUTPUT_FILE}" OUTPUT_FILE_NATIVE) set(CAT_CMD copy /B /y ${SQL_LIST_JOINED} "\"${OUTPUT_FILE_NATIVE}\"" >NUL) else () set(CAT_CMD cat ${SRC_FILE_LIST} > ${OUTPUT_FILE}) endif () add_custom_command( OUTPUT ${OUTPUT_FILE} DEPENDS ${SRC_FILE_LIST} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${CAT_CMD} COMMENT "Generating ${OUTPUT_FILE}" ) endfunction() set(INSTALL_FILE ${PROJECT_NAME}--${PROJECT_VERSION_MOD}.sql) cat_files("timestamp9.sql" ${CMAKE_CURRENT_BINARY_DIR}/${INSTALL_FILE}) add_custom_target(sqlfile ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${INSTALL_FILE}) set(MOD_FILES timestamp9--0.1.0--0.2.0.sql timestamp9--0.2.0--0.3.0.sql timestamp9--0.3.0--1.0.0.sql timestamp9--1.0.0--1.0.1.sql timestamp9--1.0.1--1.1.0.sql timestamp9--1.1.0--1.2.0.sql timestamp9--1.2.0--1.3.0.sql timestamp9--1.3.0--1.4.0.sql ) add_custom_target(sqlupdatescripts ALL DEPENDS ${MOD_FILES}) install( TARGETS ${PROJECT_NAME} DESTINATION ${PG_PKGLIBDIR}) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/${INSTALL_FILE} ${MOD_FILES} DESTINATION "${PG_SHAREDIR}/extension") timestamp9-timestamp9-1.4.0/src/timestamp9--0.1.0--0.2.0.sql000066400000000000000000000014101443766336600225430ustar00rootroot00000000000000 CREATE OR REPLACE FUNCTION timestamp9_to_date(timestamp9) RETURNS date AS '$libdir/timestamp9' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE CAST (timestamp9 AS date) WITH FUNCTION timestamp9_to_date(timestamp9) AS ASSIGNMENT; CREATE OR REPLACE FUNCTION date_to_timestamp9(date) RETURNS timestamp9 AS '$libdir/timestamp9' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE CAST (date AS timestamp9) WITH FUNCTION date_to_timestamp9(date) AS IMPLICIT; CREATE OR REPLACE FUNCTION hash_timestamp9(timestamp9) RETURNS integer AS 'hashint8' LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE OPERATOR CLASS hash_timestamp9_ops FOR TYPE timestamp9 USING hash FAMILY integer_ops AS OPERATOR 1 =, FUNCTION 1 hash_timestamp9(timestamp9); timestamp9-timestamp9-1.4.0/src/timestamp9--0.2.0--0.3.0.sql000066400000000000000000000024711443766336600225550ustar00rootroot00000000000000 CREATE FUNCTION timestamp9_larger(timestamp9, timestamp9) RETURNS timestamp9 AS '$libdir/timestamp9' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE AGGREGATE max(timestamp9) ( SFUNC = timestamp9_larger, STYPE = timestamp9, SORTOP = > ); CREATE FUNCTION timestamp9_smaller(timestamp9, timestamp9) RETURNS timestamp9 AS '$libdir/timestamp9' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE AGGREGATE min(timestamp9) ( SFUNC = timestamp9_smaller, STYPE = timestamp9, SORTOP = < ); CREATE FUNCTION timestamp9_interval_pl(timestamp9, interval) RETURNS timestamp9 AS '$libdir/timestamp9' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE OPERATOR + ( LEFTARG = timestamp9, RIGHTARG = interval, PROCEDURE = timestamp9_interval_pl, COMMUTATOR = + ); CREATE FUNCTION timestamp9_interval_mi(timestamp9, interval) RETURNS timestamp9 AS '$libdir/timestamp9' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE OPERATOR - ( LEFTARG = timestamp9, RIGHTARG = interval, PROCEDURE = timestamp9_interval_mi ); CREATE FUNCTION interval_timestamp9_pl(interval, timestamp9) RETURNS timestamp9 AS '$libdir/timestamp9' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE OPERATOR + ( LEFTARG = interval, RIGHTARG = timestamp9, PROCEDURE = interval_timestamp9_pl, COMMUTATOR = + ); timestamp9-timestamp9-1.4.0/src/timestamp9--0.3.0--1.0.0.sql000066400000000000000000000000001443766336600225360ustar00rootroot00000000000000timestamp9-timestamp9-1.4.0/src/timestamp9--1.0.0--1.0.1.sql000066400000000000000000000000001443766336600225350ustar00rootroot00000000000000timestamp9-timestamp9-1.4.0/src/timestamp9--1.0.1--1.1.0.sql000066400000000000000000000000001443766336600225360ustar00rootroot00000000000000timestamp9-timestamp9-1.4.0/src/timestamp9--1.1.0--1.2.0.sql000066400000000000000000000003241443766336600225500ustar00rootroot00000000000000DROP OPERATOR CLASS hash_timestamp9_ops USING hash; CREATE OPERATOR CLASS hash_timestamp9_ops DEFAULT FOR TYPE timestamp9 USING hash FAMILY integer_ops AS OPERATOR 1 =, FUNCTION 1 hash_timestamp9(timestamp9); timestamp9-timestamp9-1.4.0/src/timestamp9--1.2.0--1.3.0.sql000066400000000000000000000000001443766336600225410ustar00rootroot00000000000000timestamp9-timestamp9-1.4.0/src/timestamp9--1.3.0--1.4.0.sql000066400000000000000000000000001443766336600225430ustar00rootroot00000000000000timestamp9-timestamp9-1.4.0/src/timestamp9.c000066400000000000000000000450741443766336600207650ustar00rootroot00000000000000 #include "postgres.h" #include #include #include #include "access/hash.h" #include "catalog/pg_type.h" #include "libpq/pqformat.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/timestamp.h" #include "utils/date.h" #include "utils/datetime.h" #include "parser/scansup.h" #include "pgtime.h" #include "timestamp9.h" PG_MODULE_MAGIC; PG_FUNCTION_INFO_V1(timestamp9_in); PG_FUNCTION_INFO_V1(timestamp9_out); PG_FUNCTION_INFO_V1(timestamp9_recv); PG_FUNCTION_INFO_V1(timestamp9_send); PG_FUNCTION_INFO_V1(timestamp9_eq); PG_FUNCTION_INFO_V1(timestamp9_ne); PG_FUNCTION_INFO_V1(timestamp9_lt); PG_FUNCTION_INFO_V1(timestamp9_le); PG_FUNCTION_INFO_V1(timestamp9_gt); PG_FUNCTION_INFO_V1(timestamp9_ge); PG_FUNCTION_INFO_V1(bt_timestamp9_cmp); PG_FUNCTION_INFO_V1(timestamp9_to_timestamptz); PG_FUNCTION_INFO_V1(timestamptz_to_timestamp9); PG_FUNCTION_INFO_V1(timestamp9_to_timestamp); PG_FUNCTION_INFO_V1(timestamp_to_timestamp9); PG_FUNCTION_INFO_V1(timestamp9_to_date); PG_FUNCTION_INFO_V1(date_to_timestamp9); PG_FUNCTION_INFO_V1(timestamp9_larger); PG_FUNCTION_INFO_V1(timestamp9_smaller); PG_FUNCTION_INFO_V1(timestamp9_interval_pl); PG_FUNCTION_INFO_V1(interval_timestamp9_pl); PG_FUNCTION_INFO_V1(timestamp9_interval_mi); #define kT_ns_in_s (int64_t)1000000000 #define kT_ns_in_us (int64_t)1000 #define NO_COLON_TZ_OFFSET_LEN (size_t)4 /* length of string 0200 */ #define COLON_TZ_OFFSET_LEN (size_t)5 /* length of string 02:00 */ static TimestampTz timestamp9_to_timestamptz_internal(timestamp9 ts9) { int64 us = ts9 / 1000; us -= ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY * USECS_PER_SEC); /* Recheck in case roundoff produces something just out of range */ if (!IS_VALID_TIMESTAMP(us)) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp9 out of range: \"%lld\"", ts9))); return us; } static timestamp9 timestamptz_to_timestamp9_internal(TimestampTz ts) { int64 ns; ts += ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY * USECS_PER_SEC); ns = ts * 1000; return ns; } static timestamp9 date2timestamp9(DateADT dateVal) { timestamp9 result; struct pg_tm tt; struct pg_tm* tm = &tt; int tz; if (DATE_IS_NOBEGIN(dateVal) || DATE_IS_NOEND(dateVal)) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("date out of range for timestamp"))); else { /* * Date's range is wider than timestamp's, so check for boundaries. * Since dates have the same minimum values as timestamps, only upper * boundary need be checked for overflow. */ if (dateVal >= (TIMESTAMP9_END_JULIAN - UNIX_EPOCH_JDATE)) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("date out of range for timestamp"))); j2date(dateVal + POSTGRES_EPOCH_JDATE, &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday)); tm->tm_hour = 0; tm->tm_min = 0; tm->tm_sec = 0; tz = DetermineTimeZoneOffset(tm, session_timezone); result = dateVal * USECS_PER_DAY * kT_ns_in_us + tz * USECS_PER_SEC * kT_ns_in_us + (POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * USECS_PER_DAY * kT_ns_in_us; } return result; } static TimestampTz timestamp2timestamptz(Timestamp timestamp) { TimestampTz result; struct pg_tm tt; struct pg_tm* tm = &tt; fsec_t fsec; int tz; if (TIMESTAMP_NOT_FINITE(timestamp)) result = timestamp; else { if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); tz = DetermineTimeZoneOffset(tm, session_timezone); if (tm2timestamp(tm, fsec, &tz, &result) != 0) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); } return result; } static Timestamp timestamptz2timestamp(TimestampTz timestamp) { Timestamp result; struct pg_tm tt; struct pg_tm* tm = &tt; fsec_t fsec; int tz; if (TIMESTAMP_NOT_FINITE(timestamp)) result = timestamp; else { if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); if (tm2timestamp(tm, fsec, NULL, &result) != 0) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); } return result; } /* * This function is directly copied from backend/utils/adt/timestamp.c. * Additionally, we check if the numeric time zone contains a valid numeric * part, since DecodeTimezone doesn't check it. */ static int parse_sane_timezone(struct pg_tm *tm, char *tzname) #if PG16_GE { int dterr; int tz; /* * Look up the requested timezone. First we try to interpret it as a * numeric timezone specification; if DecodeTimezone decides it doesn't * like the format, we try timezone abbreviations and names. * * Note pg_tzset happily parses numeric input that DecodeTimezone would * reject. To avoid having it accept input that would otherwise be seen * as invalid, it's enough to disallow having a digit in the first * position of our input string. */ if (isdigit((unsigned char) *tzname)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid input syntax for type %s: \"%s\"", "numeric time zone", tzname), errhint("Numeric time zones must have \"-\" or \"+\" as first character."))); /* * If tzname is a numeric time zone, let's check if it contains a valid numeric part. */ if ((tzname[0] == '+' || tzname[0] == '-') && tzname[1] == '\0') { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid input syntax for type %s: \"%s\"", "numeric timezone", tzname), errhint("Numeric time zones must have the numeric part"))); } dterr = DecodeTimezone(tzname, &tz); if (dterr != 0) { int type, val; pg_tz *tzp; if (dterr == DTERR_TZDISP_OVERFLOW) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("numeric time zone \"%s\" out of range", tzname))); else if (dterr != DTERR_BAD_FORMAT) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("time zone \"%s\" not recognized", tzname))); type = DecodeTimezoneName(tzname, &val, &tzp); if (type == TZNAME_FIXED_OFFSET) { /* fixed-offset abbreviation */ tz = -val; } else if (type == TZNAME_DYNTZ) { /* dynamic-offset abbreviation, resolve using specified time */ tz = DetermineTimeZoneAbbrevOffset(tm, tzname, tzp); } else { /* full zone name */ tz = DetermineTimeZoneOffset(tm, tzp); } } return tz; } #else { int rt; int tz; /* * Look up the requested timezone. First we try to interpret it as a * numeric timezone specification; if DecodeTimezone decides it doesn't * like the format, we look in the timezone abbreviation table (to handle * cases like "EST"), and if that also fails, we look in the timezone * database (to handle cases like "America/New_York"). (This matches the * order in which timestamp input checks the cases; it's important because * the timezone database unwisely uses a few zone names that are identical * to offset abbreviations.) * * Note pg_tzset happily parses numeric input that DecodeTimezone would * reject. To avoid having it accept input that would otherwise be seen * as invalid, it's enough to disallow having a digit in the first * position of our input string. */ if (isdigit((unsigned char) *tzname)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid input syntax for type %s: \"%s\"", "numeric time zone", tzname), errhint("Numeric time zones must have \"-\" or \"+\" as first character."))); /* * If tzname is a numeric time zone, let's check if it contains a valid numeric part. */ if ((tzname[0] == '+' || tzname[0] == '-') && tzname[1] == '\0') { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid input syntax for type %s: \"%s\"", "numeric timezone", tzname), errhint("Numeric time zones must have the numeric part"))); } rt = DecodeTimezone(tzname, &tz); if (rt != 0) { char *lowzone; int type, val; pg_tz *tzp; if (rt == DTERR_TZDISP_OVERFLOW) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("numeric time zone \"%s\" out of range", tzname))); else if (rt != DTERR_BAD_FORMAT) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("time zone \"%s\" not recognized", tzname))); /* DecodeTimezoneAbbrev requires lowercase input */ lowzone = downcase_truncate_identifier(tzname, strlen(tzname), false); type = DecodeTimezoneAbbrev(0, lowzone, &val, &tzp); if (type == TZ || type == DTZ) { /* fixed-offset abbreviation */ tz = -val; } else if (type == DYNTZ) { /* dynamic-offset abbreviation, resolve using specified time */ tz = DetermineTimeZoneAbbrevOffset(tm, tzname, tzp); } else { /* try it as a full zone name */ tzp = pg_tzset(tzname); if (tzp) tz = DetermineTimeZoneOffset(tm, tzp); else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("time zone \"%s\" not recognized", tzname))); } } return tz; } #endif /* * timestamp9_in - converts "num" to timestamp9 */ Datum timestamp9_in(PG_FUNCTION_ARGS) { char *str = PG_GETARG_CSTRING(0); timestamp9 result = 0ll; fsec_t fsec; struct pg_tm tt; struct pg_tm* p_tm = &tt; int dtype; int tz; int nf; char *field[MAXDATEFIELDS]; int ftype[MAXDATEFIELDS]; char lowstr[MAXDATELEN + MAXDATEFIELDS]; long long ratio; bool fractional_valid = false; size_t len = strlen(str); int parsed_length; long long ns; #if PG16_GE DateTimeErrorExtra extra; #endif if (len > MAXDATELEN) { ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("input string too long. invalid input format for timestamp9" ))); } /* first try the raw nanosecond bigint format: eg. 1554809728000100000 */ if(sscanf(str, "%lld%n", &ns, &parsed_length) == 1) { if ((size_t)parsed_length == len) { PG_RETURN_TIMESTAMP9(ns); } } ratio = parse_fractional_ratio(str, len, &fractional_valid); /* then try postgres parsing of up-to microsecond fractional second timestamp (to allow greater flexibility in input) */ if (ratio <= 100 || ParseDateTime(str, lowstr, MAXDATELEN + MAXDATEFIELDS, field, ftype, MAXDATEFIELDS, &nf) != 0 || #if PG16_GE DecodeDateTime(field, ftype, nf, &dtype, p_tm, &fsec, &tz, &extra) != 0) #else DecodeDateTime(field, ftype, nf, &dtype, p_tm, &fsec, &tz) != 0) #endif { /* it doesn't work - try our own simple parsing then */ struct tm tm_ = {0}; char gmt_offset_str[TZ_STRLEN_MAX + 1] = ""; int gmt_offset = 0; int num_read; time_t tt; num_read = sscanf(str, "%d-%d-%d %d:%d:%d.%lld %255s", &tm_.tm_year, &tm_.tm_mon, &tm_.tm_mday, &tm_.tm_hour, &tm_.tm_min, &tm_.tm_sec, &ns, gmt_offset_str); if ((num_read == 7 || num_read == 8) && fractional_valid) { int gmt_offset_str_len = strlen(gmt_offset_str); struct pg_tm temp_tm = {0}; temp_tm.tm_year = tm_.tm_year; temp_tm.tm_mon = tm_.tm_mon; temp_tm.tm_mday = tm_.tm_mday; if (gmt_offset_str_len != 0) { /* If we have specified the timezone, try to decode it. */ gmt_offset = parse_sane_timezone(&temp_tm, gmt_offset_str); } else { /* If we haven't specified the timezone, let's use session_timezone to determin the gmt_offset. */ gmt_offset = DetermineTimeZoneOffset(&temp_tm, session_timezone); } tm_.tm_year -= 1900; tm_.tm_mon--; tt = timegm(&tm_); tt = tt + tm_.tm_gmtoff + gmt_offset; result = (long long)tt * kT_ns_in_s + (ns * ratio); } else { ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("invalid input format for timestamp9, required format y-m-d h:m:s.ns [+tz] \"%s\"", str))); } PG_RETURN_TIMESTAMP9(result); } switch (dtype) { case DTK_DATE: { Timestamp pg_ts; if (tm2timestamp(p_tm, fsec, &tz, &pg_ts) != 0) { ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("invalid input format for timestamp9, required format y-m-d h:m:s.ns [+tz] \"%s\"", str))); } pg_ts += ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY * USECS_PER_SEC); result = pg_ts * 1000; break; } default: ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("invalid input format for timestamp9, required format y-m-d h:m:s.ns [+tz] \"%s\"", str))); } PG_RETURN_TIMESTAMP9(result); } long long parse_fractional_ratio(const char* str, size_t len, bool* fractional_valid) { bool count = false; long long ratio = 1000000000ll; size_t i = 0; *fractional_valid = false; while (i <= len) { if (count && (str[i] == ' ' || str[i] == '+' || str[i] == '-' || str[i] == 'Z' || str[i] == '\0')) { *fractional_valid = (ratio > 0); break; } if (count) ratio /= 10; if (str[i] == '.') count = true; i++; } return ratio; } /* * timestamp9_out - converts timestamp9 to "num" */ Datum timestamp9_out(PG_FUNCTION_ARGS) { timestamp9 arg1 = PG_GETARG_TIMESTAMP9(0); char *result = (char *) palloc(41); time_t secs = (time_t)(arg1 / kT_ns_in_s); struct pg_tm *tm_; size_t offset; long long int mod = (arg1 % kT_ns_in_s); if (mod < 0) { mod += kT_ns_in_s; secs -= 1; } tm_ = pg_localtime(&secs, session_timezone); if (!tm_) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp9 out of range"))); offset = pg_strftime(result, 41, "%Y-%m-%d %H:%M:%S", tm_); offset += sprintf(result + offset, ".%09lld", mod); offset += pg_strftime(result + offset, 41, " %z", tm_); PG_RETURN_CSTRING(result); } /* * timestamp9_recv - converts external binary format to timestamp9 */ Datum timestamp9_recv(PG_FUNCTION_ARGS) { StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); PG_RETURN_TIMESTAMP9((timestamp9) pq_getmsgint64(buf)); } /* * timestamp9_send - converts timestamp9 to binary format */ Datum timestamp9_send(PG_FUNCTION_ARGS) { timestamp9 arg1 = PG_GETARG_TIMESTAMP9(0); StringInfoData buf; pq_begintypsend(&buf); pq_sendint64(&buf, arg1); PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); } Datum timestamp9_eq(PG_FUNCTION_ARGS) { PG_RETURN_BOOL(PG_GETARG_TIMESTAMP9(0) == PG_GETARG_TIMESTAMP9(1)); } Datum timestamp9_ne(PG_FUNCTION_ARGS) { PG_RETURN_BOOL(PG_GETARG_TIMESTAMP9(0) != PG_GETARG_TIMESTAMP9(1)); } Datum timestamp9_lt(PG_FUNCTION_ARGS) { PG_RETURN_BOOL(PG_GETARG_TIMESTAMP9(0) < PG_GETARG_TIMESTAMP9(1)); } Datum timestamp9_le(PG_FUNCTION_ARGS) { PG_RETURN_BOOL(PG_GETARG_TIMESTAMP9(0) <= PG_GETARG_TIMESTAMP9(1)); } Datum timestamp9_gt(PG_FUNCTION_ARGS) { PG_RETURN_BOOL(PG_GETARG_TIMESTAMP9(0) > PG_GETARG_TIMESTAMP9(1)); } Datum timestamp9_ge(PG_FUNCTION_ARGS) { PG_RETURN_BOOL(PG_GETARG_TIMESTAMP9(0) >= PG_GETARG_TIMESTAMP9(1)); } Datum bt_timestamp9_cmp(PG_FUNCTION_ARGS) { timestamp9 a = PG_GETARG_TIMESTAMP9(0); timestamp9 b = PG_GETARG_TIMESTAMP9(1); PG_RETURN_INT32((a < b) ? -1 : (a > b)); } Datum timestamp9_to_timestamptz(PG_FUNCTION_ARGS) { timestamp9 ts9 = PG_GETARG_TIMESTAMP9(0); TimestampTz us = timestamp9_to_timestamptz_internal(ts9); PG_RETURN_TIMESTAMPTZ(us); } Datum timestamptz_to_timestamp9(PG_FUNCTION_ARGS) { TimestampTz ts = PG_GETARG_TIMESTAMPTZ(0); timestamp9 ns = timestamptz_to_timestamp9_internal(ts); PG_RETURN_TIMESTAMP9(ns); } Datum timestamp9_to_timestamp(PG_FUNCTION_ARGS) { timestamp9 ts9 = PG_GETARG_TIMESTAMP9(0); TimestampTz us = ts9 / 1000; us -= ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY * USECS_PER_SEC); /* Recheck in case roundoff produces something just out of range */ if (!IS_VALID_TIMESTAMP(us)) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp9 out of range: \"%lld\"", PG_GETARG_TIMESTAMP9(0)))); us = timestamptz2timestamp(us); PG_RETURN_TIMESTAMP(us); } Datum timestamp_to_timestamp9(PG_FUNCTION_ARGS) { timestamp9 ns; TimestampTz ts = PG_GETARG_TIMESTAMP(0); ts = timestamp2timestamptz(ts); ts += ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY * USECS_PER_SEC); ns = ts * 1000; PG_RETURN_TIMESTAMP9(ns); } Datum timestamp9_to_date(PG_FUNCTION_ARGS) { timestamp9 ts9 = PG_GETARG_TIMESTAMP9(0); TimestampTz timestamp = timestamp9_to_timestamptz_internal(ts9); DateADT result; struct pg_tm tt, *tm = &tt; fsec_t fsec; int tz; if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE; PG_RETURN_DATEADT(result); } Datum date_to_timestamp9(PG_FUNCTION_ARGS) { DateADT date = PG_GETARG_DATEADT(0); timestamp9 ts = date2timestamp9(date); PG_RETURN_TIMESTAMP9(ts); } Datum timestamp9_larger(PG_FUNCTION_ARGS) { timestamp9 a = PG_GETARG_TIMESTAMP9(0); timestamp9 b = PG_GETARG_TIMESTAMP9(1); if (a > b) PG_RETURN_TIMESTAMP9(a); else PG_RETURN_TIMESTAMP9(b); } Datum timestamp9_smaller(PG_FUNCTION_ARGS) { timestamp9 a = PG_GETARG_TIMESTAMP9(0); timestamp9 b = PG_GETARG_TIMESTAMP9(1); if (a < b) PG_RETURN_TIMESTAMP9(a); else PG_RETURN_TIMESTAMP9(b); } Datum timestamp9_interval_pl(PG_FUNCTION_ARGS) { timestamp9 ts = PG_GETARG_TIMESTAMP9(0); Interval* intvl = PG_GETARG_INTERVAL_P(1); TimestampTz tstz = timestamp9_to_timestamptz_internal(ts); timestamp9 new_ts = timestamptz_to_timestamp9_internal( DatumGetTimestampTz( DirectFunctionCall2(timestamptz_pl_interval, TimestampTzGetDatum(tstz), IntervalPGetDatum(intvl)))); new_ts += ts % 1000; PG_RETURN_TIMESTAMP9(new_ts); } Datum interval_timestamp9_pl(PG_FUNCTION_ARGS) { Interval* intvl = PG_GETARG_INTERVAL_P(0); timestamp9 ts = PG_GETARG_TIMESTAMP9(1); TimestampTz tstz = timestamp9_to_timestamptz_internal(ts); timestamp9 new_ts = timestamptz_to_timestamp9_internal( DatumGetTimestampTz( DirectFunctionCall2(timestamptz_pl_interval, TimestampTzGetDatum(tstz), IntervalPGetDatum(intvl)))); new_ts += ts % 1000; PG_RETURN_TIMESTAMP9(new_ts); } Datum timestamp9_interval_mi(PG_FUNCTION_ARGS) { timestamp9 ts = PG_GETARG_TIMESTAMP9(0); Interval* intvl = PG_GETARG_INTERVAL_P(1); Interval tspan; TimestampTz tstz; timestamp9 new_ts; tspan.month = -intvl->month; tspan.day = -intvl->day; tspan.time = -intvl->time; tstz = timestamp9_to_timestamptz_internal(ts); new_ts = timestamptz_to_timestamp9_internal(DatumGetTimestampTz(DirectFunctionCall2(timestamptz_pl_interval, TimestampTzGetDatum(tstz), IntervalPGetDatum(&tspan)))); new_ts += ts % 1000; PG_RETURN_TIMESTAMP9(new_ts); } timestamp9-timestamp9-1.4.0/src/timestamp9.h000066400000000000000000000032031443766336600207560ustar00rootroot00000000000000 #pragma once #include #include "fmgr.h" #include "postgres.h" #define PG16_GE (PG_VERSION_NUM >= 160000) typedef long long timestamp9; #define DatumGetTimestamp9(X) ((timestamp9) (X)) #define Timestamp9GetDatum(X) ((Datum) (X)) #define PG_GETARG_TIMESTAMP9(n) DatumGetTimestamp9(PG_GETARG_DATUM(n)) #define PG_RETURN_TIMESTAMP9(x) return Timestamp9GetDatum(x) #define TIMESTAMP9_END_JULIAN (2547238) /* == date2j(2262, 1, 1) */ extern Datum timestamp9_in(PG_FUNCTION_ARGS); extern Datum timestamp9_out(PG_FUNCTION_ARGS); extern Datum timestamp9_recv(PG_FUNCTION_ARGS); extern Datum timestamp9_send(PG_FUNCTION_ARGS); extern Datum timestamp9_eq(PG_FUNCTION_ARGS); extern Datum timestamp9_ne(PG_FUNCTION_ARGS); extern Datum timestamp9_lt(PG_FUNCTION_ARGS); extern Datum timestamp9_le(PG_FUNCTION_ARGS); extern Datum timestamp9_gt(PG_FUNCTION_ARGS); extern Datum timestamp9_ge(PG_FUNCTION_ARGS); extern Datum bt_timestamp9_cmp(PG_FUNCTION_ARGS); extern Datum timestamp9_to_timestamptz(PG_FUNCTION_ARGS); extern Datum timestamptz_to_timestamp9(PG_FUNCTION_ARGS); extern Datum timestamp9_to_timestamp(PG_FUNCTION_ARGS); extern Datum timestamp_to_timestamp9(PG_FUNCTION_ARGS); extern Datum timestamp9_to_date(PG_FUNCTION_ARGS); extern Datum date_to_timestamp9(PG_FUNCTION_ARGS); extern Datum timestamp9_larger(PG_FUNCTION_ARGS); extern Datum timestamp9_smaller(PG_FUNCTION_ARGS); extern Datum timestamp9_interval_pl(PG_FUNCTION_ARGS); extern Datum interval_timestamp9_pl(PG_FUNCTION_ARGS); extern Datum timestamp9_interval_mi(PG_FUNCTION_ARGS); int parse_gmt_offset(const char*, bool*); long long parse_fractional_ratio(const char*, size_t, bool*); timestamp9-timestamp9-1.4.0/src/timestamp9.sql000066400000000000000000000145601443766336600213360ustar00rootroot00000000000000CREATE OR REPLACE FUNCTION timestamp9_in(cstring) RETURNS timestamp9 AS '$libdir/timestamp9' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE OR REPLACE FUNCTION timestamp9_out(timestamp9) RETURNS cstring AS '$libdir/timestamp9' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE OR REPLACE FUNCTION timestamp9_recv(internal) RETURNS timestamp9 AS '$libdir/timestamp9' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE OR REPLACE FUNCTION timestamp9_send(timestamp9) RETURNS bytea AS '$libdir/timestamp9' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE TYPE timestamp9 ( INPUT = timestamp9_in, OUTPUT = timestamp9_out, RECEIVE = timestamp9_recv, SEND = timestamp9_send, INTERNALLENGTH = 8, PASSEDBYVALUE, ALIGNMENT = double, STORAGE = plain ); CREATE OR REPLACE FUNCTION timestamp9_to_timestamptz(timestamp9) RETURNS timestamptz AS '$libdir/timestamp9' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE CAST (timestamp9 AS timestamptz) WITH FUNCTION timestamp9_to_timestamptz(timestamp9) AS ASSIGNMENT; CREATE OR REPLACE FUNCTION timestamptz_to_timestamp9(timestamptz) RETURNS timestamp9 AS '$libdir/timestamp9' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE CAST (timestamptz AS timestamp9) WITH FUNCTION timestamptz_to_timestamp9(timestamptz) AS IMPLICIT; CREATE OR REPLACE FUNCTION timestamp9_to_timestamp(timestamp9) RETURNS timestamp AS '$libdir/timestamp9' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE CAST (timestamp9 AS timestamp) WITH FUNCTION timestamp9_to_timestamp(timestamp9) AS ASSIGNMENT; CREATE OR REPLACE FUNCTION timestamp_to_timestamp9(timestamp) RETURNS timestamp9 AS '$libdir/timestamp9' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE CAST (timestamp AS timestamp9) WITH FUNCTION timestamp_to_timestamp9(timestamp) AS IMPLICIT; CREATE OR REPLACE FUNCTION timestamp9_to_date(timestamp9) RETURNS date AS '$libdir/timestamp9' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE CAST (timestamp9 AS date) WITH FUNCTION timestamp9_to_date(timestamp9) AS ASSIGNMENT; CREATE OR REPLACE FUNCTION date_to_timestamp9(date) RETURNS timestamp9 AS '$libdir/timestamp9' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE CAST (date AS timestamp9) WITH FUNCTION date_to_timestamp9(date) AS IMPLICIT; CREATE CAST (timestamp9 AS bigint) WITHOUT FUNCTION AS ASSIGNMENT; CREATE CAST (bigint AS timestamp9) WITHOUT FUNCTION AS IMPLICIT; CREATE OR REPLACE FUNCTION timestamp9_lt(timestamp9, timestamp9) RETURNS bool AS 'MODULE_PATHNAME' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE OPERATOR < ( LEFTARG = timestamp9, RIGHTARG = timestamp9, PROCEDURE = timestamp9_lt, COMMUTATOR = >, NEGATOR = >=, RESTRICT = scalarltsel, JOIN = scalarltjoinsel ); CREATE OR REPLACE FUNCTION timestamp9_le(timestamp9, timestamp9) RETURNS bool AS 'MODULE_PATHNAME' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE OPERATOR <= ( LEFTARG = timestamp9, RIGHTARG = timestamp9, PROCEDURE = timestamp9_le, COMMUTATOR = >=, NEGATOR = >, RESTRICT = scalarltsel, JOIN = scalarltjoinsel ); CREATE OR REPLACE FUNCTION timestamp9_gt(timestamp9, timestamp9) RETURNS bool AS 'MODULE_PATHNAME' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE OPERATOR > ( LEFTARG = timestamp9, RIGHTARG = timestamp9, PROCEDURE = timestamp9_gt, COMMUTATOR = <, NEGATOR = <=, RESTRICT = scalargtsel, JOIN = scalargtjoinsel ); CREATE OR REPLACE FUNCTION timestamp9_ge(timestamp9, timestamp9) RETURNS bool AS 'MODULE_PATHNAME' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE OPERATOR >= ( LEFTARG = timestamp9, RIGHTARG = timestamp9, PROCEDURE = timestamp9_ge, COMMUTATOR = <=, NEGATOR = <, RESTRICT = scalargtsel, JOIN = scalargtjoinsel ); CREATE OR REPLACE FUNCTION timestamp9_eq(timestamp9, timestamp9) RETURNS bool AS 'MODULE_PATHNAME' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE OPERATOR = ( LEFTARG = timestamp9, RIGHTARG = timestamp9, PROCEDURE = timestamp9_eq, COMMUTATOR = =, NEGATOR = <>, RESTRICT = eqsel, JOIN = eqjoinsel, HASHES, MERGES ); CREATE OR REPLACE FUNCTION timestamp9_ne(timestamp9, timestamp9) RETURNS bool AS 'MODULE_PATHNAME' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE OPERATOR <> ( LEFTARG = timestamp9, RIGHTARG = timestamp9, PROCEDURE = timestamp9_ne, COMMUTATOR = <>, NEGATOR = =, RESTRICT = neqsel, JOIN = neqjoinsel, MERGES ); CREATE OR REPLACE FUNCTION bt_timestamp9_cmp(timestamp9, timestamp9) RETURNS int AS 'MODULE_PATHNAME' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE OPERATOR CLASS btree_timestamp9_ops DEFAULT FOR TYPE timestamp9 USING btree FAMILY integer_ops AS OPERATOR 1 <, OPERATOR 2 <=, OPERATOR 3 =, OPERATOR 4 >=, OPERATOR 5 >, FUNCTION 1 bt_timestamp9_cmp(timestamp9, timestamp9); CREATE OR REPLACE FUNCTION hash_timestamp9(timestamp9) RETURNS integer AS 'hashint8' LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE OPERATOR CLASS hash_timestamp9_ops DEFAULT FOR TYPE timestamp9 USING hash FAMILY integer_ops AS OPERATOR 1 =, FUNCTION 1 hash_timestamp9(timestamp9); CREATE FUNCTION timestamp9_larger(timestamp9, timestamp9) RETURNS timestamp9 AS '$libdir/timestamp9' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE AGGREGATE max(timestamp9) ( SFUNC = timestamp9_larger, STYPE = timestamp9, SORTOP = > ); CREATE FUNCTION timestamp9_smaller(timestamp9, timestamp9) RETURNS timestamp9 AS '$libdir/timestamp9' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE AGGREGATE min(timestamp9) ( SFUNC = timestamp9_smaller, STYPE = timestamp9, SORTOP = < ); CREATE FUNCTION timestamp9_interval_pl(timestamp9, interval) RETURNS timestamp9 AS '$libdir/timestamp9' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE OPERATOR + ( LEFTARG = timestamp9, RIGHTARG = interval, PROCEDURE = timestamp9_interval_pl, COMMUTATOR = + ); CREATE FUNCTION timestamp9_interval_mi(timestamp9, interval) RETURNS timestamp9 AS '$libdir/timestamp9' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE OPERATOR - ( LEFTARG = timestamp9, RIGHTARG = interval, PROCEDURE = timestamp9_interval_mi ); CREATE FUNCTION interval_timestamp9_pl(interval, timestamp9) RETURNS timestamp9 AS '$libdir/timestamp9' LANGUAGE c IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF; CREATE OPERATOR + ( LEFTARG = interval, RIGHTARG = timestamp9, PROCEDURE = interval_timestamp9_pl, COMMUTATOR = + ); timestamp9-timestamp9-1.4.0/tests/000077500000000000000000000000001443766336600170665ustar00rootroot00000000000000timestamp9-timestamp9-1.4.0/tests/CMakeLists.txt000066400000000000000000000027271443766336600216360ustar00rootroot00000000000000set(PRIMARY_TEST_DIR ${CMAKE_CURRENT_LIST_DIR}) set(PRIMARY_TEST_DIR ${CMAKE_CURRENT_LIST_DIR} PARENT_SCOPE) set(_local_install_checks) set(_install_checks) # Testing support find_program(PG_REGRESS pg_regress HINTS "${PG_BINDIR}" "${PG_PKGLIBDIR}/pgxs/src/test/regress/") find_program(PG_ISOLATION_REGRESS NAMES pg_isolation_regress HINTS ${PG_BINDIR} ${PG_PKGLIBDIR}/pgxs/src/test/isolation ${PG_SOURCE_DIR}/src/test/isolation ${BINDIR}) include(test-defs.cmake) if(PG_REGRESS) message(STATUS "Using pg_regress ${PG_REGRESS}") add_custom_target(regresscheck COMMAND ${CMAKE_COMMAND} -E env ${PG_REGRESS_ENV} ${CMAKE_CURRENT_SOURCE_DIR}/pg_regress.sh ${PG_REGRESS_OPTS_BASE} ${PG_REGRESS_OPTS_EXTRA} ${PG_REGRESS_OPTS_INOUT} ${PG_REGRESS_OPTS_TEMP_INSTANCE} USES_TERMINAL) add_custom_target(regresschecklocal COMMAND ${CMAKE_COMMAND} -E env ${PG_REGRESS_ENV} ${CMAKE_CURRENT_SOURCE_DIR}/pg_regress.sh ${PG_REGRESS_OPTS_BASE} ${PG_REGRESS_OPTS_EXTRA} ${PG_REGRESS_OPTS_INOUT} ${PG_REGRESS_OPTS_LOCAL_INSTANCE} USES_TERMINAL) list(APPEND _local_install_checks regresschecklocal) list(APPEND _install_checks regresscheck) elseif(REGRESS_CHECKS) message(FATAL_ERROR "Program 'pg_regress' not found, but regressions checks were required.\n" "Skip regression checks using -DREGRESS_CHECKS=OFF") else() message(STATUS "Skipping regress checks since program 'pg_regress' was not found") endif() add_subdirectory(sql) add_subdirectory(expected) timestamp9-timestamp9-1.4.0/tests/README.md000066400000000000000000000004501443766336600203440ustar00rootroot00000000000000In order to run the tests, build everything and make sure PG_REGRESS is found. Then run: ``` make regresscheck ``` To add new tests, either add them to an existing `.sql` file, or add a new `.sql` file. Make sure to update the `.out` file in the `expected` directory with the expected output. timestamp9-timestamp9-1.4.0/tests/expected/000077500000000000000000000000001443766336600206675ustar00rootroot00000000000000timestamp9-timestamp9-1.4.0/tests/expected/CMakeLists.txt000066400000000000000000000000001443766336600234150ustar00rootroot00000000000000timestamp9-timestamp9-1.4.0/tests/expected/basics.out000066400000000000000000000171201443766336600226650ustar00rootroot00000000000000-- Set timezone to UTC-2 so that timestamp9 can have consistent timezone configuration -- on machines in different timezones. set timezone to 'UTC-2'; -- Test that we are able to convert nanoseconds to timestamp9. select 0::bigint::timestamp9; timestamp9 ------------------------------------- 1970-01-01 02:00:00.000000000 +0200 (1 row) select 9223372036854775807::timestamp9; timestamp9 ------------------------------------- 2262-04-12 01:47:16.854775807 +0200 (1 row) -- Test that we are able to convert various formats of timestamps to timestamp9 type. select '2019-09-19 08:30:05.123456789 +0200'::timestamp9; timestamp9 ------------------------------------- 2019-09-19 08:30:05.123456789 +0200 (1 row) select '2019-09-19 08:30:05.123456789-0200'::timestamp9; timestamp9 ------------------------------------- 2019-09-19 12:30:05.123456789 +0200 (1 row) select '2019-09-19 08:30:05.123456789+02:00'::timestamp9; timestamp9 ------------------------------------- 2019-09-19 08:30:05.123456789 +0200 (1 row) select '2019-09-19 08:30:05.123456789 -02:00'::timestamp9; timestamp9 ------------------------------------- 2019-09-19 12:30:05.123456789 +0200 (1 row) select '2019-09-19 08:30:05'::timestamp9; timestamp9 ------------------------------------- 2019-09-19 08:30:05.000000000 +0200 (1 row) -- Test that we are able to control the timezone of timestamp9 via the 'timezone' GUC. set timezone to 'UTC-8'; select '2019-09-19 08:30:05 +0800'::timestamp9; timestamp9 ------------------------------------- 2019-09-19 08:30:05.000000000 +0800 (1 row) set timezone to 'Europe/London'; select '2019-09-19 08:30:05 +0100'::timestamp9; timestamp9 ------------------------------------- 2019-09-19 08:30:05.000000000 +0100 (1 row) -- NOTE: If we don't specify the timezone when parsing the time, it follows the timezone -- of the current session by default. select '2019-09-19 08:30:05'::timestamp9; timestamp9 ------------------------------------- 2019-09-19 08:30:05.000000000 +0100 (1 row) select '2019-09-19 08:30:05.123456789'::timestamp9; timestamp9 ------------------------------------- 2019-09-19 08:30:05.123456789 +0100 (1 row) -- Test that we can use various timezones. select '2019-09-19 08:30:05.123456789 Europe/London'::timestamp9; timestamp9 ------------------------------------- 2019-09-19 08:30:05.123456789 +0100 (1 row) select '2019-09-19 08:30:05.123456789 utc-2'::timestamp9; timestamp9 ------------------------------------- 2019-09-19 07:30:05.123456789 +0100 (1 row) set timezone to 'UTC-2'; -- Test that we are able to reject bad inputs. select '2022-01-25 00:00:00.123456789 +'::timestamp9; ERROR: invalid input syntax for type numeric timezone: "+" at character 8 select '2022-01-25 00:00:00.123456789 abcd'::timestamp9; ERROR: time zone "abcd" not recognized at character 8 -- Test that we are able to compare timestamp9 values. select '2019-09-19'::timestamp9 < '2019-09-20'::timestamp9, greatest('2020-06-06'::timestamp9, '2019-01-01'::timestamp9); ?column? | greatest ----------+------------------------------------- t | 2020-06-06 00:00:00.000000000 +0200 (1 row) select '2019-09-19'::timestamp9 > '2019-09-20'::timestamp9, least('2020-06-06'::timestamp9, '2019-01-01'::timestamp9); ?column? | least ----------+------------------------------------- f | 2019-01-01 00:00:00.000000000 +0200 (1 row) select '2019-09-19'::timestamp9 >= '2019-09-20'::timestamp9; ?column? ---------- f (1 row) select '2019-09-20'::timestamp9 >= '2019-09-20'::timestamp9; ?column? ---------- t (1 row) select '2019-09-19'::timestamp9 <= '2019-09-20'::timestamp9; ?column? ---------- t (1 row) select '2019-09-20'::timestamp9 <= '2019-09-20'::timestamp9; ?column? ---------- t (1 row) select '2019-09-19'::timestamp9 = '2019-09-20'::timestamp9; ?column? ---------- f (1 row) select '2019-09-20'::timestamp9 = '2019-09-20'::timestamp9; ?column? ---------- t (1 row) -- Test that we can do arith calculations with timestamp9. select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 + interval '1 year'; ?column? ------------------------------------- 2020-09-19 23:00:00.123456789 +0200 (1 row) select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 - interval '2 years'; ?column? ------------------------------------- 2017-09-19 23:00:00.123456789 +0200 (1 row) select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 + interval '1 mon'; ?column? ------------------------------------- 2019-10-19 23:00:00.123456789 +0200 (1 row) select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 - interval '2 mons'; ?column? ------------------------------------- 2019-07-19 23:00:00.123456789 +0200 (1 row) select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 + interval '1 day'; ?column? ------------------------------------- 2019-09-20 23:00:00.123456789 +0200 (1 row) select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 - interval '2 days'; ?column? ------------------------------------- 2019-09-17 23:00:00.123456789 +0200 (1 row) select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 + interval '1 hour'; ?column? ------------------------------------- 2019-09-20 00:00:00.123456789 +0200 (1 row) select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 - interval '2 hours'; ?column? ------------------------------------- 2019-09-19 21:00:00.123456789 +0200 (1 row) select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 + interval '1 min'; ?column? ------------------------------------- 2019-09-19 23:01:00.123456789 +0200 (1 row) select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 - interval '2 mins'; ?column? ------------------------------------- 2019-09-19 22:58:00.123456789 +0200 (1 row) select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 + interval '1 sec'; ?column? ------------------------------------- 2019-09-19 23:00:01.123456789 +0200 (1 row) select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 - interval '2 secs'; ?column? ------------------------------------- 2019-09-19 22:59:58.123456789 +0200 (1 row) select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 + interval '1 millisecond'; ?column? ------------------------------------- 2019-09-19 23:00:00.124456789 +0200 (1 row) select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 - interval '2 milliseconds'; ?column? ------------------------------------- 2019-09-19 23:00:00.121456789 +0200 (1 row) select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 + interval '1 microsecond'; ?column? ------------------------------------- 2019-09-19 23:00:00.123457789 +0200 (1 row) select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 - interval '2 microseconds'; ?column? ------------------------------------- 2019-09-19 23:00:00.123454789 +0200 (1 row) CREATE TABLE tbl(ts timestamp9); CREATE INDEX ON tbl USING hash (ts); CREATE TABLE tbl1(ts timestamp9) PARTITION BY HASH (ts); timestamp9-timestamp9-1.4.0/tests/pg_regress.sh000077500000000000000000000070141443766336600215670ustar00rootroot00000000000000#!/usr/bin/env bash # Wrapper around pg_regress to be able to override the tests to run via the # TESTS environment variable # NB this script mirrors the adjacent pg_isolation_regress.sh, and they should # be kept in sync CURRENT_DIR=$(dirname $0) EXE_DIR=${EXE_DIR:-${CURRENT_DIR}} PG_REGRESS=${PG_REGRESS:-pg_regress} PG_REGRESS_DIFF_OPTS=-u TEST_SCHEDULE=${TEST_SCHEDULE:-} TEMP_SCHEDULE=${CURRENT_DIR}/temp_schedule TESTS=${TESTS:-} IGNORES=${IGNORES:-} SKIPS=${SKIPS:-} contains() { # a list contains a value foo if the regex ".* foo .*" holds true [[ $1 =~ (.*[[:space:]]|^)$2([[:space:]].*|$) ]]; return $? } echo "TESTS ${TESTS}" echo "IGNORES ${IGNORES}" echo "SKIPS ${SKIPS}" if [[ -z ${TESTS} ]]; then if [[ -z ${TEST_SCHEDULE} ]]; then for t in ${EXE_DIR}/sql/*.sql; do t=${t##${EXE_DIR}/sql/} t=${t%.sql} if ! contains "${SKIPS}" "${t}"; then TESTS="${TESTS} ${t}" fi done elif [[ -n ${IGNORES} ]] || [[ -n ${SKIPS} ]]; then # get the tests from the test schedule, but ignore our IGNORES while read t; do if [[ t =~ ignore:* ]]; then t=${t##ignore:* } IGNORES="${t} ${IGNORES}" continue fi t=${t##test: } ## check each individual test in test group to see if it should be ignored for el in ${t[@]}; do if ! contains "${SKIPS}" "${el}"; then TESTS="${TESTS} ${el}" fi done done < ${TEST_SCHEDULE} # no longer needed as the contents has been parsed above # and unsetting it helps take a shortcut later TEST_SCHEDULE= else PG_REGRESS_OPTS="${PG_REGRESS_OPTS} --schedule=${TEST_SCHEDULE}" fi else # Both this and pg_isolation_regress.sh use the same TESTS env var to decide which tests to run. # Since we only want to pass the test runner the kind of tests it can understand, # and only those which actually exist, we use TESTS as a filter for the test folder, # passing in only those tests from the directory which are found in TESTS FILTER=${TESTS} TESTS= for t in ${EXE_DIR}/sql/*.sql; do t=${t##${EXE_DIR}/sql/} t=${t%.sql} if contains "${FILTER}" "${t}" && ! contains "${SKIPS}" "${t}"; then TESTS="${TESTS} $t" fi done # When TESTS is specified the passed tests scheduler is not used and emptying it helps take a shortcut later TEST_SCHEDULE= fi if [[ -z ${TESTS} ]] && [[ -z ${TEST_SCHEDULE} ]]; then exit 0; fi function cleanup() { rm -rf ${EXE_DIR}/sql/dump rm -rf ${TEST_TABLESPACE1_PATH} rm -rf ${TEST_TABLESPACE2_PATH} rm -f ${CURRENT_DIR}/.pg_init rm -f ${TEMP_SCHEDULE} } trap cleanup EXIT # This mktemp line will work on both OSX and GNU systems TEST_TABLESPACE1_PATH=${TEST_TABLESPACE1_PATH:-$(mktemp -d 2>/dev/null || mktemp -d -t 'timescaledb_regress')} TEST_TABLESPACE2_PATH=${TEST_TABLESPACE2_PATH:-$(mktemp -d 2>/dev/null || mktemp -d -t 'timescaledb_regress')} export TEST_TABLESPACE1_PATH TEST_TABLESPACE2_PATH rm -f ${TEST_OUTPUT_DIR}/.pg_init mkdir -p ${EXE_DIR}/sql/dump export PG_REGRESS_DIFF_OPTS touch ${TEMP_SCHEDULE} rm ${TEMP_SCHEDULE} touch ${TEMP_SCHEDULE} for t in ${IGNORES}; do echo "ignore: ${t}" >> ${TEMP_SCHEDULE} done for t in ${TESTS}; do echo "test: ${t}" >> ${TEMP_SCHEDULE} done PG_REGRESS_OPTS="${PG_REGRESS_OPTS} --schedule=${TEMP_SCHEDULE}" ${PG_REGRESS} $@ ${PG_REGRESS_OPTS} timestamp9-timestamp9-1.4.0/tests/postgresql.conf000066400000000000000000000001041443766336600221330ustar00rootroot00000000000000autovacuum=false random_page_cost=1.0 log_line_prefix='%u [%p] %d ' timestamp9-timestamp9-1.4.0/tests/runner.sh000077500000000000000000000054441443766336600207450ustar00rootroot00000000000000#!/usr/bin/env bash set -u set -e CURRENT_DIR=$(dirname $0) EXE_DIR=${EXE_DIR:-${CURRENT_DIR}} PG_REGRESS_PSQL=$1 PSQL=${PSQL:-$PG_REGRESS_PSQL} PSQL="${PSQL} -X" # Prevent any .psqlrc files from being executed during the tests TEST_PGUSER=${TEST_PGUSER:-postgres} TEST_INPUT_DIR=${TEST_INPUT_DIR:-${EXE_DIR}} TEST_OUTPUT_DIR=${TEST_OUTPUT_DIR:-${EXE_DIR}} # PGAPPNAME will be 'pg_regress/test' so we cut off the prefix # to get the name of the test (PG 10 and 11 only) if [[ ${PGAPPNAME} = pg_regress/* ]]; then CURRENT_TEST=${PGAPPNAME##pg_regress/} else # PG 9.6 pg_regress does not pass in testname # so we generate unique name from pid CURRENT_TEST="test_$$" fi TEST_DBNAME="db_${CURRENT_TEST}" # Read the extension version from version.config read -r VERSION < ${CURRENT_DIR}/../version.config EXT_VERSION=${VERSION##version = } #docker doesn't set user USER=${USER:-`whoami`} TEST_SPINWAIT_ITERS=${TEST_SPINWAIT_ITERS:-100} TEST_ROLE_SUPERUSER=${TEST_ROLE_SUPERUSER:-super_user} TEST_ROLE_DEFAULT_PERM_USER=${TEST_ROLE_DEFAULT_PERM_USER:-default_perm_user} TEST_ROLE_DEFAULT_PERM_USER_2=${TEST_ROLE_DEFAULT_PERM_USER_2:-default_perm_user_2} shift function cleanup { ${PSQL} $@ -U $TEST_ROLE_SUPERUSER -d postgres -v ECHO=none -c "DROP DATABASE \"${TEST_DBNAME}\";" >/dev/null } trap cleanup EXIT # setup clusterwide settings on first run if [[ ! -f ${TEST_OUTPUT_DIR}/.pg_init ]]; then touch ${TEST_OUTPUT_DIR}/.pg_init ${PSQL} $@ -U ${USER} -d postgres -v ECHO=none -c "ALTER USER ${TEST_ROLE_SUPERUSER} WITH SUPERUSER;" >/dev/null fi cd ${EXE_DIR}/sql # create database and install timestamp9 ${PSQL} $@ -U $TEST_ROLE_SUPERUSER -d postgres -v ECHO=none -c "CREATE DATABASE \"${TEST_DBNAME}\";" ${PSQL} $@ -U $TEST_ROLE_SUPERUSER -d ${TEST_DBNAME} -v ECHO=none -c "SET client_min_messages=error; CREATE EXTENSION timestamp9; \ GRANT CREATE ON SCHEMA public TO PUBLIC;" export TEST_DBNAME # we strip out any output between # and the part about memory usage in EXPLAIN ANALYZE output of Sort nodes ${PSQL} -U ${TEST_PGUSER} \ -v ON_ERROR_STOP=0 \ -v VERBOSITY=terse \ -v ECHO=all \ -v DISABLE_OPTIMIZATIONS=off \ -v TEST_DBNAME="${TEST_DBNAME}" \ -v TEST_TABLESPACE1_PATH=\'${TEST_TABLESPACE1_PATH}\' \ -v TEST_TABLESPACE2_PATH=\'${TEST_TABLESPACE2_PATH}\' \ -v TEST_INPUT_DIR=${TEST_INPUT_DIR} \ -v TEST_OUTPUT_DIR=${TEST_OUTPUT_DIR} \ -v TEST_SPINWAIT_ITERS=${TEST_SPINWAIT_ITERS} \ -v ROLE_SUPERUSER=${TEST_ROLE_SUPERUSER} \ -v ROLE_DEFAULT_PERM_USER=${TEST_ROLE_DEFAULT_PERM_USER} \ -v ROLE_DEFAULT_PERM_USER_2=${TEST_ROLE_DEFAULT_PERM_USER_2} \ $@ -d ${TEST_DBNAME} 2>&1 | sed -e '//,/<\/exclude_from_test>/d' -e 's! Memory: [0-9]\{1,\}kB!!' -e 's! Memory Usage: [0-9]\{1,\}kB!!' timestamp9-timestamp9-1.4.0/tests/runner_shared.sh000077500000000000000000000040531443766336600222660ustar00rootroot00000000000000#!/usr/bin/env bash set -u set -e CURRENT_DIR=$(dirname $0) EXE_DIR=${EXE_DIR:-${CURRENT_DIR}} PG_REGRESS_PSQL=$1 PSQL=${PSQL:-$PG_REGRESS_PSQL} PSQL="${PSQL} -X" # Prevent any .psqlrc files from being executed during the tests TEST_PGUSER=${TEST_PGUSER:-postgres} TEST_INPUT_DIR=${TEST_INPUT_DIR:-${EXE_DIR}} TEST_OUTPUT_DIR=${TEST_OUTPUT_DIR:-${EXE_DIR}} # PGAPPNAME will be 'pg_regress/test' so we cut off the prefix # to get the name of the test (PG 10 and 11 only) TEST_BASE_NAME=${PGAPPNAME##pg_regress/} # if this is a versioned test our name will have version as suffix # so we cut off suffix to get base name if [[ ${TEST_BASE_NAME} == *-1[0-9] ]]; then TEST_BASE_NAME=${TEST_BASE_NAME%???} fi #docker doesn't set user USER=${USER:-`whoami`} TEST_ROLE_SUPERUSER=${TEST_ROLE_SUPERUSER:-super_user} TEST_ROLE_DEFAULT_PERM_USER=${TEST_ROLE_DEFAULT_PERM_USER:-default_perm_user} TEST_ROLE_DEFAULT_PERM_USER_2=${TEST_ROLE_DEFAULT_PERM_USER_2:-default_perm_user_2} shift # setup clusterwide settings on first run if [[ ! -f ${TEST_OUTPUT_DIR}/.pg_init ]]; then touch ${TEST_OUTPUT_DIR}/.pg_init ${PSQL} $@ -U ${USER} -d postgres -v ECHO=none -c "ALTER USER ${TEST_ROLE_SUPERUSER} WITH SUPERUSER;" >/dev/null ${PSQL} $@ -U $TEST_PGUSER -d ${TEST_DBNAME} -v ECHO=none < ${TEST_INPUT_DIR}/shared/sql/include/shared_setup.sql >/dev/null fi cd ${EXE_DIR}/sql # we strip out any output between # and the part about memory usage in EXPLAIN ANALYZE output of Sort nodes ${PSQL} -U ${TEST_PGUSER} \ -v ON_ERROR_STOP=0 \ -v VERBOSITY=terse \ -v ECHO=all \ -v TEST_BASE_NAME=${TEST_BASE_NAME} \ -v TEST_INPUT_DIR=${TEST_INPUT_DIR} \ -v TEST_OUTPUT_DIR=${TEST_OUTPUT_DIR} \ -v ROLE_SUPERUSER=${TEST_ROLE_SUPERUSER} \ -v ROLE_DEFAULT_PERM_USER=${TEST_ROLE_DEFAULT_PERM_USER} \ -v ROLE_DEFAULT_PERM_USER_2=${TEST_ROLE_DEFAULT_PERM_USER_2} \ $@ -d ${TEST_DBNAME} 2>&1 | sed -e '//,/<\/exclude_from_test>/d' -e 's! Memory: [0-9]\{1,\}kB!!' -e 's! Memory Usage: [0-9]\{1,\}kB!!' timestamp9-timestamp9-1.4.0/tests/sql/000077500000000000000000000000001443766336600176655ustar00rootroot00000000000000timestamp9-timestamp9-1.4.0/tests/sql/CMakeLists.txt000066400000000000000000000011111443766336600224170ustar00rootroot00000000000000set(TEST_FILES basics.sql ) if(NOT TEST_GROUP_SIZE) set(PARALLEL_GROUP_SIZE 20) else() set(PARALLEL_GROUP_SIZE ${TEST_GROUP_SIZE}) endif() set(GROUP_MEMBERS 0) list(SORT TEST_FILES) file(REMOVE ${TEST_SCHEDULE}) foreach(TEST_FILE ${TEST_FILES}) string(REGEX REPLACE "(.+)\.sql" "\\1" TESTS_TO_RUN ${TEST_FILE}) if(GROUP_MEMBERS EQUAL 0) file(APPEND ${TEST_SCHEDULE} "\ntest: ") endif() file(APPEND ${TEST_SCHEDULE} "${TESTS_TO_RUN} ") MATH(EXPR GROUP_MEMBERS "(${GROUP_MEMBERS}+1)%${PARALLEL_GROUP_SIZE}") endforeach(TEST_FILE) file(APPEND ${TEST_SCHEDULE} "\n") timestamp9-timestamp9-1.4.0/tests/sql/basics.sql000066400000000000000000000070111443766336600216510ustar00rootroot00000000000000-- Set timezone to UTC-2 so that timestamp9 can have consistent timezone configuration -- on machines in different timezones. set timezone to 'UTC-2'; -- Test that we are able to convert nanoseconds to timestamp9. select 0::bigint::timestamp9; select 9223372036854775807::timestamp9; -- Test that we are able to convert various formats of timestamps to timestamp9 type. select '2019-09-19 08:30:05.123456789 +0200'::timestamp9; select '2019-09-19 08:30:05.123456789-0200'::timestamp9; select '2019-09-19 08:30:05.123456789+02:00'::timestamp9; select '2019-09-19 08:30:05.123456789 -02:00'::timestamp9; select '2019-09-19 08:30:05'::timestamp9; -- Test that we are able to control the timezone of timestamp9 via the 'timezone' GUC. set timezone to 'UTC-8'; select '2019-09-19 08:30:05 +0800'::timestamp9; set timezone to 'Europe/London'; select '2019-09-19 08:30:05 +0100'::timestamp9; -- NOTE: If we don't specify the timezone when parsing the time, it follows the timezone -- of the current session by default. select '2019-09-19 08:30:05'::timestamp9; select '2019-09-19 08:30:05.123456789'::timestamp9; -- Test that we can use various timezones. select '2019-09-19 08:30:05.123456789 Europe/London'::timestamp9; select '2019-09-19 08:30:05.123456789 utc-2'::timestamp9; set timezone to 'UTC-2'; -- Test that we are able to reject bad inputs. select '2022-01-25 00:00:00.123456789 +'::timestamp9; select '2022-01-25 00:00:00.123456789 abcd'::timestamp9; -- Test that we are able to compare timestamp9 values. select '2019-09-19'::timestamp9 < '2019-09-20'::timestamp9, greatest('2020-06-06'::timestamp9, '2019-01-01'::timestamp9); select '2019-09-19'::timestamp9 > '2019-09-20'::timestamp9, least('2020-06-06'::timestamp9, '2019-01-01'::timestamp9); select '2019-09-19'::timestamp9 >= '2019-09-20'::timestamp9; select '2019-09-20'::timestamp9 >= '2019-09-20'::timestamp9; select '2019-09-19'::timestamp9 <= '2019-09-20'::timestamp9; select '2019-09-20'::timestamp9 <= '2019-09-20'::timestamp9; select '2019-09-19'::timestamp9 = '2019-09-20'::timestamp9; select '2019-09-20'::timestamp9 = '2019-09-20'::timestamp9; -- Test that we can do arith calculations with timestamp9. select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 + interval '1 year'; select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 - interval '2 years'; select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 + interval '1 mon'; select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 - interval '2 mons'; select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 + interval '1 day'; select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 - interval '2 days'; select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 + interval '1 hour'; select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 - interval '2 hours'; select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 + interval '1 min'; select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 - interval '2 mins'; select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 + interval '1 sec'; select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 - interval '2 secs'; select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 + interval '1 millisecond'; select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 - interval '2 milliseconds'; select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 + interval '1 microsecond'; select '2019-09-19 23:00:00.123456789 +0200'::timestamp9 - interval '2 microseconds'; CREATE TABLE tbl(ts timestamp9); CREATE INDEX ON tbl USING hash (ts); CREATE TABLE tbl1(ts timestamp9) PARTITION BY HASH (ts); timestamp9-timestamp9-1.4.0/tests/test-defs.cmake000066400000000000000000000065741443766336600220020ustar00rootroot00000000000000set(TEST_ROLE_SUPERUSER super_user) set(TEST_ROLE_DEFAULT_PERM_USER default_perm_user) set(TEST_ROLE_DEFAULT_PERM_USER_2 default_perm_user_2) set(TEST_INPUT_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(TEST_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) set(TEST_CLUSTER ${TEST_OUTPUT_DIR}/testcluster) # Basic connection info for test instance set(TEST_PGPORT_LOCAL 5432 CACHE STRING "The port of a running PostgreSQL instance") set(TEST_PGHOST localhost CACHE STRING "The hostname of a running PostgreSQL instance") set(TEST_PGUSER ${TEST_ROLE_DEFAULT_PERM_USER} CACHE STRING "The PostgreSQL test user") set(TEST_DBNAME single CACHE STRING "The database name to use for tests") set(TEST_PGPORT_TEMP_INSTANCE 15432 CACHE STRING "The port to run a temporary test PostgreSQL instance on") set(TEST_SCHEDULE ${CMAKE_CURRENT_BINARY_DIR}/test_schedule) set(TEST_SCHEDULE_SHARED ${CMAKE_CURRENT_BINARY_DIR}/shared/test_schedule_shared) set(ISOLATION_TEST_SCHEDULE ${CMAKE_CURRENT_BINARY_DIR}/isolation_test_schedule) set(PG_REGRESS_OPTS_BASE --host=${TEST_PGHOST} --load-extension=plpgsql --dlpath=${PROJECT_BINARY_DIR}/src) set(PG_REGRESS_OPTS_EXTRA --create-role=${TEST_ROLE_SUPERUSER},${TEST_ROLE_DEFAULT_PERM_USER},${TEST_ROLE_DEFAULT_PERM_USER_2} --dbname=${TEST_DBNAME} --launcher=${PRIMARY_TEST_DIR}/runner.sh) set(PG_REGRESS_SHARED_OPTS_EXTRA --create-role=${TEST_ROLE_SUPERUSER},${TEST_ROLE_DEFAULT_PERM_USER},${TEST_ROLE_DEFAULT_PERM_USER_2} --dbname=${TEST_DBNAME} --launcher=${PRIMARY_TEST_DIR}/runner_shared.sh) set(PG_ISOLATION_REGRESS_OPTS_EXTRA --create-role=${TEST_ROLE_SUPERUSER},${TEST_ROLE_DEFAULT_PERM_USER},${TEST_ROLE_DEFAULT_PERM_USER_2} --dbname=${TEST_DBNAME}) set(PG_REGRESS_OPTS_INOUT --inputdir=${TEST_INPUT_DIR} --outputdir=${TEST_OUTPUT_DIR}) set(PG_REGRESS_SHARED_OPTS_INOUT --inputdir=${TEST_INPUT_DIR}/shared --outputdir=${TEST_OUTPUT_DIR}/shared --load-extension=timescaledb) set(PG_ISOLATION_REGRESS_OPTS_INOUT --inputdir=${TEST_INPUT_DIR}/isolation --outputdir=${TEST_OUTPUT_DIR}/isolation --load-extension=timescaledb) set(PG_REGRESS_OPTS_TEMP_INSTANCE --port=${TEST_PGPORT_TEMP_INSTANCE} --temp-instance=${TEST_CLUSTER} --temp-config=${TEST_INPUT_DIR}/postgresql.conf ) set(PG_REGRESS_OPTS_LOCAL_INSTANCE --port=${TEST_PGPORT_LOCAL}) if(PG_REGRESS) set(PG_REGRESS_ENV TEST_PGUSER=${TEST_PGUSER} TEST_PGHOST=${TEST_PGHOST} TEST_ROLE_SUPERUSER=${TEST_ROLE_SUPERUSER} TEST_ROLE_DEFAULT_PERM_USER=${TEST_ROLE_DEFAULT_PERM_USER} TEST_ROLE_DEFAULT_PERM_USER_2=${TEST_ROLE_DEFAULT_PERM_USER_2} TEST_DBNAME=${TEST_DBNAME} TEST_INPUT_DIR=${TEST_INPUT_DIR} TEST_OUTPUT_DIR=${TEST_OUTPUT_DIR} TEST_SCHEDULE=${TEST_SCHEDULE} PG_BINDIR=${PG_BINDIR} PG_REGRESS=${PG_REGRESS}) endif() if(PG_ISOLATION_REGRESS) set(PG_ISOLATION_REGRESS_ENV TEST_PGUSER=${TEST_PGUSER} TEST_ROLE_SUPERUSER=${TEST_ROLE_SUPERUSER} TEST_ROLE_DEFAULT_PERM_USER=${TEST_ROLE_DEFAULT_PERM_USER} TEST_ROLE_DEFAULT_PERM_USER_2=${TEST_ROLE_DEFAULT_PERM_USER_2} TEST_DBNAME=${TEST_DBNAME} TEST_INPUT_DIR=${TEST_INPUT_DIR} TEST_OUTPUT_DIR=${TEST_OUTPUT_DIR} ISOLATION_TEST_SCHEDULE=${ISOLATION_TEST_SCHEDULE} PG_ISOLATION_REGRESS=${PG_ISOLATION_REGRESS}) endif() if (${PG_VERSION_MAJOR} GREATER "9") set(TEST_VERSION_SUFFIX ${PG_VERSION_MAJOR}) else () set(TEST_VERSION_SUFFIX ${PG_VERSION_MAJOR}.${PG_VERSION_MINOR}) endif () timestamp9-timestamp9-1.4.0/timestamp9.control000066400000000000000000000002271443766336600214230ustar00rootroot00000000000000# timestamp9 extension comment = 'timestamp nanosecond resolution' default_version = '1.4.0' module_pathname = '$libdir/timestamp9' relocatable = true timestamp9-timestamp9-1.4.0/version.config000066400000000000000000000000541443766336600205770ustar00rootroot00000000000000version = 1.4.0 update_from_version = 0.1.0