@@ -390,6 +390,325 @@ kcenon::common::VoidResult postgresql_backend::execute_query(const std::string&
390390#endif
391391}
392392
393+ kcenon::common::Result<core::database_result> postgresql_backend::select_prepared (
394+ const std::string& query,
395+ const std::vector<core::database_value>& params)
396+ {
397+ if (!is_initialized ()) {
398+ last_error_ = " Backend not initialized" ;
399+ return kcenon::common::error_info{
400+ static_cast <int >(database::error_code::invalid_state),
401+ last_error_,
402+ " postgresql_backend"
403+ };
404+ }
405+
406+ core::database_result result;
407+
408+ #ifdef USE_POSTGRESQL
409+ if (!connection_) {
410+ last_error_ = " No active connection" ;
411+ return kcenon::common::error_info{
412+ static_cast <int >(database::error_code::connection_failed),
413+ last_error_,
414+ " postgresql_backend"
415+ };
416+ }
417+ try {
418+ pqxx::connection* conn = static_cast <pqxx::connection*>(connection_);
419+ pqxx::work txn (*conn);
420+
421+ // Build pqxx::params from database_value vector
422+ pqxx::params pq_params;
423+ for (const auto & val : params) {
424+ std::visit ([&pq_params](const auto & v) {
425+ using T = std::decay_t <decltype (v)>;
426+ if constexpr (std::is_same_v<T, std::nullptr_t >) {
427+ pq_params.append ();
428+ } else if constexpr (std::is_same_v<T, bool >) {
429+ pq_params.append (v);
430+ } else if constexpr (std::is_same_v<T, int64_t >) {
431+ pq_params.append (v);
432+ } else if constexpr (std::is_same_v<T, double >) {
433+ pq_params.append (v);
434+ } else if constexpr (std::is_same_v<T, std::string>) {
435+ pq_params.append (v);
436+ }
437+ }, val);
438+ }
439+
440+ pqxx::result pqxx_result = txn.exec_params (query, pq_params);
441+ txn.commit ();
442+
443+ for (const auto & row : pqxx_result) {
444+ core::database_row db_row;
445+ for (size_t i = 0 ; i < row.size (); ++i) {
446+ std::string column_name = pqxx_result.column_name (i);
447+ if (row[i].is_null ()) {
448+ db_row[column_name] = nullptr ;
449+ } else {
450+ if (row[i].type () == PG_INT8OID ||
451+ row[i].type () == PG_INT4OID ) {
452+ db_row[column_name] = row[i].as <int64_t >();
453+ } else if (row[i].type () == PG_FLOAT8OID ||
454+ row[i].type () == PG_FLOAT4OID ) {
455+ db_row[column_name] = row[i].as <double >();
456+ } else if (row[i].type () == PG_BOOLOID ) {
457+ db_row[column_name] = row[i].as <bool >();
458+ } else {
459+ db_row[column_name] = row[i].as <std::string>();
460+ }
461+ }
462+ }
463+ result.push_back (std::move (db_row));
464+ }
465+ } catch (const std::exception& e) {
466+ last_error_ = std::string (" Select prepared error: " ) + e.what ();
467+ logger_.error (" select_prepared" , last_error_);
468+ return kcenon::common::error_info{
469+ static_cast <int >(database::error_code::query_failed),
470+ last_error_,
471+ " postgresql_backend"
472+ };
473+ }
474+ #elif defined(HAVE_LIBPQ)
475+ if (!connection_) {
476+ last_error_ = " No active connection" ;
477+ return kcenon::common::error_info{
478+ static_cast <int >(database::error_code::connection_failed),
479+ last_error_,
480+ " postgresql_backend"
481+ };
482+ }
483+ try {
484+ // Convert params to C string array for PQexecParams
485+ std::vector<std::string> param_strings;
486+ std::vector<const char *> param_values;
487+ param_strings.reserve (params.size ());
488+ param_values.reserve (params.size ());
489+
490+ for (const auto & val : params) {
491+ std::visit ([¶m_strings, ¶m_values](const auto & v) {
492+ using T = std::decay_t <decltype (v)>;
493+ if constexpr (std::is_same_v<T, std::nullptr_t >) {
494+ param_strings.emplace_back ();
495+ param_values.push_back (nullptr );
496+ } else if constexpr (std::is_same_v<T, bool >) {
497+ param_strings.push_back (v ? " t" : " f" );
498+ param_values.push_back (param_strings.back ().c_str ());
499+ } else if constexpr (std::is_same_v<T, std::string>) {
500+ param_strings.push_back (v);
501+ param_values.push_back (param_strings.back ().c_str ());
502+ } else {
503+ param_strings.push_back (std::to_string (v));
504+ param_values.push_back (param_strings.back ().c_str ());
505+ }
506+ }, val);
507+ }
508+
509+ PGresult* pg_result = PQexecParams (
510+ static_cast <PGconn*>(connection_),
511+ query.c_str (),
512+ static_cast <int >(params.size ()),
513+ nullptr , // let server infer types
514+ param_values.data (),
515+ nullptr , // text format lengths
516+ nullptr , // text format
517+ 0 // text result format
518+ );
519+
520+ if (PQresultStatus (pg_result) != PGRES_TUPLES_OK ) {
521+ last_error_ = PQerrorMessage (static_cast <PGconn*>(connection_));
522+ PQclear (pg_result);
523+ return kcenon::common::error_info{
524+ static_cast <int >(database::error_code::query_failed),
525+ last_error_,
526+ " postgresql_backend"
527+ };
528+ }
529+
530+ int rows = PQntuples (pg_result);
531+ int cols = PQnfields (pg_result);
532+
533+ for (int row = 0 ; row < rows; ++row) {
534+ core::database_row db_row;
535+ for (int col = 0 ; col < cols; ++col) {
536+ std::string column_name = PQfname (pg_result, col);
537+ if (PQgetisnull (pg_result, row, col)) {
538+ db_row[column_name] = nullptr ;
539+ } else {
540+ const char * value = PQgetvalue (pg_result, row, col);
541+ Oid type = PQftype (pg_result, col);
542+
543+ if (type == 20 || type == 21 || type == 23 ) {
544+ db_row[column_name] = static_cast <int64_t >(std::stoll (value));
545+ } else if (type == 700 || type == 701 ) {
546+ db_row[column_name] = std::stod (value);
547+ } else if (type == 16 ) {
548+ db_row[column_name] = (*value == ' t' || *value == ' 1' );
549+ } else {
550+ db_row[column_name] = std::string (value);
551+ }
552+ }
553+ }
554+ result.push_back (std::move (db_row));
555+ }
556+ PQclear (pg_result);
557+ } catch (const std::exception& e) {
558+ last_error_ = std::string (" Select prepared error: " ) + e.what ();
559+ logger_.error (" select_prepared" , last_error_);
560+ return kcenon::common::error_info{
561+ static_cast <int >(database::error_code::query_failed),
562+ last_error_,
563+ " postgresql_backend"
564+ };
565+ }
566+ #else
567+ // Fallback to string interpolation for mock mode
568+ return database_backend::select_prepared (query, params);
569+ #endif
570+
571+ last_error_.clear ();
572+ return result;
573+ }
574+
575+ kcenon::common::VoidResult postgresql_backend::execute_prepared (
576+ const std::string& query,
577+ const std::vector<core::database_value>& params)
578+ {
579+ if (!is_initialized ()) {
580+ last_error_ = " Backend not initialized" ;
581+ return kcenon::common::error_info{
582+ static_cast <int >(database::error_code::invalid_state),
583+ last_error_,
584+ " postgresql_backend"
585+ };
586+ }
587+
588+ #ifdef USE_POSTGRESQL
589+ try {
590+ if (!connection_) {
591+ last_error_ = " No active PostgreSQL connection" ;
592+ logger_.error (" execute_prepared" , last_error_);
593+ return kcenon::common::error_info{
594+ static_cast <int >(database::error_code::connection_failed),
595+ last_error_,
596+ " postgresql_backend"
597+ };
598+ }
599+
600+ pqxx::connection* conn = static_cast <pqxx::connection*>(connection_);
601+ pqxx::work txn (*conn);
602+
603+ pqxx::params pq_params;
604+ for (const auto & val : params) {
605+ std::visit ([&pq_params](const auto & v) {
606+ using T = std::decay_t <decltype (v)>;
607+ if constexpr (std::is_same_v<T, std::nullptr_t >) {
608+ pq_params.append ();
609+ } else if constexpr (std::is_same_v<T, bool >) {
610+ pq_params.append (v);
611+ } else if constexpr (std::is_same_v<T, int64_t >) {
612+ pq_params.append (v);
613+ } else if constexpr (std::is_same_v<T, double >) {
614+ pq_params.append (v);
615+ } else if constexpr (std::is_same_v<T, std::string>) {
616+ pq_params.append (v);
617+ }
618+ }, val);
619+ }
620+
621+ txn.exec_params (query, pq_params);
622+ txn.commit ();
623+ last_error_.clear ();
624+ return kcenon::common::ok ();
625+ } catch (const std::exception& e) {
626+ last_error_ = std::string (" Execute prepared error: " ) + e.what ();
627+ logger_.error (" execute_prepared" , last_error_);
628+ return kcenon::common::error_info{
629+ static_cast <int >(database::error_code::query_failed),
630+ last_error_,
631+ " postgresql_backend"
632+ };
633+ }
634+ #elif defined(HAVE_LIBPQ)
635+ if (!connection_) {
636+ last_error_ = " No active PostgreSQL connection" ;
637+ logger_.error (" execute_prepared" , last_error_);
638+ return kcenon::common::error_info{
639+ static_cast <int >(database::error_code::connection_failed),
640+ last_error_,
641+ " postgresql_backend"
642+ };
643+ }
644+
645+ std::vector<std::string> param_strings;
646+ std::vector<const char *> param_values;
647+ param_strings.reserve (params.size ());
648+ param_values.reserve (params.size ());
649+
650+ for (const auto & val : params) {
651+ std::visit ([¶m_strings, ¶m_values](const auto & v) {
652+ using T = std::decay_t <decltype (v)>;
653+ if constexpr (std::is_same_v<T, std::nullptr_t >) {
654+ param_strings.emplace_back ();
655+ param_values.push_back (nullptr );
656+ } else if constexpr (std::is_same_v<T, bool >) {
657+ param_strings.push_back (v ? " t" : " f" );
658+ param_values.push_back (param_strings.back ().c_str ());
659+ } else if constexpr (std::is_same_v<T, std::string>) {
660+ param_strings.push_back (v);
661+ param_values.push_back (param_strings.back ().c_str ());
662+ } else {
663+ param_strings.push_back (std::to_string (v));
664+ param_values.push_back (param_strings.back ().c_str ());
665+ }
666+ }, val);
667+ }
668+
669+ PGresult* pg_result = PQexecParams (
670+ static_cast <PGconn*>(connection_),
671+ query.c_str (),
672+ static_cast <int >(params.size ()),
673+ nullptr ,
674+ param_values.data (),
675+ nullptr ,
676+ nullptr ,
677+ 0
678+ );
679+
680+ if (pg_result == nullptr ) {
681+ last_error_ = " PostgreSQL execute prepared failed" ;
682+ logger_.error (" execute_prepared" , last_error_);
683+ return kcenon::common::error_info{
684+ static_cast <int >(database::error_code::query_failed),
685+ last_error_,
686+ " postgresql_backend"
687+ };
688+ }
689+
690+ ExecStatusType status = PQresultStatus (pg_result);
691+ bool success = (status == PGRES_COMMAND_OK ) || (status == PGRES_TUPLES_OK );
692+
693+ if (!success) {
694+ last_error_ = PQerrorMessage (static_cast <PGconn*>(connection_));
695+ logger_.error (" execute_prepared" , last_error_);
696+ PQclear (pg_result);
697+ return kcenon::common::error_info{
698+ static_cast <int >(database::error_code::query_failed),
699+ last_error_,
700+ " postgresql_backend"
701+ };
702+ }
703+
704+ PQclear (pg_result);
705+ last_error_.clear ();
706+ return kcenon::common::ok ();
707+ #else
708+ return database_backend::execute_prepared (query, params);
709+ #endif
710+ }
711+
393712kcenon::common::VoidResult postgresql_backend::begin_transaction ()
394713{
395714 if (!is_initialized ()) {
0 commit comments