/** * Copyright (c) 2015, Timothy Stack * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Timothy Stack nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @file time-extension-functions.cc */ #include "config.h" #include #include #include #include #include #include "base/date_time_scanner.hh" #include "base/lrucache.hpp" #include "sql_util.hh" #include "relative_time.hh" #include "vtab_module.hh" using namespace std; static nonstd::optional timeslice(sqlite3_value *time_in, nonstd::optional slice_in_opt) { thread_local date_time_scanner dts; thread_local struct { std::string c_slice_str; relative_time c_rel_time; } cache; const auto slice_in = string_fragment(slice_in_opt.value_or("15m")); if (slice_in.empty()) { throw sqlite_func_error("no time slice value given"); } if (slice_in != cache.c_slice_str.c_str()) { auto parse_res = relative_time::from_str(slice_in.data()); if (parse_res.isErr()) { throw sqlite_func_error("unable to parse time slice value: {} -- {}", slice_in, parse_res.unwrapErr().pe_msg); } cache.c_rel_time = parse_res.unwrap(); if (cache.c_rel_time.empty()) { throw sqlite_func_error("could not determine a time slice from: {}", slice_in); } cache.c_slice_str = slice_in.to_string(); } struct timeval tv; struct exttm tm; switch (sqlite3_value_type(time_in)) { case SQLITE_BLOB: case SQLITE3_TEXT: { const char *time_in_str = reinterpret_cast(sqlite3_value_text( time_in)); if (dts.scan(time_in_str, strlen(time_in_str), nullptr, &tm, tv, false) == nullptr) { dts.unlock(); if (dts.scan(time_in_str, strlen(time_in_str), nullptr, &tm, tv, false) == nullptr) { throw sqlite_func_error("unable to parse time value -- {}", time_in_str); } } break; } case SQLITE_INTEGER: { auto msecs = std::chrono::milliseconds(sqlite3_value_int64(time_in)); tv.tv_sec = std::chrono::duration_cast(msecs) .count(); tm.et_tm = *gmtime(&tv.tv_sec); tm.et_nsec = std::chrono::duration_cast( msecs % 1000).count(); break; } case SQLITE_FLOAT: { auto secs = sqlite3_value_double(time_in); double integ; auto fract = modf(secs, &integ); tv.tv_sec = integ; tm.et_tm = *gmtime(&tv.tv_sec); tm.et_nsec = floor(fract * 1000000000.0); break; } case SQLITE_NULL: { return nonstd::nullopt; } } auto win_start_opt = cache.c_rel_time.window_start(tm); if (!win_start_opt) { return nonstd::nullopt; } auto win_start = *win_start_opt; auto ts = auto_buffer::alloc(64); auto actual_length = sql_strftime(ts.in(), ts.size(), win_start.to_timeval()); ts.shrink_to(actual_length); return text_auto_buffer{ std::move(ts) }; } static nonstd::optional sql_timediff(const char *time1, const char *time2) { struct timeval tv1, tv2, retval; date_time_scanner dts1, dts2; auto parse_res1 = relative_time::from_str(time1, -1); if (parse_res1.isOk()) { tv1 = parse_res1.unwrap().adjust_now().to_timeval(); } else if (!dts1.convert_to_timeval(time1, -1, nullptr, tv1)) { return nonstd::nullopt; } auto parse_res2 = relative_time::from_str(time2, -1); if (parse_res2.isOk()) { tv2 = parse_res2.unwrap().adjust_now().to_timeval(); } else if (!dts2.convert_to_timeval(time2, -1, nullptr, tv2)) { return nonstd::nullopt; } timersub(&tv1, &tv2, &retval); return (double) retval.tv_sec + (double) retval.tv_usec / 1000000.0; } int time_extension_functions(struct FuncDef **basic_funcs, struct FuncDefAgg **agg_funcs) { static struct FuncDef time_funcs[] = { sqlite_func_adapter::builder( help_text("timeslice", "Return the start of the slice of time that the given timestamp falls in. " "If the time falls outside of the slice, NULL is returned.") .sql_function() .with_parameter({"time", "The timestamp to get the time slice for."}) .with_parameter({"slice", "The size of the time slices"}) .with_tags({"datetime"}) .with_example({ "To get the timestamp rounded down to the start of the ten minute slice", "SELECT timeslice('2017-01-01T05:05:00', '10m')" }) .with_example({ "To group log messages into five minute buckets and count them", "SELECT timeslice(log_time_msecs, '5m') AS slice, count(1) FROM lnav_example_log GROUP BY slice" }) .with_example({ "To group log messages by those before 4:30am and after", "SELECT timeslice(log_time_msecs, 'before 4:30am') AS slice, count(1) FROM lnav_example_log GROUP BY slice" }) ), sqlite_func_adapter::builder( help_text("timediff", "Compute the difference between two timestamps in seconds") .sql_function() .with_parameter({"time1", "The first timestamp"}) .with_parameter({"time2", "The timestamp to subtract from the first"}) .with_tags({"datetime"}) .with_example({ "To get the difference between two timestamps", "SELECT timediff('2017-02-03T04:05:06', '2017-02-03T04:05:00')" }) .with_example({ "To get the difference between relative timestamps", "SELECT timediff('today', 'yesterday')" }) ), { nullptr } }; *basic_funcs = time_funcs; *agg_funcs = nullptr; return SQLITE_OK; }