diff --git a/src/rgw/driver/sfs/CMakeLists.txt b/src/rgw/driver/sfs/CMakeLists.txt index e53ebd3d00a8a..1233c11fead41 100644 --- a/src/rgw/driver/sfs/CMakeLists.txt +++ b/src/rgw/driver/sfs/CMakeLists.txt @@ -25,6 +25,7 @@ set(sfs_srcs sqlite/dbconn.cc sqlite/errors.cc sqlite/sqlite_list.cc + sqlite/conversion_utils.cc bucket.cc multipart.cc object.cc diff --git a/src/rgw/driver/sfs/object_state.h b/src/rgw/driver/sfs/object_state.h index 2b1c59eb39e11..292f8384cb9dd 100644 --- a/src/rgw/driver/sfs/object_state.h +++ b/src/rgw/driver/sfs/object_state.h @@ -15,9 +15,12 @@ #define RGW_SFS_OBJECT_STATE_H #include + +#include "include/ceph_assert.h" #if FMT_VERSION >= 90000 #include #endif +#include "sqlite/dbapi.h" namespace rgw::sal::sfs { @@ -47,6 +50,36 @@ inline std::string str_object_state(ObjectState state) { return result; } +template <> +struct dbapi::sqlite::has_sqlite_type + : ::std::true_type {}; + +inline int bind_col_in_db( + sqlite3_stmt* stmt, int inx, const rgw::sal::sfs::ObjectState& val +) { + return sqlite3_bind_int(stmt, inx, static_cast(val)); +} +inline void store_result_in_db( + sqlite3_context* db, const rgw::sal::sfs::ObjectState& val +) { + sqlite3_result_int(db, static_cast(val)); +} +inline rgw::sal::sfs::ObjectState +get_col_from_db(sqlite3_stmt* stmt, int inx, dbapi::sqlite::result_type) { + if (sqlite3_column_type(stmt, inx) == SQLITE_NULL) { + ceph_abort_msg("cannot make enum value from NULL"); + } + return static_cast(sqlite3_column_int(stmt, inx)); +} + +inline rgw::sal::sfs::ObjectState +get_val_from_db(sqlite3_value* value, dbapi::sqlite::result_type) { + if (sqlite3_value_type(value) == SQLITE_NULL) { + ceph_abort_msg("cannot make enum value from NULL"); + } + return static_cast(sqlite3_value_int(value)); +} + } // namespace rgw::sal::sfs inline std::ostream& operator<<( diff --git a/src/rgw/driver/sfs/sqlite/bindings/real_time.h b/src/rgw/driver/sfs/sqlite/bindings/real_time.h index 6902f6e6fe731..a4454817ab447 100644 --- a/src/rgw/driver/sfs/sqlite/bindings/real_time.h +++ b/src/rgw/driver/sfs/sqlite/bindings/real_time.h @@ -14,6 +14,8 @@ #pragma once #include "common/ceph_time.h" +#include "include/ceph_assert.h" +#include "rgw/driver/sfs/sqlite/dbapi.h" #include "rgw/driver/sfs/sqlite/sqlite_orm.h" /// ceph::real_time is represented as a uint64 (unsigned). @@ -90,3 +92,41 @@ struct row_extractor { }; } // namespace sqlite_orm + +namespace rgw::sal::sfs::dbapi::sqlite { + +template <> +struct has_sqlite_type + : ::std::true_type {}; + +inline int bind_col_in_db( + sqlite3_stmt* stmt, int inx, const ceph::real_time& val +) { + return sqlite3_bind_int64( + stmt, inx, rgw::sal::sfs::sqlite::time_point_to_int64(val) + ); +} +inline void store_result_in_db( + sqlite3_context* db, const ceph::real_time& val +) { + sqlite3_result_int64(db, rgw::sal::sfs::sqlite::time_point_to_int64(val)); +} +inline ceph::real_time +get_col_from_db(sqlite3_stmt* stmt, int inx, result_type) { + if (sqlite3_column_type(stmt, inx) == SQLITE_NULL) { + ceph_abort_msg("cannot make enum value from NULL"); + } + return rgw::sal::sfs::sqlite::time_point_from_int64( + sqlite3_column_int64(stmt, inx) + ); +} + +inline ceph::real_time +get_val_from_db(sqlite3_value* value, result_type) { + if (sqlite3_value_type(value) == SQLITE_NULL) { + ceph_abort_msg("cannot make enum value from NULL"); + } + return rgw::sal::sfs::sqlite::time_point_from_int64(sqlite3_value_int64(value) + ); +} +} // namespace rgw::sal::sfs::dbapi::sqlite diff --git a/src/rgw/driver/sfs/sqlite/conversion_utils.cc b/src/rgw/driver/sfs/sqlite/conversion_utils.cc new file mode 100644 index 0000000000000..a0c032e59b901 --- /dev/null +++ b/src/rgw/driver/sfs/sqlite/conversion_utils.cc @@ -0,0 +1,37 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t +// vim: ts=8 sw=2 smarttab ft=cpp +/* + * Ceph - scalable distributed file system + * SFS SAL implementation + * + * Copyright (C) 2023 SUSE LLC + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + */ + +#include + +namespace rgw::sal::sfs::sqlite { + +std::string prefix_to_escaped_like(const std::string& prefix, char escape) { + std::string like_expr; + like_expr.reserve(prefix.length() + 10); + for (const char c : prefix) { + switch (c) { + case '%': + [[fallthrough]]; + case '_': + like_expr.push_back(escape); + [[fallthrough]]; + default: + like_expr.push_back(c); + } + } + like_expr.push_back('%'); + return like_expr; +} + +} // namespace rgw::sal::sfs::sqlite diff --git a/src/rgw/driver/sfs/sqlite/conversion_utils.h b/src/rgw/driver/sfs/sqlite/conversion_utils.h index 4ae6646b4c0e4..de0f906924380 100644 --- a/src/rgw/driver/sfs/sqlite/conversion_utils.h +++ b/src/rgw/driver/sfs/sqlite/conversion_utils.h @@ -128,22 +128,12 @@ void assign_db_value(const SOURCE& source, std::vector& dest) { dest = blob_vector; } +std::string prefix_to_escaped_like(const std::string& prefix, char escape); + template sqlite_orm::internal::like_t, const char*> prefix_to_like(COL col, const std::string& prefix) { - std::string like_expr; - like_expr.reserve(prefix.length() + 10); - for (const char c : prefix) { - switch (c) { - case '%': - case '_': - like_expr.push_back('\a'); - default: - like_expr.push_back(c); - } - } - like_expr.push_back('%'); - return sqlite_orm::like(col, like_expr, "\a"); + return sqlite_orm::like(col, prefix_to_escaped_like(prefix, '\a'), "\a"); } } // namespace rgw::sal::sfs::sqlite diff --git a/src/rgw/driver/sfs/sqlite/sqlite_list.cc b/src/rgw/driver/sfs/sqlite/sqlite_list.cc index 47c82a1f0f8c6..8798042f73cdb 100644 --- a/src/rgw/driver/sfs/sqlite/sqlite_list.cc +++ b/src/rgw/driver/sfs/sqlite/sqlite_list.cc @@ -13,6 +13,7 @@ #include +#include "dbapi.h" #include "rgw/driver/sfs/sqlite/conversion_utils.h" #include "rgw/driver/sfs/sqlite/objects/object_definitions.h" #include "rgw/driver/sfs/sqlite/versioned_object/versioned_object_definitions.h" @@ -101,64 +102,45 @@ bool SQLiteList::versions( // more available logic: request one more than max. if we get that // much set out_more_available, but return only up to max ceph_assert(max < std::numeric_limits::max()); - const size_t query_limit = max + 1; - - auto storage = conn->get_storage(); - auto rows = storage->select( - columns( - &DBObject::name, &DBVersionedObject::version_id, - &DBVersionedObject::mtime, &DBVersionedObject::etag, - &DBVersionedObject::size, &DBVersionedObject::version_type, - is_equal( - // IsLatest logic - // - Use the id as secondary condition if multiple version - // with same max(commit_time) exists - sqlite_orm::select( - &DBVersionedObject::id, from(), - where( - is_equal( - &DBObject::uuid, &DBVersionedObject::object_id - ) and - is_equal( - &DBVersionedObject::object_state, - ObjectState::COMMITTED - ) - ), - multi_order_by( - order_by(&DBVersionedObject::commit_time).desc(), - order_by(&DBVersionedObject::id).desc() - ), - limit(1) - ), - &DBVersionedObject::id - ) - ), - inner_join( - on(is_equal(&DBObject::uuid, &DBVersionedObject::object_id)) - ), - where( - is_equal(&DBVersionedObject::object_state, ObjectState::COMMITTED) and - is_equal(&DBObject::bucket_id, bucket_id) and - greater_than(&DBObject::name, start_after_object_name) and - prefix_to_like(&DBObject::name, prefix) - ), - // Sort: - // names a-Z - // first delete markers, then versions - (See: LC CurrentExpiration) - // newest to oldest version - multi_order_by( - order_by(&DBObject::name).asc(), - order_by(&DBVersionedObject::commit_time).desc(), - order_by(&DBVersionedObject::id).desc() - ), - limit(query_limit) - ); - - ceph_assert(rows.size() <= static_cast(query_limit)); - const size_t return_limit = std::min(max, rows.size()); - out.reserve(return_limit); - for (size_t i = 0; i < return_limit; i++) { - const auto& row = rows[i]; + const uint32_t query_limit = max + 1; + dbapi::sqlite::database db = conn->get(); + auto rows = db << R"sql( + SELECT + o.name, vo.version_id, vo.mtime, vo.etag, vo.size, vo.version_type, + (vo.id = ( SELECT id FROM versioned_objects + WHERE object_id = o.uuid + AND object_state = ? + ORDER BY commit_time desc, id desc + LIMIT 1 + )) AS is_latest + FROM objects as o + INNER JOIN versioned_objects as vo + ON (o.uuid = vo.object_id) + WHERE vo.object_state = ? + AND o.bucket_id = ? + AND o.name > ? + AND o.name LIKE ? ESCAPE CHAR(7) + ORDER BY o.name ASC, + vo.commit_time DESC, + vo.id DESC + LIMIT ?;)sql" + << ObjectState::COMMITTED << ObjectState::COMMITTED + << bucket_id << start_after_object_name + << prefix_to_escaped_like(prefix, '\a') << query_limit; + out.reserve(max); + if (out_more_available) { + *out_more_available = false; + } + for (std::tuple< + std::string, std::string, ceph::real_time, std::string, int64_t, + VersionType, bool> + row : rows) { + if (out.size() >= max) { + if (out_more_available) { + *out_more_available = true; + } + break; + } rgw_bucket_dir_entry e; e.key.name = std::get<0>(row); e.key.instance = std::get<1>(row); @@ -169,9 +151,7 @@ bool SQLiteList::versions( e.flags = to_dentry_flag(std::get<5>(row), std::get<6>(row)); out.emplace_back(e); } - if (out_more_available) { - *out_more_available = rows.size() == query_limit; - } + return true; } diff --git a/src/rgw/driver/sfs/version_type.h b/src/rgw/driver/sfs/version_type.h index aa2b598e19379..0c694e32a6416 100644 --- a/src/rgw/driver/sfs/version_type.h +++ b/src/rgw/driver/sfs/version_type.h @@ -14,6 +14,8 @@ #ifndef RGW_SFS_VERSION_TYPE_H #define RGW_SFS_VERSION_TYPE_H +#include "sqlite/dbapi.h" + namespace rgw::sal::sfs { enum class VersionType { @@ -22,6 +24,36 @@ enum class VersionType { LAST_VALUE = DELETE_MARKER }; +template <> +struct dbapi::sqlite::has_sqlite_type + : ::std::true_type {}; + +inline int bind_col_in_db( + sqlite3_stmt* stmt, int inx, const rgw::sal::sfs::VersionType& val +) { + return sqlite3_bind_int(stmt, inx, static_cast(val)); +} +inline void store_result_in_db( + sqlite3_context* db, const rgw::sal::sfs::VersionType& val +) { + sqlite3_result_int(db, static_cast(val)); +} +inline rgw::sal::sfs::VersionType +get_col_from_db(sqlite3_stmt* stmt, int inx, dbapi::sqlite::result_type) { + if (sqlite3_column_type(stmt, inx) == SQLITE_NULL) { + ceph_abort_msg("cannot make enum value from NULL"); + } + return static_cast(sqlite3_column_int(stmt, inx)); +} + +inline rgw::sal::sfs::VersionType +get_val_from_db(sqlite3_value* value, dbapi::sqlite::result_type) { + if (sqlite3_value_type(value) == SQLITE_NULL) { + ceph_abort_msg("cannot make enum value from NULL"); + } + return static_cast(sqlite3_value_int(value)); +} + } // namespace rgw::sal::sfs #endif // RGW_SFS_VERSION_TYPE_H