From 21f12a8a98f3769b805136e2d3d7bfa6adca1e64 Mon Sep 17 00:00:00 2001 From: Tomasz Sowa Date: Sun, 16 Jul 2023 04:03:03 +0200 Subject: [PATCH] add support for declared cursors Declare a cursor with the Finder::declare_cursor() method and next get the cursor with get_cursor(): morm::Cursor cursor = finder. declare_cursor("mycursorname"). select(). raw("ORDER BY column_name ASC"). get_cursor(); The cursor now can be used with fetch.*() methods and then get() or get_list(): MyObject myobject = cursor.fetch_next().get(); std::list myobjects = cursor.fetch_forward_count(10).get_list(); --- src/cursor.h | 269 ++++++++++++++++++++++++++++++++++-- src/dbconnector.cpp | 15 +- src/dbconnector.h | 6 +- src/dbexpression.cpp | 62 +++++++++ src/dbexpression.h | 11 ++ src/finder.h | 105 +++++++++----- src/postgresqlconnector.cpp | 54 +++----- src/postgresqlconnector.h | 6 +- 8 files changed, 448 insertions(+), 80 deletions(-) diff --git a/src/cursor.h b/src/cursor.h index a9ae900..f0ad6e7 100644 --- a/src/cursor.h +++ b/src/cursor.h @@ -63,9 +63,11 @@ public: has_autogenerated_select = c.has_autogenerated_select; use_table_prefix_for_fetching = c.use_table_prefix_for_fetching; query_result = c.query_result; - select_status = c.select_status; + last_query_status = c.last_query_status; select_flags = c.select_flags; rows_counter = c.rows_counter; + cursor_name = c.cursor_name; + scroll_cursor = c.scroll_cursor; if( query_result ) { @@ -107,9 +109,11 @@ public: cursor_helper.clear(); finder_helper.clear(); query_result = nullptr; - select_status = false; + last_query_status = false; select_flags = Select::default_type; rows_counter = 0; + cursor_name.clear(); + scroll_cursor = false; } @@ -131,9 +135,9 @@ public: } - virtual void set_select_status(bool select_status) + virtual void set_last_query_status(bool status) { - this->select_status = select_status; + this->last_query_status = status; } @@ -171,7 +175,7 @@ public: { bool has = false; - if( model_connector && query_result && query_result->has_db_result() && select_status ) + if( model_connector && query_result && query_result->has_db_result() && last_query_status ) { has = query_result->cur_row < query_result->result_rows; } @@ -234,7 +238,7 @@ public: } result.before_select(); - res = select_status; + res = last_query_status; if( res ) { @@ -326,6 +330,136 @@ public: } + virtual void set_cursor_name(pt::TextStream & cursor_name) + { + this->cursor_name = cursor_name; + } + + + virtual void set_scroll_cursor(bool scroll_cursor) + { + this->scroll_cursor = scroll_cursor; + } + + + virtual Cursor & fetch(const char * fetch_str) + { + last_query_status = false; + rows_counter = 0; + + if( query_result ) + { + query_result->clear(); + + if( model_connector ) + { + DbConnector * db_connector = model_connector->get_db_connector(); + + if( db_connector ) + { + bool status = db_connector->query_select(fetch_str, *query_result); + last_query_status = status; + } + } + } + + return *this; + } + + + virtual Cursor & fetch(pt::TextStream & str) + { + if( str.size() < 256 ) + { + char str_buf[256]; + + if( str.to_str(str_buf, sizeof(str_buf) / sizeof(char)) ) + { + fetch(str_buf); + } + } + else + { + put_cursor_fetch_query_too_long(); + } + + return *this; + } + + + virtual Cursor & fetch(pt::WTextStream & str) + { + if( str.size() < 256 ) + { + char str_buf[256]; + + if( str.to_str(str_buf, sizeof(str_buf) / sizeof(char)) ) + { + fetch(str_buf); + } + } + else + { + put_cursor_fetch_query_too_long(); + } + + return *this; + } + + + virtual Cursor & fetch_next() + { + return fetch(&DbExpression::prepare_fetch_next_query); + } + + + virtual Cursor & fetch_prior() + { + return fetch(&DbExpression::prepare_fetch_prior_query); + } + + + virtual Cursor & fetch_first() + { + return fetch(&DbExpression::prepare_fetch_first_query); + } + + + virtual Cursor & fetch_last() + { + return fetch(&DbExpression::prepare_fetch_last_query); + } + + + virtual Cursor & fetch_absolute(long position) + { + return fetch(&DbExpression::prepare_fetch_absotule_query, position); + } + + + virtual Cursor & fetch_relative(long position) + { + return fetch(&DbExpression::prepare_fetch_relative_query, position); + } + + + virtual Cursor & fetch_forward_count(size_t len) + { + return fetch(&DbExpression::prepare_fetch_forward_count_query, len); + } + + + virtual Cursor & fetch_backward_count(size_t len) + { + return fetch(&DbExpression::prepare_fetch_backward_count_query, len); + } + + + virtual Cursor & fetch_all() + { + return fetch(&DbExpression::prepare_fetch_all_query); + } + protected: @@ -336,11 +470,98 @@ protected: CursorHelper cursor_helper; FinderHelper finder_helper; // may CursorHelper and FinderHelper should be one class? QueryResult * query_result; - bool select_status; + bool last_query_status; Select select_flags; size_t rows_counter; std::wstring rows_counter_column_name; + pt::TextStream cursor_name; + bool scroll_cursor; + typedef void (DbExpression::*prepare_fetch_method_type)(const pt::TextStream & cursor_name, pt::TextStream & out_stream); + typedef void (DbExpression::*prepare_fetch_long_count_method_type)(const pt::TextStream & cursor_name, long count, pt::TextStream & out_stream); + typedef void (DbExpression::*prepare_fetch_size_count_method_type)(const pt::TextStream & cursor_name, size_t count, pt::TextStream & out_stream); + + + DbExpression * get_db_expression() + { + if( model_connector ) + { + DbConnector * db_connector = model_connector->get_db_connector(); + + if( db_connector ) + { + return db_connector->get_expression(); + } + } + + return nullptr; + } + + + virtual Cursor & fetch(prepare_fetch_method_type prepare_fetch_method) + { + if( !cursor_name.empty() ) + { + DbExpression * db_expression = get_db_expression(); + + if( db_expression ) + { + pt::TextStream str; + (db_expression->*prepare_fetch_method)(cursor_name, str); + fetch(str); + } + } + else + { + put_cursor_not_declared_log(); + } + + return *this; + } + + + virtual Cursor & fetch(prepare_fetch_long_count_method_type prepare_fetch_method, long count) + { + if( !cursor_name.empty() ) + { + DbExpression * db_expression = get_db_expression(); + + if( db_expression ) + { + pt::TextStream str; + (db_expression->*prepare_fetch_method)(cursor_name, count, str); + fetch(str); + } + } + else + { + put_cursor_not_declared_log(); + } + + return *this; + } + + + virtual Cursor & fetch(prepare_fetch_size_count_method_type prepare_fetch_method, size_t count) + { + if( !cursor_name.empty() ) + { + DbExpression * db_expression = get_db_expression(); + + if( db_expression ) + { + pt::TextStream str; + (db_expression->*prepare_fetch_method)(cursor_name, count, str); + fetch(str); + } + } + else + { + put_cursor_not_declared_log(); + } + + return *this; + } template @@ -359,7 +580,7 @@ protected: if( db_connector ) { - res = select_status; + res = last_query_status; try { @@ -448,6 +669,38 @@ protected: } + pt::Log * get_logger() + { + if( model_connector ) + { + return model_connector->get_logger(); + } + + return nullptr; + } + + + void put_log(pt::Log::Manipulators level, const char * msg) + { + pt::Log * plog = get_logger(); + + if( plog ) + { + (*plog) << level << msg << pt::Log::logend; + } + } + + + void put_cursor_fetch_query_too_long() + { + put_log(pt::Log::log2, "Morm: error: a fetch query should be less than 256 characters long"); + } + + + void put_cursor_not_declared_log() + { + put_log(pt::Log::log2, "Morm: you cannot use fetch* methods, a cursor has not been declared, use Finder::declare_cursor(...) to declare a cursor"); + } }; diff --git a/src/dbconnector.cpp b/src/dbconnector.cpp index cbfc695..bd0328c 100644 --- a/src/dbconnector.cpp +++ b/src/dbconnector.cpp @@ -5,7 +5,7 @@ */ /* - * Copyright (c) 2018-2022, Tomasz Sowa + * Copyright (c) 2018-2023, Tomasz Sowa * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -144,6 +144,11 @@ bool DbConnector::query_remove(const char * query_str, QueryResult & query_resul return query(query_str, query_result); } +bool DbConnector::query_declare_cursor(const char * query_str, QueryResult & query_result) +{ + return query(query_str, query_result); +} + bool DbConnector::query_select(const pt::TextStream & stream, QueryResult & query_result) @@ -166,6 +171,12 @@ bool DbConnector::query_remove(const pt::TextStream & stream, QueryResult & quer return query(stream, query_result); } +bool DbConnector::query_declare_cursor(const pt::TextStream & stream, QueryResult & query_result) +{ + return query(stream, query_result); +} + + bool DbConnector::begin() { @@ -910,4 +921,6 @@ const char * DbConnector::query_last_sequence(const wchar_t * sequence_table_nam return nullptr; } + + } diff --git a/src/dbconnector.h b/src/dbconnector.h index dd2a667..463373a 100644 --- a/src/dbconnector.h +++ b/src/dbconnector.h @@ -5,7 +5,7 @@ */ /* - * Copyright (c) 2018-2022, Tomasz Sowa + * Copyright (c) 2018-2023, Tomasz Sowa * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -86,11 +86,14 @@ public: virtual bool query_update(const char * query_str, QueryResult & query_result); virtual bool query_insert(const char * query_str, QueryResult & query_result); virtual bool query_remove(const char * query_str, QueryResult & query_result); + virtual bool query_declare_cursor(const char * query_str, QueryResult & query_result); virtual bool query_select(const pt::TextStream & stream, QueryResult & query_result); virtual bool query_update(const pt::TextStream & stream, QueryResult & query_result); virtual bool query_insert(const pt::TextStream & stream, QueryResult & query_result); virtual bool query_remove(const pt::TextStream & stream, QueryResult & query_result); + virtual bool query_declare_cursor(const pt::TextStream & stream, QueryResult & query_result); + /* * create a new transaction @@ -208,7 +211,6 @@ protected: virtual bool rollback_one_transaction(size_t index); virtual bool commit_one_transaction(size_t index); - private: unsigned int unescape_hex_char_part(char hex); diff --git a/src/dbexpression.cpp b/src/dbexpression.cpp index 4e2b0ed..f122a56 100644 --- a/src/dbexpression.cpp +++ b/src/dbexpression.cpp @@ -397,7 +397,69 @@ void DbExpression::add_rows_counter_column(Model & model) +void DbExpression::prepare_declare_cursor_query(const pt::TextStream & cursor_name, bool scroll_cursor, pt::TextStream & out_stream) +{ + out_stream << "DECLARE " << cursor_name; + if( scroll_cursor ) + out_stream << " SCROLL"; + + out_stream << " CURSOR FOR "; +} + + +void DbExpression::prepare_fetch_next_query(const pt::TextStream & cursor_name, pt::TextStream & out_stream) +{ + out_stream << "FETCH NEXT FROM " << cursor_name; +} + + +void DbExpression::prepare_fetch_prior_query(const pt::TextStream & cursor_name, pt::TextStream & out_stream) +{ + out_stream << "FETCH PRIOR FROM " << cursor_name; +} + + +void DbExpression::prepare_fetch_first_query(const pt::TextStream & cursor_name, pt::TextStream & out_stream) +{ + out_stream << "FETCH FIRST FROM " << cursor_name; +} + + +void DbExpression::prepare_fetch_last_query(const pt::TextStream & cursor_name, pt::TextStream & out_stream) +{ + out_stream << "FETCH LAST FROM " << cursor_name; +} + + +void DbExpression::prepare_fetch_absotule_query(const pt::TextStream & cursor_name, long position, pt::TextStream & out_stream) +{ + out_stream << "FETCH ABSOLUTE " << position << " FROM " << cursor_name; +} + + +void DbExpression::prepare_fetch_relative_query(const pt::TextStream & cursor_name, long position, pt::TextStream & out_stream) +{ + out_stream << "FETCH RELATIVE " << position << " FROM " << cursor_name; +} + + +void DbExpression::prepare_fetch_forward_count_query(const pt::TextStream & cursor_name, size_t len, pt::TextStream & out_stream) +{ + out_stream << "FETCH FORWARD " << len << " FROM " << cursor_name; +} + + +void DbExpression::prepare_fetch_backward_count_query(const pt::TextStream & cursor_name, size_t len, pt::TextStream & out_stream) +{ + out_stream << "FETCH BACKWARD " << len << " FROM " << cursor_name; +} + + +void DbExpression::prepare_fetch_all_query(const pt::TextStream & cursor_name, pt::TextStream & out_stream) +{ + out_stream << "FETCH ALL FROM " << cursor_name; +} } diff --git a/src/dbexpression.h b/src/dbexpression.h index 3990020..af4c572 100644 --- a/src/dbexpression.h +++ b/src/dbexpression.h @@ -78,6 +78,17 @@ public: virtual void generate_rows_counter_column_name(ModelEnv & model_env, pt::TextStream & str); + virtual void prepare_declare_cursor_query(const pt::TextStream & cursor_name, bool scroll_cursor, pt::TextStream & out_stream); + virtual void prepare_fetch_next_query(const pt::TextStream & cursor_name, pt::TextStream & out_stream); + virtual void prepare_fetch_prior_query(const pt::TextStream & cursor_name, pt::TextStream & out_stream); + virtual void prepare_fetch_first_query(const pt::TextStream & cursor_name, pt::TextStream & out_stream); + virtual void prepare_fetch_last_query(const pt::TextStream & cursor_name, pt::TextStream & out_stream); + virtual void prepare_fetch_absotule_query(const pt::TextStream & cursor_name, long position, pt::TextStream & out_stream); + virtual void prepare_fetch_relative_query(const pt::TextStream & cursor_name, long position, pt::TextStream & out_stream); + virtual void prepare_fetch_forward_count_query(const pt::TextStream & cursor_name, size_t len, pt::TextStream & out_stream); + virtual void prepare_fetch_backward_count_query(const pt::TextStream & cursor_name, size_t len, pt::TextStream & out_stream); + virtual void prepare_fetch_all_query(const pt::TextStream & cursor_name, pt::TextStream & out_stream); + protected: diff --git a/src/finder.h b/src/finder.h index c7ccad6..9c1cb46 100644 --- a/src/finder.h +++ b/src/finder.h @@ -58,65 +58,42 @@ public: Finder() { - model_connector = nullptr; - out_stream = nullptr; - db_expression = nullptr; - was_query_error = false; - model_data = nullptr; - rows_counter_name = nullptr; + initialize(); } Finder(pt::TextStream & out_stream) { + initialize(); this->out_stream = &out_stream; - this->model_connector = nullptr; - db_expression = nullptr; - was_query_error = false; - model_data = nullptr; - rows_counter_name = nullptr; } Finder(ModelConnector & model_connector) { + initialize(); this->model_connector = &model_connector; - out_stream = nullptr; - db_expression = nullptr; - was_query_error = false; - model_data = nullptr; - rows_counter_name = nullptr; set_out_stream(); } Finder(ModelConnector * model_connector) { + initialize(); this->model_connector = model_connector; - out_stream = nullptr; - db_expression = nullptr; - was_query_error = false; - model_data = nullptr; - rows_counter_name = nullptr; set_out_stream(); } Finder(pt::TextStream & out_stream, ModelConnector & model_connector) { + initialize(); this->out_stream = &out_stream; this->model_connector = &model_connector; - db_expression = nullptr; - was_query_error = false; - model_data = nullptr; - rows_counter_name = nullptr; } Finder(pt::TextStream & out_stream, ModelConnector * model_connector) { + initialize(); this->out_stream = &out_stream; this->model_connector = model_connector; - db_expression = nullptr; - was_query_error = false; - model_data = nullptr; - rows_counter_name = nullptr; } Finder & set_connector(ModelConnector * model_connector) @@ -159,13 +136,29 @@ public: Finder & set_rows_counter_name(const wchar_t * rows_counter_name) { - this->rows_counter_name = rows_counter_name; + this->rows_counter_name = rows_counter_name; // may use a pt::TextStream? return *this; } Finder & set_rows_counter_name(const std::wstring & rows_counter_name) { - this->rows_counter_name = &rows_counter_name; + this->rows_counter_name = rows_counter_name.c_str(); + return *this; + } + + Finder & declare_cursor(const char * cursor_name, bool scroll_cursor = false) + { + this->cursor_name.clear(); + this->cursor_name << cursor_name; + this->scroll_cursor = scroll_cursor; + return *this; + } + + Finder & declare_cursor(const wchar_t * cursor_name, bool scroll_cursor = false) + { + this->cursor_name.clear(); + this->cursor_name << cursor_name; + this->scroll_cursor = scroll_cursor; return *this; } @@ -185,6 +178,14 @@ public: { start_new_select(select_flags); + if( !cursor_name.empty() ) + { + if( out_stream && db_expression ) + { + db_expression->prepare_declare_cursor_query(cursor_name, scroll_cursor, *out_stream); + } + } + if( !select_flags.is_no_auto_generated_columns() ) { generate_standard_select(select_flags); @@ -196,6 +197,17 @@ public: /* * used if we are not using auto generated selects + * + * + * a sample how to get the id field, assuming the table name is 'mymodel'. + * mymodel = finder. + * select(morm::Select::no_auto_generated_columns). + * use_table_prefix(true). + * raw("SELECT id AS \"mymodel.id\""). + * raw("FROM mymodel"). + * raw("WHERE id = 123"). + * get(); + * */ Finder & use_table_prefix(bool use_table_prefix_for_fetching) { @@ -1193,6 +1205,8 @@ public: cursor.set_has_autogenerated_select(model_env.has_autogenerated_select); cursor.use_table_prefix(use_table_prefix_for_fetching); cursor.set_select_flags(model_env.select_flags); + cursor.set_cursor_name(cursor_name); + cursor.set_scroll_cursor(scroll_cursor); if( model_env.select_flags.is_with_rows_counter() ) { @@ -1215,11 +1229,22 @@ public: cursor.set_query_result(query_result); query_result->references_count = 1; - bool status = db_connector->query_select(*out_stream, *query_result); - cursor.set_select_status(status); + if( cursor_name.empty() ) + { + bool status = db_connector->query_select(*out_stream, *query_result); + cursor.set_last_query_status(status); + } + else + { + bool status = db_connector->query_declare_cursor(*out_stream, *query_result); + cursor.set_last_query_status(status); + } } } + if( !cursor_name.empty() ) + cursor_name.clear(); + return cursor; } @@ -1439,8 +1464,22 @@ private: ModelData * model_data; bool use_table_prefix_for_fetching; const wchar_t * rows_counter_name; + pt::TextStream cursor_name; + bool scroll_cursor; + void initialize() + { + out_stream = nullptr; + model_connector = nullptr; + db_expression = nullptr; + was_query_error = false; + model_data = nullptr; + use_table_prefix_for_fetching = false; + rows_counter_name = nullptr; + scroll_cursor = false; + } + void set_db_expression() { if( model_connector ) diff --git a/src/postgresqlconnector.cpp b/src/postgresqlconnector.cpp index fc84f93..9555b39 100644 --- a/src/postgresqlconnector.cpp +++ b/src/postgresqlconnector.cpp @@ -5,7 +5,7 @@ */ /* - * Copyright (c) 2018-2022, Tomasz Sowa + * Copyright (c) 2018-2023, Tomasz Sowa * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -207,14 +207,14 @@ bool PostgreSQLConnector::query(const std::string & query_str, QueryResult & que } -bool PostgreSQLConnector::query_select(const char * query_str, QueryResult & query_result) +bool PostgreSQLConnector::query_command(const char * query_str, QueryResult & query_result, ExecStatusType expected_status) { PostgreSQLQueryResult * psql_result = dynamic_cast(&query_result); bool result = false; if( psql_result ) { - result = (do_query(query_str, psql_result) && psql_result->psql_status == PGRES_TUPLES_OK); + result = (do_query(query_str, psql_result) && psql_result->psql_status == expected_status); psql_result->status = result; } @@ -222,48 +222,29 @@ bool PostgreSQLConnector::query_select(const char * query_str, QueryResult & que } +bool PostgreSQLConnector::query_select(const char * query_str, QueryResult & query_result) +{ + return query_command(query_str, query_result, PGRES_TUPLES_OK); +} + bool PostgreSQLConnector::query_update(const char * query_str, QueryResult & query_result) { - PostgreSQLQueryResult * psql_result = dynamic_cast(&query_result); - bool result = false; - - if( psql_result ) - { - result = (do_query(query_str, psql_result) && psql_result->psql_status == PGRES_COMMAND_OK); - psql_result->status = result; - } - - return result; + return query_command(query_str, query_result, PGRES_COMMAND_OK); } - bool PostgreSQLConnector::query_insert(const char * query_str, QueryResult & query_result) { - PostgreSQLQueryResult * psql_result = dynamic_cast(&query_result); - bool result = false; - - if( psql_result ) - { - result = (do_query(query_str, psql_result) && psql_result->psql_status == PGRES_COMMAND_OK); - psql_result->status = result; - } - - return result; + return query_command(query_str, query_result, PGRES_COMMAND_OK); } - bool PostgreSQLConnector::query_remove(const char * query_str, QueryResult & query_result) { - PostgreSQLQueryResult * psql_result = dynamic_cast(&query_result); - bool result = false; + return query_command(query_str, query_result, PGRES_COMMAND_OK); +} - if( psql_result ) - { - result = (do_query(query_str, psql_result) && psql_result->psql_status == PGRES_COMMAND_OK); - psql_result->status = result; - } - - return result; +bool PostgreSQLConnector::query_declare_cursor(const char * query_str, QueryResult & query_result) +{ + return query_command(query_str, query_result, PGRES_COMMAND_OK); } @@ -291,6 +272,11 @@ bool PostgreSQLConnector::query_remove(const pt::TextStream & stream, QueryResul return query_remove(query_str.c_str(), query_result); } +bool PostgreSQLConnector::query_declare_cursor(const pt::TextStream & stream, QueryResult & query_result) +{ + stream.to_str(query_str); + return query_declare_cursor(query_str.c_str(), query_result); +} diff --git a/src/postgresqlconnector.h b/src/postgresqlconnector.h index 11eda2b..d0c1e97 100644 --- a/src/postgresqlconnector.h +++ b/src/postgresqlconnector.h @@ -5,7 +5,7 @@ */ /* - * Copyright (c) 2018-2022, Tomasz Sowa + * Copyright (c) 2018-2023, Tomasz Sowa * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -63,12 +63,13 @@ public: bool query_update(const char * query_str, QueryResult & query_result); bool query_insert(const char * query_str, QueryResult & query_result); bool query_remove(const char * query_str, QueryResult & query_result); + bool query_declare_cursor(const char * query_str, QueryResult & query_result); bool query_select(const pt::TextStream & stream, QueryResult & query_result); bool query_update(const pt::TextStream & stream, QueryResult & query_result); bool query_insert(const pt::TextStream & stream, QueryResult & query_result); bool query_remove(const pt::TextStream & stream, QueryResult & query_result); - + bool query_declare_cursor(const pt::TextStream & stream, QueryResult & query_result); /* * https://www.postgresql.org/docs/14/libpq-connect.html#LIBPQ-CONNSTRING @@ -145,6 +146,7 @@ protected: virtual void connect(); virtual bool do_query(const char * query_str, PostgreSQLQueryResult * psql_result); + virtual bool query_command(const char * query_str, QueryResult & query_result, ExecStatusType expected_status); virtual void allocate_default_expression(); virtual void overwrite(pt::TextStream & stream); virtual const char * query_last_sequence(const wchar_t * sequence_table_name);