From 796707d9e147fdf7208620bd3762edf62c6dd482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=81oskot?= Date: Thu, 18 Aug 2016 15:22:41 +0200 Subject: [PATCH] Add catalog::find_table_privileges function. Add catalog::table_privileges result set. It is convenient wrapper for ODBC API call SQLTablePrivileges and its result set. --- src/nanodbc.cpp | 83 ++++++++++++++++++++++++++++++++++++++++ src/nanodbc.h | 39 ++++++++++++++++++- test/base_test_fixture.h | 48 +++++++++++++++++++++++ test/mssql_test.cpp | 5 +++ test/mysql_test.cpp | 2 + test/postgresql_test.cpp | 8 ++++ test/sqlite_test.cpp | 6 +++ 7 files changed, 189 insertions(+), 2 deletions(-) diff --git a/src/nanodbc.cpp b/src/nanodbc.cpp index ff54074ff..0213c5abf 100644 --- a/src/nanodbc.cpp +++ b/src/nanodbc.cpp @@ -3702,6 +3702,58 @@ string_type catalog::tables::table_remarks() const return result_.get(4, string_type()); } +catalog::table_privileges::table_privileges(result& find_result) + : result_(find_result) +{ +} + +bool catalog::table_privileges::next() +{ + return result_.next(); +} + +string_type catalog::table_privileges::table_catalog() const +{ + // TABLE_CAT might be NULL + return result_.get(0, string_type()); +} + +string_type catalog::table_privileges::table_schema() const +{ + // TABLE_SCHEM might be NULL + return result_.get(1, string_type()); +} + +string_type catalog::table_privileges::table_name() const +{ + // TABLE_NAME column is never NULL + return result_.get(2); +} + +string_type catalog::table_privileges::grantor() const +{ + // GRANTOR might be NULL + return result_.get(3, string_type()); +} + +string_type catalog::table_privileges::grantee() const +{ + // GRANTEE column is never NULL + return result_.get(4); +} + +string_type catalog::table_privileges::privilege() const +{ + // PRIVILEGE column is never NULL + return result_.get(5); +} + +string_type catalog::table_privileges::is_grantable() const +{ + // IS_GRANTABLE might be NULL + return result_.get(6, string_type()); +} + catalog::primary_keys::primary_keys(result& find_result) : result_(find_result) { @@ -3905,6 +3957,37 @@ catalog::tables catalog::find_tables( return catalog::tables(find_result); } +catalog::table_privileges catalog::find_table_privileges( + const string_type& catalog, + const string_type& table, + const string_type& schema) +{ + // Passing a null pointer to a search pattern argument does not + // constrain the search for that argument; that is, a null pointer and + // the search pattern % (any characters) are equivalent. + // However, a zero-length search pattern - that is, a valid pointer to + // a string of length zero - matches only the empty string (""). + // See https://msdn.microsoft.com/en-us/library/ms710171.aspx + + statement stmt(conn_); + RETCODE rc; + NANODBC_CALL_RC( + NANODBC_FUNC(SQLTablePrivileges), + rc, + stmt.native_statement_handle(), + (NANODBC_SQLCHAR*)(catalog.empty() ? nullptr : catalog.c_str()), + (catalog.empty() ? 0 : SQL_NTS), + (NANODBC_SQLCHAR*)(schema.empty() ? nullptr : schema.c_str()), + (schema.empty() ? 0 : SQL_NTS), + (NANODBC_SQLCHAR*)(table.empty() ? nullptr : table.c_str()), + (table.empty() ? 0 : SQL_NTS)); + if (!success(rc)) + NANODBC_THROW_DATABASE_ERROR(stmt.native_statement_handle(), SQL_HANDLE_STMT); + + result find_result(stmt, 1); + return catalog::table_privileges(find_result); +} + catalog::columns catalog::find_columns( const string_type& column, const string_type& table, diff --git a/src/nanodbc.h b/src/nanodbc.h index ce69e007a..c5609ba65 100644 --- a/src/nanodbc.h +++ b/src/nanodbc.h @@ -1487,8 +1487,6 @@ class catalog /// \note MSDN: This column returns a zero-length string if nullability is unknown. /// ISO rules are followed to determine nullability. /// An ISO SQL-compliant DBMS cannot return an empty string. - /// - /// TODO: Translate "YES","NO", strings to IsNullable enum? string_type is_nullable() const; private: @@ -1522,6 +1520,26 @@ class catalog result result_; }; + /// \brief Result set for a list of tables and the privileges associated with each table. + class table_privileges + { + public: + bool next(); ///< Move to the next result in the result set + string_type table_catalog() const; ///< Fetch table catalog. + string_type table_schema() const; ///< Fetch table schema. + string_type table_name() const; ///< Fetch table name. + string_type grantor() const; ///< Fetch name of user who granted the privilege. + string_type grantee() const; ///< Fetch name of user whom the privilege was granted. + string_type privilege() const; ///< Fetch the table privilege. + /// Fetch indicator whether the grantee is permitted to grant the privilege to other users. + string_type is_grantable() const; + + private: + friend class nanodbc::catalog; + table_privileges(result& find_result); + result result_; + }; + /// \brief Creates catalog operating on database accessible through the specified connection. catalog(connection& conn); @@ -1540,6 +1558,23 @@ class catalog const string_type& schema = string_type(), const string_type& catalog = string_type()); + /// \brief Creates result set with tables and the privileges associated with each table. + /// Tables information is obtained by executing `SQLTablePrivileges` function within + /// scope of the connected database accessible with the specified connection. + /// Since this function is implemented in terms of the `SQLTablePrivileges`s, it returns + /// result set ordered by TABLE_CAT, TABLE_SCHEM, TABLE_NAME, PRIVILEGE, and GRANTEE. + /// + /// \param catalog The table catalog. It cannot contain a string search pattern. + /// \param schema String search pattern for schema names, treated as the Pattern Value Arguments. + /// \param table String search pattern for table names, treated as the Pattern Value Arguments. + /// + /// \note Due to the fact catalog cannot is not the Pattern Value Argument, + /// order of parameters is different than in the other catalog look-up functions. + catalog::table_privileges find_table_privileges( + const string_type& catalog, + const string_type& table = string_type(), + const string_type& schema = string_type()); + /// \brief Creates result set with columns in one or more tables. /// /// Columns information is obtained by executing `SQLColumns` function within diff --git a/test/base_test_fixture.h b/test/base_test_fixture.h index f969c5fed..b3569d645 100644 --- a/test/base_test_fixture.h +++ b/test/base_test_fixture.h @@ -864,6 +864,54 @@ struct base_test_fixture } } + void catalog_table_privileges_test() + { + nanodbc::connection connection = connect(); + nanodbc::catalog catalog(connection); + + // create several tables + create_table( + connection, NANODBC_TEXT("catalog_table_privileges_test"), NANODBC_TEXT("i int")); + + // Check we can iterate over any tables + { + auto tables = catalog.find_table_privileges(NANODBC_TEXT("")); + long count = 0; + while (tables.next()) + { + // These values must not be NULL (returned as empty string) + REQUIRE(!tables.table_name().empty()); + REQUIRE(!tables.privilege().empty()); + count++; + } + REQUIRE(count > 0); + } + + // Check we can find a particular table + { + auto tables = catalog.find_table_privileges( + NANODBC_TEXT(""), NANODBC_TEXT("catalog_table_privileges_test")); + long count = 0; + std::set privileges; + while (tables.next()) + { + // These two values must not be NULL (returned as empty string) + REQUIRE(tables.table_name() == NANODBC_TEXT("catalog_table_privileges_test")); + privileges.insert(tables.privilege()); + count++; + } + REQUIRE(count > 0); + + // verify expected privileges + REQUIRE(!privileges.empty()); + REQUIRE(privileges.count(NANODBC_TEXT("SELECT"))); + REQUIRE(privileges.count(NANODBC_TEXT("INSERT"))); + REQUIRE(privileges.count(NANODBC_TEXT("UPDATE"))); + REQUIRE(privileges.count(NANODBC_TEXT("DELETE"))); + // there can be more + } + } + void column_descriptor_test() { auto connection = connect(); diff --git a/test/mssql_test.cpp b/test/mssql_test.cpp index b46f758bc..12af1a180 100644 --- a/test/mssql_test.cpp +++ b/test/mssql_test.cpp @@ -282,6 +282,11 @@ TEST_CASE_METHOD(mssql_fixture, "catalog_tables_test", "[mssql][catalog][tables] catalog_tables_test(); } +TEST_CASE_METHOD(mssql_fixture, "catalog_table_privileges_test", "[mssql][catalog][tables]") +{ + catalog_table_privileges_test(); +} + TEST_CASE_METHOD(mssql_fixture, "column_descriptor_test", "[mssql][columns]") { column_descriptor_test(); diff --git a/test/mysql_test.cpp b/test/mysql_test.cpp index 8c0fb52d0..1e93f8a66 100644 --- a/test/mysql_test.cpp +++ b/test/mysql_test.cpp @@ -99,6 +99,8 @@ TEST_CASE_METHOD(mysql_fixture, "catalog_tables_test", "[mysql][catalog][tables] catalog_tables_test(); } +// TODO: Add catalog_table_privileges_test - SQLTablePrivileges returns empty result set + TEST_CASE_METHOD(mysql_fixture, "column_descriptor_test", "[mysql][columns]") { column_descriptor_test(); diff --git a/test/postgresql_test.cpp b/test/postgresql_test.cpp index 2bf025a3e..568f93ff5 100644 --- a/test/postgresql_test.cpp +++ b/test/postgresql_test.cpp @@ -58,6 +58,14 @@ TEST_CASE_METHOD(postgresql_fixture, "catalog_tables_test", "[postgresql][catalo catalog_tables_test(); } +TEST_CASE_METHOD( + postgresql_fixture, + "catalog_table_privileges_test", + "[postgresql][catalog][tables]") +{ + catalog_table_privileges_test(); +} + TEST_CASE_METHOD(postgresql_fixture, "column_descriptor_test", "[postgresql][columns]") { column_descriptor_test(); diff --git a/test/sqlite_test.cpp b/test/sqlite_test.cpp index 27272d466..14d2e64f5 100644 --- a/test/sqlite_test.cpp +++ b/test/sqlite_test.cpp @@ -153,6 +153,12 @@ TEST_CASE_METHOD(sqlite_fixture, "catalog_tables_test", "[sqlite][catalog][table catalog_tables_test(); } +TEST_CASE_METHOD(sqlite_fixture, "catalog_table_privileges_test", "[sqlite][catalog][tables]") +{ + before_catalog_test(); + catalog_table_privileges_test(); +} + TEST_CASE_METHOD(sqlite_fixture, "column_descriptor_test", "[sqlite][columns]") { column_descriptor_test();