Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // https://codereview.stackexchange.com/questions/220557/sql-odbc-bind-to-c-classes-row-wise
- // Refactored to automatic DTO generation
- //template<typename...>
- //struct show;
- //template<auto...>
- //struct show_v;
- template <typename... T>
- inline constexpr bool dependent_false = false;
- namespace details {
- enum class bind_type
- {
- defaultType,
- timestamp,
- };
- template <typename T, typename V>
- struct bind_desc
- {
- // using declaration_type = V;
- using type = T;
- const std::size_t size/* = 1 / 0*/;
- T V::* const value;
- const bind_type odbcType;
- };
- template<typename T>
- concept not_varsize = std::integral<T> || std::floating_point<T> || std::same_as<T, SQL_TIMESTAMP_STRUCT>;
- #pragma region dtos
- struct ts_dto
- {
- SQL_TIMESTAMP_STRUCT buf;
- SQLLEN len;
- static constexpr SQLSMALLINT sql_c_type = SQL_C_TYPE_TIMESTAMP;
- std::string toValue() const
- {
- return len == sizeof buf
- ? TimestampToString(buf)
- : (_ASSERT(len == SQL_NULL_DATA), std::string{});
- }
- };
- struct ts_nn_dto
- {
- SQL_TIMESTAMP_STRUCT buf;
- static constexpr SQLSMALLINT sql_c_type = SQL_C_TYPE_TIMESTAMP;
- std::string toValue() const
- {
- return TimestampToString(buf);
- }
- };
- template <std::size_t col_size>
- struct string_dto
- {
- char buf[col_size];
- SQLLEN len;
- static constexpr SQLSMALLINT sql_c_type = SQL_C_CHAR;
- std::string toValue() const
- {
- // bufferSize >= gotlength or is null
- _ASSERTE(col_size - 1u >= std::size_t(len) || len == SQL_NULL_DATA);
- std::string ret;
- if (len != SQL_NULL_DATA)
- {
- _ASSERT(len >= 0);
- // len - the length of the data before truncation because of the data buffer being too small
- ret.assign(buf, std::clamp<std::size_t>(len, 0, col_size - 1u));
- }
- return ret;
- }
- };
- struct long_nn_dto
- {
- long buf;
- static constexpr SQLSMALLINT sql_c_type = SQL_C_SLONG;
- long toValue() const
- { return buf; }
- };
- struct long_dto
- {
- long buf;
- SQLLEN len;
- static constexpr SQLSMALLINT sql_c_type = SQL_C_SLONG;
- long toValue() const
- { return len == sizeof buf ? buf : (_ASSERT(len == SQL_NULL_DATA), 0); }
- };
- template <typename T, std::size_t col_size, bind_type odbcType>
- consteval auto to_dto()
- {
- if constexpr (std::is_same_v<T, std::string>)
- {
- if constexpr (odbcType == bind_type::timestamp)
- {
- if constexpr (col_size == 0)
- return ts_nn_dto{};
- else
- return ts_dto{};
- }
- else
- return string_dto<col_size + 1>{}; // + '\0'
- }
- else if constexpr (std::is_same_v<T, long>)
- {
- if constexpr (col_size == 0)
- return long_nn_dto{};
- else
- return long_dto{};
- }
- else
- static_assert(dependent_false<T>, "implement dto for T");
- }
- template <bind_desc Col>
- using to_dto_t = decltype(to_dto<typename decltype(Col)::type, Col.size, Col.odbcType>());
- #pragma endregion
- // TODO: inline this concept after msvc merge bug fix in 16.x branch
- // https://developercommunity.visualstudio.com/t/requires-expression-cannot-be-evaluated-as-bool-in/1204110
- template<auto x>
- concept has_len_member = requires { x.len; };
- template <typename dto>
- bool BindCol(SQLHSTMT stmt, SQLUSMALLINT n, dto& dto_column)
- {
- SQLPOINTER buf_ptr = [&]
- {
- if constexpr (std::is_array_v<decltype(dto_column.buf)>)
- return dto_column.buf;
- else
- return &dto_column.buf;
- }();
- SQLLEN* len_ptr = [&] {
- if constexpr (details::has_len_member<dto_column>)
- return &dto_column.len;
- else
- return nullptr;
- }();
- const SQLRETURN rc =
- ::SQLBindCol(stmt, n, dto::sql_c_type,
- buf_ptr, sizeof dto_column.buf, len_ptr);
- //show_v<sizeof dto_column.buf>{};
- return rc == SQL_SUCCESS;
- }
- }
- template<typename V, details::bind_desc... Cols>
- class OdbcBinder
- {
- using DTOrow = tuple<details::to_dto_t<Cols>...>;
- template <std::size_t... I>
- static V Convert(const DTOrow& t, std::index_sequence<I...>)
- {
- V value;
- ((value.*(Cols.value) = get<I>(t).toValue()), ...);
- return value;
- }
- public:
- OdbcBinder() = default;
- OdbcBinder(const OdbcBinder&) = delete;
- OdbcBinder(OdbcBinder&&) = delete;
- std::vector<V> GetQueryResult(COdbc& odbc, std::string_view query, SQLULEN limit = 0, SQLULEN batchSize = 0)
- {
- //show<DTOrow>{};
- constexpr SQLULEN default_batch_size = 32 * 1024 / sizeof(DTOrow); // 32 kb at least
- if (batchSize == 0)
- batchSize = limit != 0 && limit < default_batch_size ? limit : default_batch_size;
- //static_assert(std::is_trivial_v<DTOrow> && std::is_standard_layout_v<DTOrow>);
- static_assert(std::is_nothrow_move_constructible_v<V>); // for fast std::vector reallocation
- // The SQL Server Native Client ODBC driver offers an optimization using rowsets to retrieve a whole result set quickly.
- // 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.
- // 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.
- // After the statement has been executed, increase the rowset size and use either column-wise or row-wise binding.
- // https://docs.microsoft.com/en-us/sql/relational-databases/native-client-odbc-cursors/properties/cursor-rowset-size?view=sql-server-ver15
- // maybe TODO: MSSQL Fast Forward-Only Cursors
- // https://docs.microsoft.com/en-us/sql/relational-databases/native-client-odbc-cursors/programming/fast-forward-only-cursors-odbc?view=sql-server-ver15
- autoHSTMT stmt{ odbc.GetHstmt() };
- SQLRETURN rc;
- if (limit != 0)
- {
- rc = ::SQLSetStmtAttr(stmt, SQL_ATTR_MAX_ROWS, (SQLPOINTER)limit, SQL_IS_UINTEGER); _ASSERT(rc == 0);
- if (rc != SQL_SUCCESS)
- odbc.LogLastError(stmt);
- }
- rc = ::SQLExecDirect(stmt, (SQLCHAR*)query.data(), static_cast<SQLINTEGER>(query.size()));
- if (!SQL_SUCCEEDED(rc))
- {
- throw ODBCException("OB: SQLExecDirect", rc, odbc.LogLastError(stmt));
- }
- const std::unique_ptr<DTOrow[]> mem = std::make_unique_for_overwrite<DTOrow[]>(batchSize);
- //std::span<DTOrow> rowArray(mem.get(), batchSize);
- SQLULEN numRowsFetched = 0;
- rc = ::SQLSetStmtAttr(stmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)batchSize, SQL_IS_UINTEGER); _ASSERT(rc == 0);
- rc = ::SQLSetStmtAttr(stmt, SQL_ATTR_ROW_BIND_TYPE, (SQLPOINTER)sizeof(DTOrow), SQL_IS_UINTEGER); _ASSERT(rc == 0);
- rc = ::SQLSetStmtAttr(stmt, SQL_ATTR_ROWS_FETCHED_PTR, &numRowsFetched, SQL_IS_POINTER); _ASSERT(rc == 0);
- // Bind columns
- SQLUSMALLINT N = 0;
- apply(
- [&](auto&... dto_row) {
- if (!(details::BindCol(stmt, ++N, dto_row) && ...))
- throw ODBCException("OB: SQLBindCol", SQL_ERROR, odbc.LogLastError(stmt));
- }, mem[0]);
- std::vector<V> results;
- results.reserve(batchSize);
- constexpr auto is = std::make_index_sequence<sizeof...(Cols)>{};
- do
- {
- while (SQL_SUCCEEDED(rc = ::SQLFetch(stmt)))
- {
- if (rc == SQL_SUCCESS_WITH_INFO)
- odbc.LogLastError(stmt);
- for (SQLUINTEGER i = 0; i < numRowsFetched; ++i)
- {
- results.emplace_back(Convert(mem[i], is));
- }
- }
- if (rc != SQL_NO_DATA)
- {
- throw ODBCException("OB: SQLFetch", rc, odbc.LogLastError(stmt));
- }
- } while (SQL_SUCCEEDED(rc = ::SQLMoreResults(stmt)));
- if (rc != SQL_NO_DATA)
- {
- throw ODBCException("OB: SQLMoreResults", rc, odbc.LogLastError(stmt));
- }
- stmt.reset();
- if (results.capacity() > results.size() * 3)
- results.shrink_to_fit();
- return results;
- }
- };
- template <typename V>
- consteval auto sized(std::string V::* ptr, std::size_t col_size)
- {
- return details::bind_desc<std::string, V>{ col_size, ptr };
- }
- template <details::not_varsize T, typename V>
- consteval auto not_null(T V::* ptr)
- {
- return details::bind_desc<T, V>{ 0, ptr };
- }
- template <details::not_varsize T, typename V>
- consteval auto nullable(T V::* ptr)
- {
- return details::bind_desc<T, V>{ 1, ptr };
- }
- template <typename V>
- consteval auto not_null_ts(std::string V::* ptr)
- {
- return details::bind_desc<std::string, V>{ 0, ptr, details::bind_type::timestamp };
- }
- template <typename V>
- consteval auto nullable_ts(std::string V::* ptr)
- {
- return details::bind_desc<std::string, V>{ 1, ptr, details::bind_type::timestamp };
- }
- /////////////////////////////////////////
- struct Incident
- {
- long id;
- std::string name;
- std::string comments;
- /* ... */
- std::string timeSend;
- std::string timeClosed;
- long num;
- };
- int main()
- {
- auto odbc = COdbc{};
- OdbcBinder<Incident
- , not_null(&Incident::id)
- , sized(&Incident::name, 25)
- , sized(&Incident::comments, 100)
- , not_null_ts(&Incident::timeSend)
- , nullable_ts(&Incident::timeClosed)
- , nullable(&Incident::num)
- > binder;
- auto vec = binder.GetQueryResult(odbc, "select ...");
- return 0;
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement