@@ -26,21 +26,31 @@ pub struct SqlErrorSpan {
2626 pub start : usize ,
2727 pub end : usize ,
2828 pub line : usize ,
29- pub near : Option < String > ,
29+ pub highlight : Option < String > ,
3030}
3131
3232fn format_sql_error_loc ( span : & Option < SqlErrorSpan > ) -> String {
3333 span. as_ref ( )
3434 . map ( |s| {
35- if let Some ( near ) = & s. near {
36- format ! ( " near '{near}' at line {}" , s . line )
35+ if let Some ( highlight ) = & s. highlight {
36+ format ! ( "\n {highlight}" )
3737 } else {
3838 format ! ( " at line {}, range {}..{}" , s. line, s. start, s. end)
3939 }
4040 } )
4141 . unwrap_or_default ( )
4242}
4343
44+ fn format_not_null_message ( column : & Option < String > , span : & Option < SqlErrorSpan > ) -> String {
45+ match column {
46+ Some ( column) => format ! (
47+ "column: {column} cannot be null{}" ,
48+ format_sql_error_loc( span)
49+ ) ,
50+ None => format ! ( "cannot be null{}" , format_sql_error_loc( span) ) ,
51+ }
52+ }
53+
4454#[ derive( thiserror:: Error , Debug ) ]
4555pub enum DatabaseError {
4656 #[ error( "agg miss: {0}" ) ]
@@ -160,8 +170,11 @@ pub enum DatabaseError {
160170 } ,
161171 #[ error( "no transaction begin" ) ]
162172 NoTransactionBegin ,
163- #[ error( "cannot be null" ) ]
164- NotNull ,
173+ #[ error( "{msg}" , msg = format_not_null_message( column, span) ) ]
174+ NotNull {
175+ column : Option < String > ,
176+ span : Option < SqlErrorSpan > ,
177+ } ,
165178 #[ error( "over flow" ) ]
166179 OverFlow ,
167180 #[ error( "parser bool: {0}" ) ]
@@ -291,6 +304,20 @@ impl DatabaseError {
291304 }
292305 }
293306
307+ pub fn not_null ( ) -> Self {
308+ Self :: NotNull {
309+ column : None ,
310+ span : None ,
311+ }
312+ }
313+
314+ pub fn not_null_column ( name : impl Into < String > ) -> Self {
315+ Self :: NotNull {
316+ column : Some ( name. into ( ) ) ,
317+ span : None ,
318+ }
319+ }
320+
294321 pub fn with_span ( self , span : SqlErrorSpan ) -> Self {
295322 match self {
296323 Self :: CastFail { from, to, .. } => Self :: CastFail {
@@ -318,15 +345,19 @@ impl DatabaseError {
318345 name,
319346 span : Some ( span) ,
320347 } ,
348+ Self :: NotNull { column, .. } => Self :: NotNull {
349+ column,
350+ span : Some ( span) ,
351+ } ,
321352 other => other,
322353 }
323354 }
324355
325356 pub fn with_sql_context ( self , sql : & str ) -> Self {
326357 let annotate = |span : Option < SqlErrorSpan > | -> Option < SqlErrorSpan > {
327358 span. map ( |mut span| {
328- if span. near . is_none ( ) {
329- span. near = extract_sql_near ( sql, & span) ;
359+ if span. highlight . is_none ( ) {
360+ span. highlight = build_sql_highlight ( sql, & span) ;
330361 }
331362 span
332363 } )
@@ -358,6 +389,10 @@ impl DatabaseError {
358389 name,
359390 span : annotate ( span) ,
360391 } ,
392+ Self :: NotNull { column, span } => Self :: NotNull {
393+ column,
394+ span : annotate ( span) ,
395+ } ,
361396 other => other,
362397 }
363398 }
@@ -369,38 +404,48 @@ impl DatabaseError {
369404 | DatabaseError :: ColumnNotFound { span, .. }
370405 | DatabaseError :: InvalidTable { span, .. }
371406 | DatabaseError :: FunctionNotFound { span, .. }
372- | DatabaseError :: ParametersNotFound { span, .. } => span. as_ref ( ) ,
407+ | DatabaseError :: ParametersNotFound { span, .. }
408+ | DatabaseError :: NotNull { span, .. } => span. as_ref ( ) ,
373409 _ => None ,
374410 }
375411 }
376412}
377413
378- fn extract_sql_near ( sql : & str , span : & SqlErrorSpan ) -> Option < String > {
414+ fn build_sql_highlight ( sql : & str , span : & SqlErrorSpan ) -> Option < String > {
379415 if span. line == 0 || span. start == 0 {
380416 return None ;
381417 }
382418
383- let line = sql. lines ( ) . nth ( span. line . saturating_sub ( 1 ) ) ?;
384- let line = line. trim_end_matches ( '\r' ) ;
419+ let lines = sql
420+ . lines ( )
421+ . map ( |line| line. trim_end_matches ( '\r' ) . to_string ( ) )
422+ . collect :: < Vec < _ > > ( ) ;
423+ if lines. is_empty ( ) || span. line > lines. len ( ) {
424+ return None ;
425+ }
385426
386- let line_char_count = line . chars ( ) . count ( ) ;
387- let start_idx = span . start . saturating_sub ( 1 ) . min ( line_char_count ) ;
388- let end_idx = span. end . min ( line_char_count ) . max ( start_idx + 1 ) ;
427+ let width = lines . len ( ) . to_string ( ) . len ( ) ;
428+ let mut out = String :: new ( ) ;
429+ out . push_str ( & format ! ( "--> line {} \n " , span. line ) ) ;
389430
390- let start_byte = char_to_byte_offset ( line , start_idx ) ? ;
391- let end_byte = char_to_byte_offset ( line , end_idx ) ? ;
392- let near = line . get ( start_byte..end_byte ) ? . trim ( ) ;
431+ for ( i , line ) in lines . iter ( ) . enumerate ( ) {
432+ let line_no = i + 1 ;
433+ out . push_str ( & format ! ( "{line_no:>width$} | {line} \n " , width = width ) ) ;
393434
394- if near. is_empty ( ) {
395- None
396- } else {
397- Some ( near. to_string ( ) )
435+ if line_no == span. line {
436+ let char_len = line. chars ( ) . count ( ) ;
437+ let start = span. start . saturating_sub ( 1 ) . min ( char_len) ;
438+ let end = span. end . min ( char_len) . max ( start + 1 ) ;
439+ let marker_len = end. saturating_sub ( start) . max ( 1 ) ;
440+ out. push_str ( & format ! (
441+ "{:>width$} | {}{}\n " ,
442+ "" ,
443+ " " . repeat( start) ,
444+ "^" . repeat( marker_len) ,
445+ width = width
446+ ) ) ;
447+ }
398448 }
399- }
400449
401- fn char_to_byte_offset ( s : & str , char_index : usize ) -> Option < usize > {
402- if char_index == s. chars ( ) . count ( ) {
403- return Some ( s. len ( ) ) ;
404- }
405- s. char_indices ( ) . nth ( char_index) . map ( |( idx, _) | idx)
450+ Some ( out. trim_end ( ) . to_string ( ) )
406451}
0 commit comments