Advertisement
Guest User

Untitled

a guest
Nov 17th, 2021
43
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 9.74 KB | None | 0 0
  1. // https://codereview.stackexchange.com/questions/220557/sql-odbc-bind-to-c-classes-row-wise
  2. // Refactored to automatic DTO generation
  3.  
  4. //template<typename...>
  5. //struct show;
  6. //template<auto...>
  7. //struct show_v;
  8. template <typename... T>
  9. inline constexpr bool dependent_false = false;
  10.  
  11. namespace details {
  12.  
  13. enum class bind_type
  14. {
  15.     defaultType,
  16.     timestamp,
  17. };
  18.  
  19. template <typename T, typename V>
  20. struct bind_desc
  21. {
  22.     // using declaration_type = V;
  23.     using type = T;
  24.     const std::size_t size/* = 1 / 0*/;
  25.     T V::* const value;
  26.     const bind_type odbcType;
  27. };
  28.  
  29. template<typename T>
  30. concept not_varsize = std::integral<T> || std::floating_point<T> || std::same_as<T, SQL_TIMESTAMP_STRUCT>;
  31.  
  32. #pragma region dtos
  33.  
  34. struct ts_dto
  35. {
  36.     SQL_TIMESTAMP_STRUCT buf;
  37.     SQLLEN len;
  38.     static constexpr SQLSMALLINT sql_c_type = SQL_C_TYPE_TIMESTAMP;
  39.     std::string toValue() const
  40.     {
  41.         return len == sizeof buf
  42.             ? TimestampToString(buf)
  43.             : (_ASSERT(len == SQL_NULL_DATA), std::string{});
  44.     }
  45. };
  46. struct ts_nn_dto
  47. {
  48.     SQL_TIMESTAMP_STRUCT buf;
  49.     static constexpr SQLSMALLINT sql_c_type = SQL_C_TYPE_TIMESTAMP;
  50.     std::string toValue() const
  51.     {
  52.         return TimestampToString(buf);
  53.     }
  54. };
  55.  
  56. template <std::size_t col_size>
  57. struct string_dto
  58. {
  59.     char buf[col_size];
  60.     SQLLEN len;
  61.     static constexpr SQLSMALLINT sql_c_type = SQL_C_CHAR;
  62.     std::string toValue() const
  63.     {
  64.         //         bufferSize  >=    gotlength     or   is null
  65.         _ASSERTE(col_size - 1u >= std::size_t(len) || len == SQL_NULL_DATA);
  66.  
  67.         std::string ret;
  68.         if (len != SQL_NULL_DATA)
  69.         {
  70.             _ASSERT(len >= 0);
  71.             // len - the length of the data before truncation because of the data buffer being too small
  72.             ret.assign(buf, std::clamp<std::size_t>(len, 0, col_size - 1u));
  73.         }
  74.  
  75.         return ret;
  76.     }
  77. };
  78.  
  79. struct long_nn_dto
  80. {
  81.     long buf;
  82.     static constexpr SQLSMALLINT sql_c_type = SQL_C_SLONG;
  83.     long toValue() const
  84.     { return buf; }
  85. };
  86. struct long_dto
  87. {
  88.     long buf;
  89.     SQLLEN len;
  90.     static constexpr SQLSMALLINT sql_c_type = SQL_C_SLONG;
  91.     long toValue() const
  92.     { return len == sizeof buf ? buf : (_ASSERT(len == SQL_NULL_DATA), 0); }
  93. };
  94.  
  95. template <typename T, std::size_t col_size, bind_type odbcType>
  96. consteval auto to_dto()
  97. {
  98.     if constexpr (std::is_same_v<T, std::string>)
  99.     {
  100.         if constexpr (odbcType == bind_type::timestamp)
  101.         {
  102.             if constexpr (col_size == 0)
  103.                 return ts_nn_dto{};
  104.             else
  105.                 return ts_dto{};
  106.         }
  107.         else
  108.             return string_dto<col_size + 1>{}; // + '\0'
  109.     }
  110.     else if constexpr (std::is_same_v<T, long>)
  111.     {
  112.         if constexpr (col_size == 0)
  113.             return long_nn_dto{};
  114.         else
  115.             return long_dto{};
  116.     }
  117.     else
  118.         static_assert(dependent_false<T>, "implement dto for T");
  119. }
  120.  
  121. template <bind_desc Col>
  122. using to_dto_t = decltype(to_dto<typename decltype(Col)::type, Col.size, Col.odbcType>());
  123. #pragma endregion
  124.  
  125. // TODO: inline this concept after msvc merge bug fix in 16.x branch
  126. // https://developercommunity.visualstudio.com/t/requires-expression-cannot-be-evaluated-as-bool-in/1204110
  127. template<auto x>
  128. concept has_len_member = requires { x.len; };
  129.  
  130. template <typename dto>
  131. bool BindCol(SQLHSTMT stmt, SQLUSMALLINT n, dto& dto_column)
  132. {
  133.     SQLPOINTER buf_ptr = [&]
  134.     {
  135.         if constexpr (std::is_array_v<decltype(dto_column.buf)>)
  136.             return dto_column.buf;
  137.         else
  138.             return &dto_column.buf;
  139.     }();
  140.  
  141.     SQLLEN* len_ptr = [&] {
  142.         if constexpr (details::has_len_member<dto_column>)
  143.             return &dto_column.len;
  144.         else
  145.             return nullptr;
  146.     }();
  147.  
  148.     const SQLRETURN rc =
  149.         ::SQLBindCol(stmt, n, dto::sql_c_type,
  150.             buf_ptr, sizeof dto_column.buf, len_ptr);
  151.     //show_v<sizeof dto_column.buf>{};
  152.     return rc == SQL_SUCCESS;
  153. }
  154. }
  155.  
  156. template<typename V, details::bind_desc... Cols>
  157. class OdbcBinder
  158. {
  159.     using DTOrow = tuple<details::to_dto_t<Cols>...>;
  160.  
  161.     template <std::size_t... I>
  162.     static V Convert(const DTOrow& t, std::index_sequence<I...>)
  163.     {
  164.         V value;
  165.         ((value.*(Cols.value) = get<I>(t).toValue()), ...);
  166.         return value;
  167.     }
  168.  
  169. public:
  170.     OdbcBinder() = default;
  171.     OdbcBinder(const OdbcBinder&) = delete;
  172.     OdbcBinder(OdbcBinder&&) = delete;
  173.  
  174.     std::vector<V> GetQueryResult(COdbc& odbc, std::string_view query, SQLULEN limit = 0, SQLULEN batchSize = 0)
  175.     {
  176.         //show<DTOrow>{};
  177.         constexpr SQLULEN default_batch_size = 32 * 1024 / sizeof(DTOrow); // 32 kb at least
  178.         if (batchSize == 0)
  179.             batchSize = limit != 0 && limit < default_batch_size ? limit : default_batch_size;
  180.  
  181.         //static_assert(std::is_trivial_v<DTOrow> && std::is_standard_layout_v<DTOrow>);
  182.         static_assert(std::is_nothrow_move_constructible_v<V>); // for fast std::vector reallocation
  183.  
  184.         // The SQL Server Native Client ODBC driver offers an optimization using rowsets to retrieve a whole result set quickly.
  185.         // To use this optimization, set the cursor attributes to their defaults (forward-only, read-only, rowset size = 1) at the time SQLExecDirect or SQLExecute is called.
  186.         // The SQL Server Native Client ODBC driver sets up a default result set. This is more efficient than server cursors when transferring results to the client without scrolling.
  187.         // After the statement has been executed, increase the rowset size and use either column-wise or row-wise binding.
  188.         // https://docs.microsoft.com/en-us/sql/relational-databases/native-client-odbc-cursors/properties/cursor-rowset-size?view=sql-server-ver15
  189.         // maybe TODO: MSSQL Fast Forward-Only Cursors
  190.         // https://docs.microsoft.com/en-us/sql/relational-databases/native-client-odbc-cursors/programming/fast-forward-only-cursors-odbc?view=sql-server-ver15
  191.  
  192.         autoHSTMT stmt{ odbc.GetHstmt() };
  193.         SQLRETURN rc;
  194.  
  195.         if (limit != 0)
  196.         {
  197.             rc = ::SQLSetStmtAttr(stmt, SQL_ATTR_MAX_ROWS, (SQLPOINTER)limit, SQL_IS_UINTEGER); _ASSERT(rc == 0);
  198.             if (rc != SQL_SUCCESS)
  199.                 odbc.LogLastError(stmt);
  200.         }
  201.  
  202.         rc = ::SQLExecDirect(stmt, (SQLCHAR*)query.data(), static_cast<SQLINTEGER>(query.size()));
  203.         if (!SQL_SUCCEEDED(rc))
  204.         {
  205.             throw ODBCException("OB: SQLExecDirect", rc, odbc.LogLastError(stmt));
  206.         }
  207.  
  208.         const std::unique_ptr<DTOrow[]> mem = std::make_unique_for_overwrite<DTOrow[]>(batchSize);
  209.         //std::span<DTOrow> rowArray(mem.get(), batchSize);
  210.  
  211.         SQLULEN numRowsFetched = 0;
  212.  
  213.         rc = ::SQLSetStmtAttr(stmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)batchSize, SQL_IS_UINTEGER); _ASSERT(rc == 0);
  214.         rc = ::SQLSetStmtAttr(stmt, SQL_ATTR_ROW_BIND_TYPE, (SQLPOINTER)sizeof(DTOrow), SQL_IS_UINTEGER); _ASSERT(rc == 0);
  215.         rc = ::SQLSetStmtAttr(stmt, SQL_ATTR_ROWS_FETCHED_PTR, &numRowsFetched, SQL_IS_POINTER); _ASSERT(rc == 0);
  216.  
  217.         // Bind columns
  218.         SQLUSMALLINT N = 0;
  219.         apply(
  220.             [&](auto&... dto_row) {
  221.                 if (!(details::BindCol(stmt, ++N, dto_row) && ...))
  222.                     throw ODBCException("OB: SQLBindCol", SQL_ERROR, odbc.LogLastError(stmt));
  223.             }, mem[0]);
  224.  
  225.         std::vector<V> results;
  226.         results.reserve(batchSize);
  227.  
  228.         constexpr auto is = std::make_index_sequence<sizeof...(Cols)>{};
  229.         do
  230.         {
  231.             while (SQL_SUCCEEDED(rc = ::SQLFetch(stmt)))
  232.             {
  233.                 if (rc == SQL_SUCCESS_WITH_INFO)
  234.                     odbc.LogLastError(stmt);
  235.  
  236.                 for (SQLUINTEGER i = 0; i < numRowsFetched; ++i)
  237.                 {
  238.                     results.emplace_back(Convert(mem[i], is));
  239.                 }
  240.             }
  241.             if (rc != SQL_NO_DATA)
  242.             {
  243.                 throw ODBCException("OB: SQLFetch", rc, odbc.LogLastError(stmt));
  244.             }
  245.         } while (SQL_SUCCEEDED(rc = ::SQLMoreResults(stmt)));
  246.         if (rc != SQL_NO_DATA)
  247.         {
  248.             throw ODBCException("OB: SQLMoreResults", rc, odbc.LogLastError(stmt));
  249.         }
  250.  
  251.         stmt.reset();
  252.  
  253.         if (results.capacity() > results.size() * 3)
  254.             results.shrink_to_fit();
  255.  
  256.         return results;
  257.     }
  258. };
  259.  
  260. template <typename V>
  261. consteval auto sized(std::string V::* ptr, std::size_t col_size)
  262. {
  263.     return details::bind_desc<std::string, V>{ col_size, ptr };
  264. }
  265.  
  266. template <details::not_varsize T, typename V>
  267. consteval auto not_null(T V::* ptr)
  268. {
  269.     return details::bind_desc<T, V>{ 0, ptr };
  270. }
  271. template <details::not_varsize T, typename V>
  272. consteval auto nullable(T V::* ptr)
  273. {
  274.     return details::bind_desc<T, V>{ 1, ptr };
  275. }
  276. template <typename V>
  277. consteval auto not_null_ts(std::string V::* ptr)
  278. {
  279.     return details::bind_desc<std::string, V>{ 0, ptr, details::bind_type::timestamp };
  280. }
  281. template <typename V>
  282. consteval auto nullable_ts(std::string V::* ptr)
  283. {
  284.     return details::bind_desc<std::string, V>{ 1, ptr, details::bind_type::timestamp };
  285. }
  286.  
  287.  
  288. /////////////////////////////////////////
  289.  
  290.  
  291. struct Incident
  292. {
  293.     long id;
  294.     std::string name;
  295.     std::string comments;
  296.     /* ... */
  297.     std::string timeSend;
  298.     std::string timeClosed;
  299.     long num;
  300. };
  301.  
  302. int main()
  303. {
  304.  
  305.     auto odbc = COdbc{};
  306.     OdbcBinder<Incident
  307.         , not_null(&Incident::id)
  308.         , sized(&Incident::name, 25)
  309.         , sized(&Incident::comments, 100)
  310.         , not_null_ts(&Incident::timeSend)
  311.         , nullable_ts(&Incident::timeClosed)
  312.         , nullable(&Incident::num)
  313.     > binder;
  314.  
  315.     auto vec = binder.GetQueryResult(odbc, "select ...");
  316.  
  317.     return 0;
  318. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement