diff --git a/Cargo.lock b/Cargo.lock index 6c1c1439b1..60f49db51b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -793,9 +793,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.30" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", diff --git a/influxdb_influxql_parser/src/literal.rs b/influxdb_influxql_parser/src/literal.rs index a301c82027..3611987051 100644 --- a/influxdb_influxql_parser/src/literal.rs +++ b/influxdb_influxql_parser/src/literal.rs @@ -592,10 +592,9 @@ mod test { // infallible let ts = nanos_to_timestamp(i64::MAX); - assert_eq!(ts.timestamp_nanos(), i64::MAX); + assert_eq!(ts.timestamp_nanos_opt().unwrap(), i64::MAX); - // let ts = nanos_to_timestamp(i64::MIN); - // This line panics with an arithmetic overflow. - // assert_eq!(ts.timestamp_nanos(), i64::MIN); + let ts = nanos_to_timestamp(i64::MIN); + assert_eq!(ts.timestamp_nanos_opt().unwrap(), i64::MIN); } } diff --git a/influxdb_influxql_parser/src/time_range.rs b/influxdb_influxql_parser/src/time_range.rs index afb9526430..4d07580d34 100644 --- a/influxdb_influxql_parser/src/time_range.rs +++ b/influxdb_influxql_parser/src/time_range.rs @@ -252,7 +252,14 @@ pub fn split_cond( }; let ts = match expr { - Expr::Literal(Literal::Timestamp(ts)) => ts.timestamp_nanos(), + Expr::Literal(Literal::Timestamp(ts)) => match ts.timestamp_nanos_opt() { + Some(ts) => ts, + None => { + return ControlFlow::Break(error::map::internal( + "timestamp out o range", + )); + } + }, expr => { return ControlFlow::Break(error::map::internal(format!( "expected Timestamp, got: {}", @@ -390,7 +397,9 @@ impl TimeRange { /// Simplifies an InfluxQL duration `expr` to a nanosecond interval represented as an `i64`. pub fn duration_expr_to_nanoseconds(ctx: &ReduceContext, expr: &Expr) -> Result<i64, ExprError> { match reduce_time_expr(ctx, expr)? { - Expr::Literal(Literal::Timestamp(v)) => Ok(v.timestamp_nanos()), + Expr::Literal(Literal::Timestamp(v)) => v + .timestamp_nanos_opt() + .ok_or_else(|| error::map::expr("timestamp out of range")), _ => error::expr("invalid duration expression"), } } @@ -637,10 +646,15 @@ fn expr_to_duration(ctx: &ReduceContext, expr: Expr) -> ExprResult { Ok(lit(match expr { Expr::Literal(Literal::Duration(v)) => v, Expr::Literal(Literal::Integer(v)) => Duration(v), - Expr::Literal(Literal::Timestamp(v)) => Duration(v.timestamp_nanos()), - Expr::Literal(Literal::String(v)) => { - Duration(parse_timestamp_nanos(&v, ctx.tz)?.timestamp_nanos()) - } + Expr::Literal(Literal::Timestamp(v)) => Duration( + v.timestamp_nanos_opt() + .ok_or_else(|| error::map::expr("timestamp out of range"))?, + ), + Expr::Literal(Literal::String(v)) => Duration( + parse_timestamp_nanos(&v, ctx.tz)? + .timestamp_nanos_opt() + .ok_or_else(|| error::map::expr("timestamp out of range"))?, + ), _ => return error::expr(format!("unable to cast {expr} to duration")), })) } diff --git a/iox_data_generator/src/bin/iox_data_generator.rs b/iox_data_generator/src/bin/iox_data_generator.rs index 5a56e7063c..3355b2852b 100644 --- a/iox_data_generator/src/bin/iox_data_generator.rs +++ b/iox_data_generator/src/bin/iox_data_generator.rs @@ -139,12 +139,15 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { } let execution_start_time = Local::now(); + let execution_start_time_nanos = execution_start_time + .timestamp_nanos_opt() + .expect("'now' is in nano range"); let start_datetime = datetime_nanoseconds(config.start.as_deref(), execution_start_time); let end_datetime = datetime_nanoseconds(config.end.as_deref(), execution_start_time); - let start_display = start_datetime.unwrap_or_else(|| execution_start_time.timestamp_nanos()); - let end_display = end_datetime.unwrap_or_else(|| execution_start_time.timestamp_nanos()); + let start_display = start_datetime.unwrap_or(execution_start_time_nanos); + let end_display = end_datetime.unwrap_or(execution_start_time_nanos); let continue_on = config.do_continue; @@ -201,7 +204,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { &mut points_writer_builder, start_datetime, end_datetime, - execution_start_time.timestamp_nanos(), + execution_start_time_nanos, continue_on, config.batch_size, config.print, @@ -231,7 +234,9 @@ fn datetime_nanoseconds(arg: Option<&str>, now: DateTime<Local>) -> Option<i64> now - chrono_duration }); - datetime.timestamp_nanos() + datetime + .timestamp_nanos_opt() + .expect("timestamp out of range") }) } @@ -255,7 +260,9 @@ mod test { fn relative() { let fixed_now = Local::now(); let ns = datetime_nanoseconds(Some("1hr"), fixed_now); - let expected = (fixed_now - chrono::Duration::hours(1)).timestamp_nanos(); + let expected = (fixed_now - chrono::Duration::hours(1)) + .timestamp_nanos_opt() + .unwrap(); assert_eq!(ns, Some(expected)); } } diff --git a/iox_query/src/frontend.rs b/iox_query/src/frontend.rs index f3ac2892d0..7a6bd640b6 100644 --- a/iox_query/src/frontend.rs +++ b/iox_query/src/frontend.rs @@ -39,12 +39,14 @@ mod test { .start_timestamp .value() .expect("start timestamp") - .timestamp_nanos(); + .timestamp_nanos_opt() + .expect("start timestamp in range"); let end_ts = $EXTRACTED .end_timestamp .value() .expect("end timestamp") - .timestamp_nanos(); + .timestamp_nanos_opt() + .expect("end timestamp in range"); assert!(start_ts > 0, "start timestamp was non zero"); assert!(end_ts > 0, "end timestamp was non zero"); diff --git a/iox_query_influxql/src/plan/planner.rs b/iox_query_influxql/src/plan/planner.rs index 313eb13ee5..cf8d72f180 100644 --- a/iox_query_influxql/src/plan/planner.rs +++ b/iox_query_influxql/src/plan/planner.rs @@ -1843,10 +1843,10 @@ impl<'a> InfluxQLToLogicalPlan<'a> { Literal::Float(v) => Ok(lit(*v)), Literal::String(v) => Ok(lit(v)), Literal::Boolean(v) => Ok(lit(*v)), - Literal::Timestamp(v) => Ok(lit(ScalarValue::TimestampNanosecond( - Some(v.timestamp_nanos()), - None, - ))), + Literal::Timestamp(v) => v + .timestamp_nanos_opt() + .ok_or_else(|| error::map::query("timestamp out of range")) + .map(|ts| lit(ScalarValue::TimestampNanosecond(Some(ts), None))), Literal::Duration(v) => { Ok(lit(ScalarValue::IntervalMonthDayNano(Some((**v).into())))) } @@ -2210,9 +2210,14 @@ impl<'a> InfluxQLToLogicalPlan<'a> { let time_range = if time_range.is_unbounded() { TimeRange { lower: Some(match cutoff { - MetadataCutoff::Absolute(dt) => dt.timestamp_nanos(), + MetadataCutoff::Absolute(dt) => dt + .timestamp_nanos_opt() + .ok_or_else(|| error::map::query("timestamp out of range"))?, MetadataCutoff::Relative(delta) => { - start_time.timestamp_nanos() - delta.as_nanos() as i64 + start_time + .timestamp_nanos_opt() + .ok_or_else(|| error::map::query("timestamp out of range"))? + - delta.as_nanos() as i64 } }), upper: None, diff --git a/iox_query_influxql/src/plan/rewriter.rs b/iox_query_influxql/src/plan/rewriter.rs index 5a0ad563a1..be45122959 100644 --- a/iox_query_influxql/src/plan/rewriter.rs +++ b/iox_query_influxql/src/plan/rewriter.rs @@ -123,7 +123,7 @@ impl RewriteSelect { let time_range = match (interval, time_range.upper) { (Some(interval), None) if interval.duration > 0 => TimeRange { lower: time_range.lower, - upper: Some(now.timestamp_nanos()), + upper: Some(now.timestamp_nanos_opt().expect("'now' in nano range")), }, _ => time_range, }; diff --git a/iox_time/Cargo.toml b/iox_time/Cargo.toml index ea982fe647..d8ef58e829 100644 --- a/iox_time/Cargo.toml +++ b/iox_time/Cargo.toml @@ -7,7 +7,7 @@ edition.workspace = true license.workspace = true [dependencies] -chrono = { version = "0.4.30", default-features = false, features = ["clock", "std"] } +chrono = { version = "0.4.31", default-features = false, features = ["clock", "std"] } parking_lot = "0.12" tokio = { version = "1.32", features = ["macros", "parking_lot", "rt-multi-thread", "sync", "time"] } workspace-hack = { version = "0.1", path = "../workspace-hack" } diff --git a/iox_time/src/lib.rs b/iox_time/src/lib.rs index 3fd72e2cdf..853155d231 100644 --- a/iox_time/src/lib.rs +++ b/iox_time/src/lib.rs @@ -111,7 +111,8 @@ impl Time { /// Returns the number of non-leap-nanoseconds since January 1, 1970 UTC pub fn timestamp_nanos(&self) -> i64 { - self.0.timestamp_nanos() + // TODO: ensure that this can never over-/underflow + self.0.timestamp_nanos_opt().expect("nanos in range") } /// Returns the number of seconds since January 1, 1970 UTC @@ -539,7 +540,7 @@ mod test { ); assert_eq!( time, - Time::from_timestamp_nanos(date_time.timestamp_nanos()) + Time::from_timestamp_nanos(date_time.timestamp_nanos_opt().unwrap()) ); assert_eq!( Time::from_timestamp_millis(date_time.timestamp_millis()).unwrap(), @@ -549,7 +550,10 @@ mod test { ) ); - assert_eq!(time.timestamp_nanos(), date_time.timestamp_nanos()); + assert_eq!( + time.timestamp_nanos(), + date_time.timestamp_nanos_opt().unwrap() + ); assert_eq!(time.timestamp_millis(), date_time.timestamp_millis()); assert_eq!(time.to_rfc3339(), date_time.to_rfc3339()); diff --git a/predicate/src/delete_predicate.rs b/predicate/src/delete_predicate.rs index 75a37878da..b298a630d5 100644 --- a/predicate/src/delete_predicate.rs +++ b/predicate/src/delete_predicate.rs @@ -206,7 +206,11 @@ fn parse_time(input: &str) -> Result<i64> { // See examples here https://docs.influxdata.com/influxdb/v2.0/reference/cli/influx/delete/#delete-all-points-within-a-specified-time-frame let datetime_result = DateTime::parse_from_rfc3339(input); match datetime_result { - Ok(datetime) => Ok(datetime.timestamp_nanos()), + Ok(datetime) => datetime + .timestamp_nanos_opt() + .ok_or_else(|| Error::InvalidTimestamp { + value: datetime.to_string(), + }), Err(timestamp_err) => { // See if it is in nanosecond form let time_result = input.parse::<i64>(); diff --git a/query_functions/src/window/internal.rs b/query_functions/src/window/internal.rs index 474a4c8127..8000881f50 100644 --- a/query_functions/src/window/internal.rs +++ b/query_functions/src/window/internal.rs @@ -199,7 +199,9 @@ fn to_timestamp_nanos_utc( let ndatetime = NaiveDateTime::new(ndate, ntime); let datetime = DateTime::<Utc>::from_naive_utc_and_offset(ndatetime, Utc); - datetime.timestamp_nanos() + datetime + .timestamp_nanos_opt() + .expect("timestamp nanos in range") } impl Add<Duration> for i64 { @@ -386,7 +388,7 @@ mod tests { /// t: mustParseTime("1970-02-01T00:00:00Z"), fn must_parse_time(s: &str) -> i64 { let datetime = DateTime::parse_from_rfc3339(s).unwrap(); - datetime.timestamp_nanos() + datetime.timestamp_nanos_opt().unwrap() } /// TestWindow_GetEarliestBounds diff --git a/router/Cargo.toml b/router/Cargo.toml index 03ba9c8e1b..001ce6608c 100644 --- a/router/Cargo.toml +++ b/router/Cargo.toml @@ -48,7 +48,7 @@ workspace-hack = { version = "0.1", path = "../workspace-hack" } [dev-dependencies] assert_matches = "1.5" base64 = "0.21.4" -chrono = { version = "0.4.30", default-features = false } +chrono = { version = "0.4.31", default-features = false } criterion = { version = "0.5", default-features = false, features = [ "async_tokio", "rayon", diff --git a/trace_exporters/src/jaeger.rs b/trace_exporters/src/jaeger.rs index d7a6155719..1e6e4fbdb5 100644 --- a/trace_exporters/src/jaeger.rs +++ b/trace_exporters/src/jaeger.rs @@ -139,7 +139,19 @@ impl JaegerAgentExporter { service_name: self.service_name.clone(), tags: self.tags.clone(), }, - spans: spans.into_iter().map(Into::into).collect(), + spans: spans + .into_iter() + .filter_map(|span| match jaeger::Span::try_from(span) { + Ok(span) => Some(span), + Err(e) => { + warn!( + %e, + "cannot convert span to jaeger format", + ); + None + } + }) + .collect(), seq_no, stats: None, } diff --git a/trace_exporters/src/jaeger/span.rs b/trace_exporters/src/jaeger/span.rs index 83addb6d97..f6234f5e3c 100644 --- a/trace_exporters/src/jaeger/span.rs +++ b/trace_exporters/src/jaeger/span.rs @@ -13,8 +13,10 @@ fn split_trace_id(trace_id: TraceId) -> (i64, i64) { (trace_id_high, trace_id_low) } -impl From<Span> for jaeger::Span { - fn from(mut s: Span) -> Self { +impl TryFrom<Span> for jaeger::Span { + type Error = String; + + fn try_from(mut s: Span) -> Result<Self, Self::Error> { let (trace_id_high, trace_id_low) = split_trace_id(s.ctx.trace_id); // A parent span id of 0 indicates no parent span ID (span IDs are non-zero) @@ -22,10 +24,17 @@ impl From<Span> for jaeger::Span { let (start_time, duration) = match (s.start, s.end) { (Some(start), Some(end)) => ( - start.timestamp_nanos() / 1000, + start.timestamp_nanos_opt().ok_or_else(|| { + format!("start timestamp cannot be represented as nanos: {start}") + })? / 1000, (end - start).num_microseconds().expect("no overflow"), ), - (Some(start), _) => (start.timestamp_nanos() / 1000, 0), + (Some(start), _) => ( + start.timestamp_nanos_opt().ok_or_else(|| { + format!("start timestamp cannot be represented as nanos: {start}") + })? / 1000, + 0, + ), _ => (0, 0), }; @@ -57,7 +66,12 @@ impl From<Span> for jaeger::Span { let logs = match s.events.is_empty() { true => None, - false => Some(s.events.into_iter().map(Into::into).collect()), + false => Some( + s.events + .into_iter() + .map(TryInto::try_into) + .collect::<Result<_, _>>()?, + ), }; let references = if s.ctx.links.is_empty() { @@ -81,7 +95,7 @@ impl From<Span> for jaeger::Span { ) }; - Self { + Ok(Self { trace_id_low, trace_id_high, span_id: s.ctx.span_id.get() as i64, @@ -93,14 +107,18 @@ impl From<Span> for jaeger::Span { duration, tags, logs, - } + }) } } -impl From<SpanEvent> for jaeger::Log { - fn from(event: SpanEvent) -> Self { - Self { - timestamp: event.time.timestamp_nanos() / 1000, +impl TryFrom<SpanEvent> for jaeger::Log { + type Error = String; + + fn try_from(event: SpanEvent) -> Result<Self, Self::Error> { + Ok(Self { + timestamp: event.time.timestamp_nanos_opt().ok_or_else(|| { + format!("timestamp cannot be represented as nanos: {}", event.time) + })? / 1000, fields: vec![jaeger::Tag { key: "event".to_string(), v_type: jaeger::TagType::String, @@ -110,7 +128,7 @@ impl From<SpanEvent> for jaeger::Log { v_long: None, v_binary: None, }], - } + }) } }