37#include <condition_variable>
48#include <sqlpp23/sqlpp23.h>
52#include "iceberg/result.h"
54namespace iceberg::sql {
56template <
typename Connection>
60 : connection_(std::move(connection)) {}
62 template <
typename Fn>
63 decltype(
auto) WithConnection(Fn&& fn) {
64 std::lock_guard lock(mutex_);
65 return std::forward<Fn>(fn)(connection_);
68 Status RunInTransaction(
const std::function<Status()>& body) {
69 std::lock_guard lock(mutex_);
70 auto transaction = sqlpp::start_transaction(connection_);
72 Status status = body();
73 if (status.has_value()) {
76 transaction.rollback();
81 transaction.rollback();
89 Connection connection_;
90 std::recursive_mutex mutex_;
93template <
typename Pool>
96 using Connection =
typename Pool::_pooled_connection_t;
99 : pool_(std::move(pool)), max_connections_(std::max<int32_t>(1, max_connections)) {}
101 template <
typename Fn>
102 decltype(
auto) WithConnection(Fn&& fn) {
103 if (
auto* connection = ActiveTransactionConnection()) {
104 return std::forward<Fn>(fn)(*connection);
107 ConnectionLease lease(*
this);
108 auto connection = pool_.get();
109 return std::forward<Fn>(fn)(connection);
112 Status RunInTransaction(
const std::function<Status()>& body) {
113 if (ActiveTransactionConnection() !=
nullptr) {
114 return InvalidArgument(
"Nested SQL catalog transactions are not supported");
117 ConnectionLease lease(*
this);
118 auto connection = pool_.get();
119 TransactionContext context{
this, &connection};
120 ScopedTransactionContext scope(context);
122 auto transaction = sqlpp::start_transaction(connection);
124 Status status = body();
125 if (status.has_value()) {
126 transaction.commit();
128 transaction.rollback();
133 transaction.rollback();
141 struct TransactionContext {
143 Connection* connection;
146 class ScopedTransactionContext {
148 explicit ScopedTransactionContext(TransactionContext& context)
149 : previous_(active_context_) {
150 active_context_ = &context;
153 ScopedTransactionContext(
const ScopedTransactionContext&) =
delete;
154 ScopedTransactionContext& operator=(
const ScopedTransactionContext&) =
delete;
155 ScopedTransactionContext(ScopedTransactionContext&&) =
delete;
156 ScopedTransactionContext& operator=(ScopedTransactionContext&&) =
delete;
158 ~ScopedTransactionContext() { active_context_ = previous_; }
161 TransactionContext* previous_;
164 class ConnectionLease {
167 std::unique_lock lock(source_.mutex_);
168 source_.available_.wait(
169 lock, [&] {
return source_.active_connections_ < source_.max_connections_; });
170 ++source_.active_connections_;
173 ConnectionLease(
const ConnectionLease&) =
delete;
174 ConnectionLease& operator=(
const ConnectionLease&) =
delete;
175 ConnectionLease(ConnectionLease&&) =
delete;
176 ConnectionLease& operator=(ConnectionLease&&) =
delete;
180 std::lock_guard lock(source_.mutex_);
181 --source_.active_connections_;
183 source_.available_.notify_one();
190 Connection* ActiveTransactionConnection() {
191 if (active_context_ !=
nullptr && active_context_->owner ==
this) {
192 return active_context_->connection;
198 const int32_t max_connections_;
200 std::condition_variable available_;
201 int32_t active_connections_ = 0;
203 inline static thread_local TransactionContext* active_context_ =
nullptr;
213template <
typename ConnectionSource,
typename Traits>
216 template <
typename... SourceArgs>
218 : source_(std::forward<SourceArgs>(source_args)...),
219 catalog_(std::move(catalog_name)) {}
222 return Guard(
"initialize catalog tables", [&] {
223 source_.WithConnection([&](
auto& connection) {
224 connection(std::string(kCreateTablesSql));
225 connection(std::string(kCreateNamespacePropertiesSql));
227 connection(std::string(kAddRecordTypeSql));
228 }
catch (
const std::exception& e) {
229 if (!IsDuplicateColumn(e)) {
240 const IcebergNamespaceProperties namespaces{};
241 const IcebergTables tables{};
242 std::vector<std::string> names;
243 auto status = Guard(
"list namespaces", [&] {
244 source_.WithConnection([&](
auto& connection) {
245 for (
const auto& row :
246 connection(sqlpp::select(sqlpp::distinct, namespaces.ns)
248 .where(namespaces.catalogName == catalog_))) {
249 names.emplace_back(row.ns);
251 for (
const auto& row :
252 connection(sqlpp::select(sqlpp::distinct, tables.tableNamespace)
254 .where(tables.catalogName == catalog_ and
255 TableRecordFilter(tables)))) {
256 names.emplace_back(row.tableNamespace);
260 if (!status.has_value())
return std::unexpected(status.error());
265 std::string_view ns)
override {
266 const IcebergNamespaceProperties t{};
267 std::vector<NamespaceProperty> properties;
268 auto status = Guard(
"get namespace properties", [&] {
269 source_.WithConnection([&](
auto& connection) {
270 for (
const auto& row : connection(
271 sqlpp::select(t.propertyKey, t.propertyValue)
273 .where(t.catalogName == catalog_ and t.ns == std::string(ns)))) {
275 property.
key = std::string(row.propertyKey);
276 if (row.propertyValue.has_value()) {
277 property.value = std::string(*row.propertyValue);
279 properties.push_back(std::move(property));
283 if (!status.has_value())
return std::unexpected(status.error());
288 std::optional<std::string_view> value)
override {
289 const IcebergNamespaceProperties t{};
290 std::optional<std::string> value_str;
291 if (value.has_value()) value_str = std::string(*value);
292 return Guard(
"insert namespace property", [&] {
293 source_.WithConnection([&](
auto& connection) {
294 connection(sqlpp::insert_into(t).set(
295 t.catalogName = catalog_, t.ns = std::string(ns),
296 t.propertyKey = std::string(key), t.propertyValue = value_str));
302 const IcebergNamespaceProperties t{};
303 return Guard(
"delete namespace property", [&] {
304 source_.WithConnection([&](
auto& connection) {
305 connection(sqlpp::delete_from(t).where(t.catalogName == catalog_ and
306 t.ns == std::string(ns) and
307 t.propertyKey == std::string(key)));
313 const IcebergNamespaceProperties t{};
314 int64_t affected = 0;
315 auto status = Guard(
"delete namespace", [&] {
316 source_.WithConnection([&](
auto& connection) {
317 affected =
static_cast<int64_t
>(
318 connection(sqlpp::delete_from(t).where(t.catalogName == catalog_ and
319 t.ns == std::string(ns)))
323 if (!status.has_value())
return std::unexpected(status.error());
330 const IcebergTables t{};
331 std::vector<std::string> names;
332 auto status = Guard(
"list tables", [&] {
333 source_.WithConnection([&](
auto& connection) {
334 for (
const auto& row :
335 connection(sqlpp::select(t.tableName)
337 .where(t.catalogName == catalog_ and
338 t.tableNamespace == std::string(ns) and
339 TableRecordFilter(t)))) {
340 names.emplace_back(row.tableName);
344 if (!status.has_value())
return std::unexpected(status.error());
348 Result<bool>
TableExists(std::string_view ns, std::string_view name)
override {
349 const IcebergTables t{};
351 auto status = Guard(
"check table exists", [&] {
352 source_.WithConnection([&](
auto& connection) {
353 auto result = connection(sqlpp::select(t.tableName)
355 .where(t.catalogName == catalog_ and
356 t.tableNamespace == std::string(ns) and
357 t.tableName == std::string(name) and
358 TableRecordFilter(t)));
359 exists = !result.empty();
362 if (!status.has_value())
return std::unexpected(status.error());
367 std::string_view ns, std::string_view name)
override {
368 const IcebergTables t{};
369 std::optional<std::string> location;
370 auto status = Guard(
"get table metadata location", [&] {
371 source_.WithConnection([&](
auto& connection) {
372 auto result = connection(sqlpp::select(t.metadataLocation)
374 .where(t.catalogName == catalog_ and
375 t.tableNamespace == std::string(ns) and
376 t.tableName == std::string(name) and
377 TableRecordFilter(t)));
378 if (!result.empty()) {
379 const auto& row = result.front();
380 if (row.metadataLocation.has_value()) {
381 location = std::string(*row.metadataLocation);
386 if (!status.has_value())
return std::unexpected(status.error());
391 std::string_view metadata_location)
override {
392 const IcebergTables t{};
393 return Guard(
"insert table", [&] {
394 source_.WithConnection([&](
auto& connection) {
395 connection(sqlpp::insert_into(t).set(
396 t.catalogName = catalog_, t.tableNamespace = std::string(ns),
397 t.tableName = std::string(name),
398 t.metadataLocation = std::string(metadata_location),
399 t.recordType = std::string(kTableRecordType)));
405 std::string_view ns, std::string_view name, std::string_view new_location,
406 std::string_view new_previous_location,
407 std::string_view expected_current_location)
override {
408 const IcebergTables t{};
409 int64_t affected = 0;
410 auto status = Guard(
"update table metadata location", [&] {
411 source_.WithConnection([&](
auto& connection) {
412 affected =
static_cast<int64_t
>(
415 .set(t.metadataLocation = std::string(new_location),
416 t.previousMetadataLocation = std::string(new_previous_location))
417 .where(t.catalogName == catalog_ and
418 t.tableNamespace == std::string(ns) and
419 t.tableName == std::string(name) and
420 t.metadataLocation ==
421 std::string(expected_current_location) and
422 TableRecordFilter(t)))
426 if (!status.has_value())
return std::unexpected(status.error());
430 Result<int64_t>
DeleteTable(std::string_view ns, std::string_view name)
override {
431 const IcebergTables t{};
432 int64_t affected = 0;
433 auto status = Guard(
"delete table", [&] {
434 source_.WithConnection([&](
auto& connection) {
435 affected =
static_cast<int64_t
>(
436 connection(sqlpp::delete_from(t).where(t.catalogName == catalog_ and
437 t.tableNamespace == std::string(ns) and
438 t.tableName == std::string(name) and
439 TableRecordFilter(t)))
443 if (!status.has_value())
return std::unexpected(status.error());
447 Result<int64_t>
RenameTable(std::string_view from_ns, std::string_view from_name,
448 std::string_view to_ns, std::string_view to_name)
override {
449 const IcebergTables t{};
450 int64_t affected = 0;
451 auto status = Guard(
"rename table", [&] {
452 source_.WithConnection([&](
auto& connection) {
453 affected =
static_cast<int64_t
>(
454 connection(sqlpp::update(t)
455 .set(t.tableNamespace = std::string(to_ns),
456 t.tableName = std::string(to_name))
457 .where(t.catalogName == catalog_ and
458 t.tableNamespace == std::string(from_ns) and
459 t.tableName == std::string(from_name) and
460 TableRecordFilter(t)))
464 if (!status.has_value())
return std::unexpected(status.error());
470 return source_.RunInTransaction(body);
471 }
catch (
const std::exception& e) {
472 return Translate(e,
"transaction");
477 static constexpr std::string_view kTableRecordType =
"TABLE";
479 auto TableRecordFilter(
const IcebergTables& t)
const {
480 return t.recordType == std::string(kTableRecordType) or t.recordType.is_null();
485 static constexpr std::string_view kCreateTablesSql =
486 "CREATE TABLE IF NOT EXISTS iceberg_tables ("
487 "catalog_name VARCHAR(255) NOT NULL, "
488 "table_namespace VARCHAR(255) NOT NULL, "
489 "table_name VARCHAR(255) NOT NULL, "
490 "metadata_location VARCHAR(1000), "
491 "previous_metadata_location VARCHAR(1000), "
492 "iceberg_type VARCHAR(5), "
493 "PRIMARY KEY (catalog_name, table_namespace, table_name))";
495 static constexpr std::string_view kAddRecordTypeSql =
496 "ALTER TABLE iceberg_tables ADD COLUMN iceberg_type VARCHAR(5)";
498 static constexpr std::string_view kCreateNamespacePropertiesSql =
499 "CREATE TABLE IF NOT EXISTS iceberg_namespace_properties ("
500 "catalog_name VARCHAR(255) NOT NULL, "
501 "namespace VARCHAR(255) NOT NULL, "
502 "property_key VARCHAR(255) NOT NULL, "
503 "property_value VARCHAR(1000), "
504 "PRIMARY KEY (catalog_name, namespace, property_key))";
507 template <
typename Fn>
508 Status Guard(std::string_view op, Fn&& fn) {
510 std::forward<Fn>(fn)();
512 }
catch (
const std::exception& e) {
513 return Translate(e, op);
517 Status Translate(
const std::exception& e, std::string_view op)
const {
518 if (Traits::IsUniqueViolation(e)) {
519 return AlreadyExists(
"{}: unique constraint violation: {}", op, e.what());
521 return IOError(
"{}: {}", op, e.what());
524 bool IsDuplicateColumn(
const std::exception& e)
const {
525 if constexpr (
requires { Traits::IsDuplicateColumn(e); }) {
526 return Traits::IsDuplicateColumn(e);
531 ConnectionSource source_;
532 std::string catalog_;
Semantic, driver-agnostic storage interface for the SQL catalog.
Definition catalog_store.h:72
Definition catalog_store_sqlpp23_internal.h:94
Definition catalog_store_sqlpp23_internal.h:57
CatalogStore backed by a concrete sqlpp23 connection source.
Definition catalog_store_sqlpp23_internal.h:214
Result< int64_t > RenameTable(std::string_view from_ns, std::string_view from_name, std::string_view to_ns, std::string_view to_name) override
Move a table row to a new namespace and/or name.
Definition catalog_store_sqlpp23_internal.h:447
Result< std::vector< std::string > > ListTableNames(std::string_view ns) override
Return the table names stored directly under ns.
Definition catalog_store_sqlpp23_internal.h:329
Result< int64_t > UpdateTableMetadataLocation(std::string_view ns, std::string_view name, std::string_view new_location, std::string_view new_previous_location, std::string_view expected_current_location) override
Conditionally update a table's metadata location (optimistic CAS).
Definition catalog_store_sqlpp23_internal.h:404
Status RunInTransaction(const std::function< Status()> &body) override
Execute body atomically inside a single transaction.
Definition catalog_store_sqlpp23_internal.h:468
Status Initialize() override
Create the backing tables if they do not already exist.
Definition catalog_store_sqlpp23_internal.h:221
Status InsertNamespaceProperty(std::string_view ns, std::string_view key, std::optional< std::string_view > value) override
Insert one property row for ns.
Definition catalog_store_sqlpp23_internal.h:287
Result< std::vector< std::string > > ListNamespaceNames() override
Return the distinct namespace identifiers known to this catalog.
Definition catalog_store_sqlpp23_internal.h:239
Status InsertTable(std::string_view ns, std::string_view name, std::string_view metadata_location) override
Insert a new table row with the given metadata location.
Definition catalog_store_sqlpp23_internal.h:390
Result< int64_t > DeleteNamespace(std::string_view ns) override
Delete all property rows for ns.
Definition catalog_store_sqlpp23_internal.h:312
Result< std::optional< std::string > > GetTableMetadataLocation(std::string_view ns, std::string_view name) override
Return the current metadata location of (ns, name).
Definition catalog_store_sqlpp23_internal.h:366
Result< std::vector< NamespaceProperty > > GetNamespaceProperties(std::string_view ns) override
Return all property rows stored for ns.
Definition catalog_store_sqlpp23_internal.h:264
Result< bool > TableExists(std::string_view ns, std::string_view name) override
Whether a row exists for the table (ns, name).
Definition catalog_store_sqlpp23_internal.h:348
Result< int64_t > DeleteTable(std::string_view ns, std::string_view name) override
Delete the table row (ns, name), if present.
Definition catalog_store_sqlpp23_internal.h:430
Status DeleteNamespaceProperty(std::string_view ns, std::string_view key) override
Delete the property row identified by (ns, key), if present.
Definition catalog_store_sqlpp23_internal.h:301
A namespace property row.
Definition catalog_store.h:44
std::string key
Property key.
Definition catalog_store.h:46