#!/usr/bin/env python3 # encoding: utf-8 import re import itertools import sys import argparse # Create SQL statement to verify dateTime64 is accepted as argument to functions taking DateTime. FUNCTIONS=""" toTimeZone(N, 'UTC') toYear(N) toQuarter(N) toMonth(N) toDayOfYear(N) toDayOfMonth(N) toDayOfWeek(N) toHour(N) toMinute(N) toSecond(N) toUnixTimestamp(N) toStartOfYear(N) toStartOfISOYear(N) toStartOfQuarter(N) toStartOfMonth(N) toMonday(N) toStartOfWeek(N) toStartOfDay(N) toStartOfHour(N) toStartOfMinute(N) toStartOfFiveMinute(N) toStartOfTenMinutes(N) toStartOfFifteenMinutes(N) toStartOfInterval(N, INTERVAL 1 year) toStartOfInterval(N, INTERVAL 1 month) toStartOfInterval(N, INTERVAL 1 day) toStartOfInterval(N, INTERVAL 15 minute) date_trunc('year', N) date_trunc('month', N) date_trunc('day', N) date_trunc('minute', N) toTime(N) toRelativeYearNum(N) toRelativeQuarterNum(N) toRelativeMonthNum(N) toRelativeWeekNum(N) toRelativeDayNum(N) toRelativeHourNum(N) toRelativeMinuteNum(N) toRelativeSecondNum(N) toISOYear(N) toISOWeek(N) toWeek(N) toYearWeek(N) timeSlot(N) toYYYYMM(N) toYYYYMMDD(N) toYYYYMMDDhhmmss(N) addYears(N, 1) addMonths(N, 1) addWeeks(N, 1) addDays(N, 1) addHours(N, 1) addMinutes(N, 1) addSeconds(N, 1) addQuarters(N, 1) subtractYears(N, 1) subtractMonths(N, 1) subtractWeeks(N, 1) subtractDays(N, 1) subtractHours(N, 1) subtractMinutes(N, 1) subtractSeconds(N, 1) subtractQuarters(N, 1) CAST(N as DateTime('Europe/Minsk')) CAST(N as Date) CAST(N as UInt64) CAST(N as DateTime64(0, 'Europe/Minsk')) CAST(N as DateTime64(3, 'Europe/Minsk')) CAST(N as DateTime64(6, 'Europe/Minsk')) CAST(N as DateTime64(9, 'Europe/Minsk')) # Casting our test values to DateTime(12) will cause an overflow and hence will fail the test under UB sanitizer. # CAST(N as DateTime64(12)) # DateTime64(18) will always fail due to zero precision, but it is Ok to test here: # CAST(N as DateTime64(18)) formatDateTime(N, '%C %d %D %e %F %H %I %j %m %M %p %R %S %T %u %V %w %y %Y %%') """.splitlines() # Expanded later to cartesian product of all arguments. # NOTE: {N} to be turned into N after str.format() for keys (format string), but not for list of values! extra_ops = [ # With same type: ( ['N {op} N'], { 'op': [ '- ', # does not work, but should it? '+ ', # does not work, but should it? '!=', '==', # equality and inequality supposed to take sub-second part in account '< ', '<=', '> ', '>=' ] } ), # With other DateTime types: ( [ 'N {op} {arg}', '{arg} {op} N' ], { 'op': [ '-', # does not work, but should it? '!=', '==', # these are naturally expected to work, but they don't: '< ', '<=', '> ', '>=' ], 'arg': ['DT', 'D', 'DT64'], } ), # With arithmetic types ( [ 'N {op} {arg}', '{arg} {op} N' ], { 'op': [ '+ ', '- ', '==', '!=', '< ', '<=', '> ', '>=' ], 'arg': [ 'toUInt8(1)', 'toInt8(-1)', 'toUInt16(1)', 'toInt16(-1)', 'toUInt32(1)', 'toInt32(-1)', 'toUInt64(1)', 'toInt64(-1)' ], }, ), ] # Expand extra_ops here for funcs, args in extra_ops: args_keys = list(args.keys()) for args_vals in itertools.product(*list(args.values())): for func in funcs: result_func = func.format(**dict(list(zip(args_keys, args_vals)))) FUNCTIONS.append(result_func) # filter out empty lines and commented out lines COMMENTED_OUT_LINE_RE = re.compile(r"^\s*#") FUNCTIONS = list([f for f in FUNCTIONS if len(f) != 0 and COMMENTED_OUT_LINE_RE.match(f) == None]) TYPES = ['D', 'DT', 'DT64'] def escape_string(s): if sys.version_info[0] > 2: return s.encode('unicode_escape').decode('utf-8').replace("'", "\\'") else: return s.encode('string-escape').decode('utf-8') def execute_functions_for_types(functions, types): # TODO: use string.Template here to allow lines that do not contain type, like: SELECT CAST(toDateTime64(1234567890), 'DateTime64') for func in functions: print(("""SELECT 'SELECT {func}';""".format(func=escape_string(func)))) for dt in types: prologue = "\ WITH \ toDateTime64('2019-09-16 19:20:11.234', 3, 'Europe/Minsk') as DT64, \ toDateTime('2019-09-16 19:20:11', 'Europe/Minsk') as DT, \ toDate('2019-09-16') as D, {X} as N".format(X=dt) print(("""{prologue} SELECT toTypeName(r), {func} as r FORMAT CSV;""".format(prologue=prologue, func=func))) print("""SELECT '------------------------------------------';""") def main(): def parse_args(): parser = argparse.ArgumentParser() parser.add_argument('--functions_re', type=re.compile, help="RE to enable functions", default=None) parser.add_argument('--types_re', type=lambda s: re.compile('^(' + s + ')$'), help="RE to enable types, supported types: " + ",".join(TYPES), default=None) parser.add_argument('--list_functions', action='store_true', help="List all functions to be tested and exit") return parser.parse_args() args = parse_args() functions = FUNCTIONS types = TYPES if args.functions_re: functions = list([f for f in functions if args.functions_re.search(f)]) if len(functions) == 0: print("functions list is empty") return -1 if args.types_re: types = list([t for t in types if args.types_re.match(t)]) if len(types) == 0: print("types list is empty") return -1 if args.list_functions: print(("\n".join(functions))) return 0 execute_functions_for_types(functions, types) if __name__ == '__main__': exit(main())