66#include " duckdb/parser/statement/extension_statement.hpp"
77#include " duckdb/function/table_function.hpp"
88#include " duckdb/main/connection.hpp"
9+ #include " duckdb/logging/logger.hpp"
910
1011// Include FFI types header
1112#include " yardstick_ffi.h"
@@ -222,6 +223,8 @@ static std::string RewritePercentileWithinGroup(const std::string &sql) {
222223// TABLE FUNCTION: yardstick(sql) - Execute SQL with AGGREGATE() expansion
223224// =============================================================================
224225
226+ static void HandleAggregateWarnings (ClientContext &context, const string &warnings);
227+
225228struct YardstickQueryData : public TableFunctionData {
226229 string original_sql;
227230 string rewritten_sql;
@@ -235,6 +238,9 @@ static unique_ptr<FunctionData> YardstickQueryBind(ClientContext &context,
235238 vector<string> &names) {
236239 auto data = make_uniq<YardstickQueryData>();
237240 data->original_sql = input.inputs [0 ].GetValue <string>();
241+ if (input.inputs .size () > 1 ) {
242+ HandleAggregateWarnings (context, input.inputs [1 ].GetValue <string>());
243+ }
238244
239245 // Rewrite the SQL using our Rust code
240246 if (yardstick_has_aggregate (data->original_sql .c_str ())) {
@@ -330,6 +336,49 @@ static bool StartsWithSemantic(const std::string &query, std::string &stripped_q
330336 return true ;
331337}
332338
339+ static std::string EscapeSqlStringLiteral (const std::string &value) {
340+ string escaped;
341+ for (char c : value) {
342+ if (c == ' \' ' ) {
343+ escaped += " ''" ;
344+ } else {
345+ escaped += c;
346+ }
347+ }
348+ return escaped;
349+ }
350+
351+ static std::string AggregateWarnings (YardstickAggregateResult &result) {
352+ return result.warnings ? string (result.warnings ) : string ();
353+ }
354+
355+ static bool WarningsAsErrors (ClientContext &context) {
356+ Value setting;
357+ if (!context.TryGetCurrentSetting (" warnings_as_errors" , setting) || setting.IsNull ()) {
358+ return false ;
359+ }
360+ return setting.GetValue <bool >();
361+ }
362+
363+ static void HandleAggregateWarnings (ClientContext &context, const string &warnings) {
364+ if (warnings.empty ()) {
365+ return ;
366+ }
367+ if (WarningsAsErrors (context)) {
368+ throw InvalidInputException (" %s" , warnings);
369+ }
370+ DUCKDB_LOG_WARNING (context, " %s" , warnings.c_str ());
371+ }
372+
373+ static std::string YardstickWrapperSql (const std::string &expanded_sql, const std::string &warnings) {
374+ string wrapper_sql = " SELECT * FROM yardstick('" + EscapeSqlStringLiteral (expanded_sql) + " '" ;
375+ if (!warnings.empty ()) {
376+ wrapper_sql += " , '" + EscapeSqlStringLiteral (warnings) + " '" ;
377+ }
378+ wrapper_sql += " )" ;
379+ return wrapper_sql;
380+ }
381+
333382#if YARDSTICK_TOKEN_PARSE_FN
334383// DuckDB main: parse_function receives the post-PEG-failure token tail rather
335384// than the query string. Yardstick rewrites queries earlier, in
@@ -367,20 +416,10 @@ ParserExtensionParseResult yardstick_parse(ParserExtensionInfo *,
367416
368417 if (result.had_aggregate ) {
369418 string expanded_sql (result.expanded_sql );
419+ string warnings = AggregateWarnings (result);
370420 yardstick_free_aggregate_result (result);
371421
372- // Escape single quotes for embedding in string literal
373- string escaped_sql;
374- for (char c : expanded_sql) {
375- if (c == ' \' ' ) {
376- escaped_sql += " ''" ;
377- } else {
378- escaped_sql += c;
379- }
380- }
381-
382- // Wrap in table function call
383- string wrapper_sql = " SELECT * FROM yardstick('" + escaped_sql + " ')" ;
422+ string wrapper_sql = YardstickWrapperSql (expanded_sql, warnings);
384423
385424 Parser parser;
386425 parser.ParseQuery (wrapper_sql);
@@ -469,6 +508,7 @@ ParserOverrideResult yardstick_parser_override(ParserExtensionInfo *,
469508
470509 if (result.had_aggregate ) {
471510 string expanded_sql (result.expanded_sql );
511+ string warnings = AggregateWarnings (result);
472512 yardstick_free_aggregate_result (result);
473513
474514 // Validate the expanded SQL parses. If expansion produced garbage
@@ -489,16 +529,7 @@ ParserOverrideResult yardstick_parser_override(ParserExtensionInfo *,
489529 validation_parser.statements [0 ]->type == StatementType::SELECT_STATEMENT ;
490530
491531 if (is_select) {
492- string escaped_sql;
493- for (char c : expanded_sql) {
494- if (c == ' \' ' ) {
495- escaped_sql += " ''" ;
496- } else {
497- escaped_sql += c;
498- }
499- }
500-
501- string wrapper_sql = " SELECT * FROM yardstick('" + escaped_sql + " ')" ;
532+ string wrapper_sql = YardstickWrapperSql (expanded_sql, warnings);
502533 Parser parser;
503534 parser.ParseQuery (wrapper_sql);
504535 return ParserOverrideResult (std::move (parser.statements ));
@@ -594,20 +625,11 @@ BoundStatement yardstick_bind(ClientContext &context, Binder &binder,
594625
595626 if (result.had_aggregate ) {
596627 string expanded_sql (result.expanded_sql );
628+ string warnings = AggregateWarnings (result);
597629 yardstick_free_aggregate_result (result);
598630
599- // Escape single quotes for embedding in string literal
600- string escaped_sql;
601- for (char c : expanded_sql) {
602- if (c == ' \' ' ) {
603- escaped_sql += " ''" ;
604- } else {
605- escaped_sql += c;
606- }
607- }
608-
609631 // Rebind through table function so rewritten SQL executes with normal planning
610- string wrapper_sql = " SELECT * FROM yardstick(' " + escaped_sql + " ') " ;
632+ string wrapper_sql = YardstickWrapperSql (expanded_sql, warnings) ;
611633 Parser parser;
612634 parser.ParseQuery (wrapper_sql);
613635 auto statements = std::move (parser.statements );
@@ -678,6 +700,9 @@ static void LoadInternal(ExtensionLoader &loader) {
678700 TableFunction query_func (" yardstick" , {LogicalType::VARCHAR },
679701 YardstickQueryFunction, YardstickQueryBind);
680702 loader.RegisterFunction (query_func);
703+ TableFunction query_func_with_warnings (" yardstick" , {LogicalType::VARCHAR , LogicalType::VARCHAR },
704+ YardstickQueryFunction, YardstickQueryBind);
705+ loader.RegisterFunction (query_func_with_warnings);
681706}
682707
683708void YardstickExtension::Load (ExtensionLoader &loader) {
0 commit comments