@@ -49,14 +49,18 @@ use crate::reader::DuckDBReader;
4949/// - Partition_by columns exist in schema
5050/// - Remapping target aesthetics are supported by geom
5151/// - Remapping source columns are valid stat columns for geom
52- fn validate ( layers : & [ Layer ] , layer_schemas : & [ Schema ] ) -> Result < ( ) > {
52+ fn validate (
53+ layers : & [ Layer ] ,
54+ layer_schemas : & [ Schema ] ,
55+ aesthetic_context : & Option < AestheticContext > ,
56+ ) -> Result < ( ) > {
5357 for ( idx, ( layer, schema) ) in layers. iter ( ) . zip ( layer_schemas. iter ( ) ) . enumerate ( ) {
5458 let schema_columns: HashSet < & str > = schema. iter ( ) . map ( |c| c. name . as_str ( ) ) . collect ( ) ;
5559 let supported = layer. geom . aesthetics ( ) . supported ( ) ;
5660
5761 // Validate required aesthetics for this geom
5862 layer
59- . validate_required_aesthetics ( )
63+ . validate_mapping ( aesthetic_context , false )
6064 . map_err ( |e| GgsqlError :: ValidationError ( format ! ( "Layer {}: {}" , idx + 1 , e) ) ) ?;
6165
6266 // Validate SETTING parameters are valid for this geom
@@ -1029,7 +1033,11 @@ pub fn prepare_data_with_reader<R: Reader>(query: &str, reader: &R) -> Result<Pr
10291033
10301034 // Validate all layers against their schemas
10311035 // This must happen BEFORE build_layer_query because stat transforms remove consumed aesthetics
1032- validate ( & specs[ 0 ] . layers , & layer_schemas) ?;
1036+ validate (
1037+ & specs[ 0 ] . layers ,
1038+ & layer_schemas,
1039+ & specs[ 0 ] . aesthetic_context ,
1040+ ) ?;
10331041
10341042 // Create scales for all mapped aesthetics that don't have explicit SCALE clauses
10351043 scale:: create_missing_scales ( & mut specs[ 0 ] ) ;
@@ -2503,8 +2511,8 @@ mod tests {
25032511 match result {
25042512 Err ( GgsqlError :: ValidationError ( msg) ) => {
25052513 assert ! (
2506- msg. contains( "pos2 " ) ,
2507- "Error should mention missing pos2 aesthetic: {}" ,
2514+ msg. contains( "y " ) ,
2515+ "Error should mention missing y aesthetic: {}" ,
25082516 msg
25092517 ) ;
25102518 }
@@ -2645,4 +2653,88 @@ mod tests {
26452653 "line layer should not have fill due to null AS fill"
26462654 ) ;
26472655 }
2656+
2657+ // ========================================================================
2658+ // Validation Error Message Tests (User-facing aesthetic names)
2659+ // ========================================================================
2660+
2661+ #[ cfg( feature = "duckdb" ) ]
2662+ #[ test]
2663+ fn test_validation_error_shows_user_facing_names_for_missing_aesthetics ( ) {
2664+ // Test that validation errors show user-facing names (x, y) instead of internal (pos1, pos2)
2665+ let reader = DuckDBReader :: from_connection_string ( "duckdb://memory" ) . unwrap ( ) ;
2666+
2667+ reader
2668+ . connection ( )
2669+ . execute (
2670+ "CREATE TABLE test_data AS SELECT * FROM (VALUES (1, 2)) AS t(a, b)" ,
2671+ duckdb:: params![ ] ,
2672+ )
2673+ . unwrap ( ) ;
2674+
2675+ // Query missing required aesthetic 'y' - should show 'y' not 'pos2'
2676+ let query = r#"
2677+ SELECT * FROM test_data
2678+ VISUALISE
2679+ DRAW point MAPPING a AS x
2680+ "# ;
2681+
2682+ let result = prepare_data_with_reader ( query, & reader) ;
2683+ assert ! ( result. is_err( ) , "Expected validation error" ) ;
2684+
2685+ let err_msg = match result {
2686+ Err ( e) => e. to_string ( ) ,
2687+ Ok ( _) => panic ! ( "Expected error" ) ,
2688+ } ;
2689+ assert ! (
2690+ err_msg. contains( "`y`" ) ,
2691+ "Error should mention user-facing name 'y', got: {}" ,
2692+ err_msg
2693+ ) ;
2694+ assert ! (
2695+ !err_msg. contains( "pos2" ) ,
2696+ "Error should not mention internal name 'pos2', got: {}" ,
2697+ err_msg
2698+ ) ;
2699+ }
2700+
2701+ #[ cfg( feature = "duckdb" ) ]
2702+ #[ test]
2703+ fn test_validation_error_shows_user_facing_names_for_unsupported_aesthetics ( ) {
2704+ // Test that validation errors show user-facing names for unsupported aesthetics
2705+ let reader = DuckDBReader :: from_connection_string ( "duckdb://memory" ) . unwrap ( ) ;
2706+
2707+ reader
2708+ . connection ( )
2709+ . execute (
2710+ "CREATE TABLE test_data AS SELECT * FROM (VALUES (1, 2, 3)) AS t(a, b, c)" ,
2711+ duckdb:: params![ ] ,
2712+ )
2713+ . unwrap ( ) ;
2714+
2715+ // Query with unsupported aesthetic 'xmin' for point - should show 'xmin' not 'pos1min'
2716+ let query = r#"
2717+ SELECT * FROM test_data
2718+ VISUALISE
2719+ DRAW point MAPPING a AS x, b AS y, c AS xmin
2720+ "# ;
2721+
2722+ let result = prepare_data_with_reader ( query, & reader) ;
2723+ assert ! ( result. is_err( ) , "Expected validation error" ) ;
2724+
2725+ let err_msg = match result {
2726+ Err ( e) => e. to_string ( ) ,
2727+ Ok ( _) => panic ! ( "Expected error" ) ,
2728+ } ;
2729+ assert ! (
2730+ err_msg. contains( "`xmin`" ) ,
2731+ "Error should mention user-facing name 'xmin', got: {}" ,
2732+ err_msg
2733+ ) ;
2734+ assert ! (
2735+ !err_msg. contains( "pos1min" ) ,
2736+ "Error should not mention internal name 'pos1min', got: {}" ,
2737+ err_msg
2738+ ) ;
2739+ }
26482740}
0 commit comments