diff --git a/lib/src/HttpResponseImpl.h b/lib/src/HttpResponseImpl.h index aa8018cb73..0a4fa4c745 100644 --- a/lib/src/HttpResponseImpl.h +++ b/lib/src/HttpResponseImpl.h @@ -465,10 +465,6 @@ class DROGON_EXPORT HttpResponseImpl : public HttpResponse private: bool allowCompression_{true}; - void setAllowCompression(bool allow) override; - - bool allowCompression() const override; - void setBody(const char *body, size_t len) override { bodyPtr_ = std::make_shared(body, len); @@ -478,6 +474,10 @@ class DROGON_EXPORT HttpResponseImpl : public HttpResponse } } + void setAllowCompression(bool allow) override; + + bool allowCompression() const override; + void setContentTypeCodeAndCustomString(ContentType type, const char *typeString, size_t typeStringLength) override diff --git a/orm_lib/inc/drogon/orm/Field.h b/orm_lib/inc/drogon/orm/Field.h index 53b2dbc73f..e682636fb9 100644 --- a/orm_lib/inc/drogon/orm/Field.h +++ b/orm_lib/inc/drogon/orm/Field.h @@ -66,6 +66,35 @@ class DROGON_EXPORT Field return result_.getLength(row_, column_); } + SqlFieldType sqlType() const noexcept + { + return result_.getSqlType(column_); + } + + /// SQL type name (VARCHAR, INT, DECIMAL, etc.) + const std::string &typeName() const noexcept + { + return result_.getTypeName(column_); + } + + /// Character length (VARCHAR) + int columnLength() const noexcept + { + return result_.getColumnLength(column_); + } + + /// Numeric precision (DECIMAL / NUMERIC) + int precision() const noexcept + { + return result_.getPrecision(column_); + } + + /// Numeric scale (DECIMAL / NUMERIC) + int scale() const noexcept + { + return result_.getScale(column_); + } + /// Convert to a type T value template T as() const diff --git a/orm_lib/inc/drogon/orm/Result.h b/orm_lib/inc/drogon/orm/Result.h index ab6389682d..3ac6620f32 100644 --- a/orm_lib/inc/drogon/orm/Result.h +++ b/orm_lib/inc/drogon/orm/Result.h @@ -34,12 +34,40 @@ class Row; class ResultImpl; using ResultImplPtr = std::shared_ptr; +enum class SqlFieldType : uint8_t +{ + Unknown = 0, + Bool, + Int, + BigInt, + Float, + Double, + Decimal, + Varchar, + Text, + Date, + Time, + DateTime, + Blob, + Json, + Binary +}; + enum class SqlStatus { Ok, End }; +struct ColumnMeta +{ + SqlFieldType sqlType{SqlFieldType::Unknown}; + std::string typeName; + int length{0}; + int precision{0}; + int scale{0}; +}; + /// Result set containing data returned by a query or command. /** This behaves as a container (as defined by the C++ standard library) and * provides random access const iterators to iterate over its rows. A row @@ -139,6 +167,82 @@ class DROGON_EXPORT Result */ unsigned long long insertId() const noexcept; + /** + * @brief Get the logical SQL type of the specified column. + * + * @param column Zero-based index of the column (must be less than + * columns()). + * @return The abstracted SQL field type for the given column, or + * SqlFieldType::Unknown if the type cannot be determined. + * + * @note Type metadata may only be fully populated for some database + * backends (for example, MySQL). For other backends, the result + * may be limited or fall back to SqlFieldType::Unknown. + */ + SqlFieldType getSqlType(SizeType column) const; + + /** + * @brief Get the database-specific type name of the specified column. + * + * @param column Zero-based index of the column (must be less than + * columns()). + * @return A reference to a string containing the type name as reported + * by the underlying database driver (for example, "INT", + * "VARCHAR", "DECIMAL(10,2)", etc.). + * + * @note Type-name metadata may only be fully populated for some database + * backends (for example, MySQL). On other backends, the returned + * string may be empty or use a backend-specific representation. + */ + const std::string &getTypeName(SizeType column) const; + + /** + * @brief Get the defined maximum length of the specified column. + * + * @param column Zero-based index of the column (must be less than + * columns()). + * @return The maximum length for the column in characters or bytes, as + * reported by the underlying database driver, or 0 if this + * information is not available. + * + * @note Length metadata may only be populated for some database backends + * (for example, MySQL) and for certain column types (such as + * character and binary types). + */ + int getColumnLength(SizeType column) const; + + /** + * @brief Get the numeric precision for the specified column. + * + * @param column Zero-based index of the column (must be less than + * columns()). + * @return The precision (total number of significant digits) for the + * column, as reported by the underlying database driver, or 0 if + * not applicable or not available. + * + * @note Precision is generally only meaningful for numeric types such as + * DECIMAL or NUMERIC. On other types, the value may be 0 or + * unspecified, and metadata may only be provided by some backends + * (for example, MySQL). + */ + int getPrecision(SizeType column) const; + + /** + * @brief Get the numeric scale for the specified column. + * + * @param column Zero-based index of the column (must be less than + * columns()). + * @return The scale (number of fractional digits) for the column, as + * reported by the underlying database driver, or 0 if not + * applicable or not available. + * + * @note Scale is generally only meaningful for numeric types such as + * DECIMAL or NUMERIC. On other types, the value may be 0 or + * unspecified, and metadata may only be provided by some backends + * (for example, MySQL). + */ + int getScale(SizeType column) const; + #ifdef _MSC_VER Result() noexcept = default; #endif diff --git a/orm_lib/src/Result.cc b/orm_lib/src/Result.cc index fe45ebb314..309884f90e 100644 --- a/orm_lib/src/Result.cc +++ b/orm_lib/src/Result.cc @@ -170,6 +170,31 @@ unsigned long long Result::insertId() const noexcept return resultPtr_->insertId(); } +SqlFieldType Result::getSqlType(SizeType column) const +{ + return resultPtr_->columnMeta(column).sqlType; +} + +const std::string &Result::getTypeName(SizeType column) const +{ + return resultPtr_->columnMeta(column).typeName; +} + +int Result::getColumnLength(SizeType column) const +{ + return resultPtr_->columnMeta(column).length; +} + +int Result::getPrecision(SizeType column) const +{ + return resultPtr_->columnMeta(column).precision; +} + +int Result::getScale(SizeType column) const +{ + return resultPtr_->columnMeta(column).scale; +} + int Result::oid(RowSizeType column) const noexcept { return resultPtr_->oid(column); diff --git a/orm_lib/src/ResultImpl.h b/orm_lib/src/ResultImpl.h index fb8e1d0379..5322b9819b 100644 --- a/orm_lib/src/ResultImpl.h +++ b/orm_lib/src/ResultImpl.h @@ -37,6 +37,13 @@ class ResultImpl : public trantor::NonCopyable virtual bool isNull(SizeType row, RowSizeType column) const = 0; virtual FieldSizeType getLength(SizeType row, RowSizeType column) const = 0; + virtual const ColumnMeta &columnMeta(SizeType column) const + { + (void)column; + static const ColumnMeta dummy{}; + return dummy; + } + virtual unsigned long long insertId() const noexcept { return 0; diff --git a/orm_lib/src/mysql_impl/MysqlResultImpl.cc b/orm_lib/src/mysql_impl/MysqlResultImpl.cc index 20b4a42768..ff664b0dd8 100644 --- a/orm_lib/src/mysql_impl/MysqlResultImpl.cc +++ b/orm_lib/src/mysql_impl/MysqlResultImpl.cc @@ -83,3 +83,8 @@ unsigned long long MysqlResultImpl::insertId() const noexcept { return insertId_; } + +const ColumnMeta &MysqlResultImpl::columnMeta(SizeType column) const +{ + return columnMeta_.at(column); +} diff --git a/orm_lib/src/mysql_impl/MysqlResultImpl.h b/orm_lib/src/mysql_impl/MysqlResultImpl.h index 64013f0271..4b86b8dedb 100644 --- a/orm_lib/src/mysql_impl/MysqlResultImpl.h +++ b/orm_lib/src/mysql_impl/MysqlResultImpl.h @@ -26,6 +26,106 @@ namespace drogon { namespace orm { + +inline SqlFieldType mysqlTypeToSql(enum enum_field_types t, unsigned int flags) +{ + switch (t) + { + case MYSQL_TYPE_TINY: + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_LONG: + return SqlFieldType::Int; + + case MYSQL_TYPE_LONGLONG: + return SqlFieldType::BigInt; + + case MYSQL_TYPE_FLOAT: + return SqlFieldType::Float; + + case MYSQL_TYPE_DOUBLE: + return SqlFieldType::Double; + + case MYSQL_TYPE_NEWDECIMAL: + return SqlFieldType::Decimal; + + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_VARCHAR: + if (flags & BINARY_FLAG) + return SqlFieldType::Binary; + return SqlFieldType::Varchar; + + case MYSQL_TYPE_STRING: + if (flags & BINARY_FLAG) + return SqlFieldType::Binary; + return SqlFieldType::Text; + + case MYSQL_TYPE_BLOB: + return SqlFieldType::Blob; + + case MYSQL_TYPE_DATE: + return SqlFieldType::Date; + + case MYSQL_TYPE_TIME: + return SqlFieldType::Time; + + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + return SqlFieldType::DateTime; + + case MYSQL_TYPE_JSON: + return SqlFieldType::Json; + + default: + return SqlFieldType::Unknown; + } +} + +inline const char *mysqlFieldTypeToName(enum enum_field_types t, + unsigned int flags) +{ + switch (t) + { + case MYSQL_TYPE_TINY: + return "TINYINT"; + case MYSQL_TYPE_SHORT: + return "SMALLINT"; + case MYSQL_TYPE_LONG: + return "INT"; + case MYSQL_TYPE_LONGLONG: + return "BIGINT"; + case MYSQL_TYPE_FLOAT: + return "FLOAT"; + case MYSQL_TYPE_DOUBLE: + return "DOUBLE"; + case MYSQL_TYPE_NEWDECIMAL: + return "DECIMAL"; + + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_VARCHAR: + return (flags & BINARY_FLAG) ? "VARBINARY" : "VARCHAR"; + + case MYSQL_TYPE_STRING: + return (flags & BINARY_FLAG) ? "BINARY" : "CHAR"; + + case MYSQL_TYPE_BLOB: + return (flags & BINARY_FLAG) ? "BLOB" : "TEXT"; + + case MYSQL_TYPE_DATE: + return "DATE"; + case MYSQL_TYPE_TIME: + return "TIME"; + case MYSQL_TYPE_DATETIME: + return "DATETIME"; + case MYSQL_TYPE_TIMESTAMP: + return "TIMESTAMP"; + case MYSQL_TYPE_JSON: + return "JSON"; + + default: + return "UNKNOWN"; + } +} + class MysqlResultImpl : public ResultImpl { public: @@ -39,20 +139,44 @@ class MysqlResultImpl : public ResultImpl affectedRows_(affectedRows), insertId_(insertId) { - if (fieldsNumber_ > 0) + if (fieldArray_ && fieldsNumber_ > 0) { + columnMeta_.resize(fieldsNumber_); + fieldsMapPtr_ = std::make_shared< std::unordered_map>(); - for (RowSizeType i = 0; i < fieldsNumber_; ++i) + fieldsMapPtr_->reserve(fieldsNumber_); + + for (unsigned int i = 0; i < fieldsNumber_; ++i) { - std::string fieldName = fieldArray_[i].name; + const MYSQL_FIELD &f = fieldArray_[i]; + auto &meta = columnMeta_[i]; + + meta.sqlType = mysqlTypeToSql(f.type, f.flags); + + const char *typeName = mysqlFieldTypeToName(f.type, f.flags); + meta.typeName = typeName ? typeName : "UNKNOWN"; + + meta.length = static_cast(f.length); + + meta.precision = (meta.sqlType == SqlFieldType::Decimal) + ? static_cast(f.length) + : 0; + + meta.scale = (meta.sqlType == SqlFieldType::Decimal) + ? static_cast(f.decimals) + : 0; + + std::string fieldName = f.name; std::transform(fieldName.begin(), fieldName.end(), fieldName.begin(), - [](unsigned char c) { return tolower(c); }); + [](unsigned char c) { return std::tolower(c); }); + (*fieldsMapPtr_)[fieldName] = i; } } + if (size() > 0) { rowsPtr_ = std::make_shared< @@ -80,11 +204,13 @@ class MysqlResultImpl : public ResultImpl bool isNull(SizeType row, RowSizeType column) const override; FieldSizeType getLength(SizeType row, RowSizeType column) const override; unsigned long long insertId() const noexcept override; + const ColumnMeta &columnMeta(SizeType column) const override; private: const std::shared_ptr result_; const Result::SizeType rowsNumber_; const MYSQL_FIELD *fieldArray_; + std::vector columnMeta_; const Result::RowSizeType fieldsNumber_; const SizeType affectedRows_; const unsigned long long insertId_; diff --git a/trantor b/trantor index 5000e2a726..720db22f1a 160000 --- a/trantor +++ b/trantor @@ -1 +1 @@ -Subproject commit 5000e2a72687232c8675b28ce86a29ed7d44309e +Subproject commit 720db22f1a367d36c41c22a16ccfc71f1cdad595