iceberg-cpp
Loading...
Searching...
No Matches
catalog_store_sqlpp23_internal.h
Go to the documentation of this file.
1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20#pragma once
21
35
36#include <algorithm>
37#include <condition_variable>
38#include <cstdint>
39#include <exception>
40#include <functional>
41#include <mutex>
42#include <optional>
43#include <string>
44#include <string_view>
45#include <utility>
46#include <vector>
47
48#include <sqlpp23/sqlpp23.h>
49
52#include "iceberg/result.h"
53
54namespace iceberg::sql {
55
56template <typename Connection>
58 public:
59 explicit SingleConnectionSource(Connection connection)
60 : connection_(std::move(connection)) {}
61
62 template <typename Fn>
63 decltype(auto) WithConnection(Fn&& fn) {
64 std::lock_guard lock(mutex_);
65 return std::forward<Fn>(fn)(connection_);
66 }
67
68 Status RunInTransaction(const std::function<Status()>& body) {
69 std::lock_guard lock(mutex_);
70 auto transaction = sqlpp::start_transaction(connection_);
71 try {
72 Status status = body();
73 if (status.has_value()) {
74 transaction.commit();
75 } else {
76 transaction.rollback();
77 }
78 return status;
79 } catch (...) {
80 try {
81 transaction.rollback();
82 } catch (...) {
83 }
84 throw;
85 }
86 }
87
88 private:
89 Connection connection_;
90 std::recursive_mutex mutex_;
91};
92
93template <typename Pool>
95 public:
96 using Connection = typename Pool::_pooled_connection_t;
97
98 PooledConnectionSource(Pool pool, int32_t max_connections)
99 : pool_(std::move(pool)), max_connections_(std::max<int32_t>(1, max_connections)) {}
100
101 template <typename Fn>
102 decltype(auto) WithConnection(Fn&& fn) {
103 if (auto* connection = ActiveTransactionConnection()) {
104 return std::forward<Fn>(fn)(*connection);
105 }
106
107 ConnectionLease lease(*this);
108 auto connection = pool_.get();
109 return std::forward<Fn>(fn)(connection);
110 }
111
112 Status RunInTransaction(const std::function<Status()>& body) {
113 if (ActiveTransactionConnection() != nullptr) {
114 return InvalidArgument("Nested SQL catalog transactions are not supported");
115 }
116
117 ConnectionLease lease(*this);
118 auto connection = pool_.get();
119 TransactionContext context{this, &connection};
120 ScopedTransactionContext scope(context);
121
122 auto transaction = sqlpp::start_transaction(connection);
123 try {
124 Status status = body();
125 if (status.has_value()) {
126 transaction.commit();
127 } else {
128 transaction.rollback();
129 }
130 return status;
131 } catch (...) {
132 try {
133 transaction.rollback();
134 } catch (...) {
135 }
136 throw;
137 }
138 }
139
140 private:
141 struct TransactionContext {
143 Connection* connection;
144 };
145
146 class ScopedTransactionContext {
147 public:
148 explicit ScopedTransactionContext(TransactionContext& context)
149 : previous_(active_context_) {
150 active_context_ = &context;
151 }
152
153 ScopedTransactionContext(const ScopedTransactionContext&) = delete;
154 ScopedTransactionContext& operator=(const ScopedTransactionContext&) = delete;
155 ScopedTransactionContext(ScopedTransactionContext&&) = delete;
156 ScopedTransactionContext& operator=(ScopedTransactionContext&&) = delete;
157
158 ~ScopedTransactionContext() { active_context_ = previous_; }
159
160 private:
161 TransactionContext* previous_;
162 };
163
164 class ConnectionLease {
165 public:
166 explicit ConnectionLease(PooledConnectionSource& source) : source_(source) {
167 std::unique_lock lock(source_.mutex_);
168 source_.available_.wait(
169 lock, [&] { return source_.active_connections_ < source_.max_connections_; });
170 ++source_.active_connections_;
171 }
172
173 ConnectionLease(const ConnectionLease&) = delete;
174 ConnectionLease& operator=(const ConnectionLease&) = delete;
175 ConnectionLease(ConnectionLease&&) = delete;
176 ConnectionLease& operator=(ConnectionLease&&) = delete;
177
178 ~ConnectionLease() {
179 {
180 std::lock_guard lock(source_.mutex_);
181 --source_.active_connections_;
182 }
183 source_.available_.notify_one();
184 }
185
186 private:
187 PooledConnectionSource& source_;
188 };
189
190 Connection* ActiveTransactionConnection() {
191 if (active_context_ != nullptr && active_context_->owner == this) {
192 return active_context_->connection;
193 }
194 return nullptr;
195 }
196
197 Pool pool_;
198 const int32_t max_connections_;
199 std::mutex mutex_;
200 std::condition_variable available_;
201 int32_t active_connections_ = 0;
202
203 inline static thread_local TransactionContext* active_context_ = nullptr;
204};
205
213template <typename ConnectionSource, typename Traits>
214class Sqlpp23CatalogStore final : public CatalogStore {
215 public:
216 template <typename... SourceArgs>
217 explicit Sqlpp23CatalogStore(std::string catalog_name, SourceArgs&&... source_args)
218 : source_(std::forward<SourceArgs>(source_args)...),
219 catalog_(std::move(catalog_name)) {}
220
221 Status Initialize() override {
222 return Guard("initialize catalog tables", [&] {
223 source_.WithConnection([&](auto& connection) {
224 connection(std::string(kCreateTablesSql));
225 connection(std::string(kCreateNamespacePropertiesSql));
226 try {
227 connection(std::string(kAddRecordTypeSql));
228 } catch (const std::exception& e) {
229 if (!IsDuplicateColumn(e)) {
230 throw;
231 }
232 }
233 });
234 });
235 }
236
237 // --- Namespaces --------------------------------------------------------
238
239 Result<std::vector<std::string>> ListNamespaceNames() override {
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)
247 .from(namespaces)
248 .where(namespaces.catalogName == catalog_))) {
249 names.emplace_back(row.ns);
250 }
251 for (const auto& row :
252 connection(sqlpp::select(sqlpp::distinct, tables.tableNamespace)
253 .from(tables)
254 .where(tables.catalogName == catalog_ and
255 TableRecordFilter(tables)))) {
256 names.emplace_back(row.tableNamespace);
257 }
258 });
259 });
260 if (!status.has_value()) return std::unexpected(status.error());
261 return names;
262 }
263
264 Result<std::vector<NamespaceProperty>> GetNamespaceProperties(
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)
272 .from(t)
273 .where(t.catalogName == catalog_ and t.ns == std::string(ns)))) {
274 NamespaceProperty property;
275 property.key = std::string(row.propertyKey);
276 if (row.propertyValue.has_value()) {
277 property.value = std::string(*row.propertyValue);
278 }
279 properties.push_back(std::move(property));
280 }
281 });
282 });
283 if (!status.has_value()) return std::unexpected(status.error());
284 return properties;
285 }
286
287 Status InsertNamespaceProperty(std::string_view ns, std::string_view key,
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));
297 });
298 });
299 }
300
301 Status DeleteNamespaceProperty(std::string_view ns, std::string_view key) override {
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)));
308 });
309 });
310 }
311
312 Result<int64_t> DeleteNamespace(std::string_view ns) override {
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)))
320 .affected_rows);
321 });
322 });
323 if (!status.has_value()) return std::unexpected(status.error());
324 return affected;
325 }
326
327 // --- Tables ------------------------------------------------------------
328
329 Result<std::vector<std::string>> ListTableNames(std::string_view ns) override {
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)
336 .from(t)
337 .where(t.catalogName == catalog_ and
338 t.tableNamespace == std::string(ns) and
339 TableRecordFilter(t)))) {
340 names.emplace_back(row.tableName);
341 }
342 });
343 });
344 if (!status.has_value()) return std::unexpected(status.error());
345 return names;
346 }
347
348 Result<bool> TableExists(std::string_view ns, std::string_view name) override {
349 const IcebergTables t{};
350 bool exists = false;
351 auto status = Guard("check table exists", [&] {
352 source_.WithConnection([&](auto& connection) {
353 auto result = connection(sqlpp::select(t.tableName)
354 .from(t)
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();
360 });
361 });
362 if (!status.has_value()) return std::unexpected(status.error());
363 return exists;
364 }
365
366 Result<std::optional<std::string>> GetTableMetadataLocation(
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)
373 .from(t)
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);
382 }
383 }
384 });
385 });
386 if (!status.has_value()) return std::unexpected(status.error());
387 return location;
388 }
389
390 Status InsertTable(std::string_view ns, std::string_view name,
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)));
400 });
401 });
402 }
403
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>(
413 connection(
414 sqlpp::update(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)))
423 .affected_rows);
424 });
425 });
426 if (!status.has_value()) return std::unexpected(status.error());
427 return affected;
428 }
429
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)))
440 .affected_rows);
441 });
442 });
443 if (!status.has_value()) return std::unexpected(status.error());
444 return affected;
445 }
446
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)))
461 .affected_rows);
462 });
463 });
464 if (!status.has_value()) return std::unexpected(status.error());
465 return affected;
466 }
467
468 Status RunInTransaction(const std::function<Status()>& body) override {
469 try {
470 return source_.RunInTransaction(body);
471 } catch (const std::exception& e) {
472 return Translate(e, "transaction");
473 }
474 }
475
476 private:
477 static constexpr std::string_view kTableRecordType = "TABLE";
478
479 auto TableRecordFilter(const IcebergTables& t) const {
480 return t.recordType == std::string(kTableRecordType) or t.recordType.is_null();
481 }
482
483 // Schema compatible with the Apache Iceberg Java JdbcCatalog. sqlpp23 cannot
484 // emit DDL, so these run as plain string statements.
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))";
494
495 static constexpr std::string_view kAddRecordTypeSql =
496 "ALTER TABLE iceberg_tables ADD COLUMN iceberg_type VARCHAR(5)";
497
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))";
505
507 template <typename Fn>
508 Status Guard(std::string_view op, Fn&& fn) {
509 try {
510 std::forward<Fn>(fn)();
511 return {};
512 } catch (const std::exception& e) {
513 return Translate(e, op);
514 }
515 }
516
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());
520 }
521 return IOError("{}: {}", op, e.what());
522 }
523
524 bool IsDuplicateColumn(const std::exception& e) const {
525 if constexpr (requires { Traits::IsDuplicateColumn(e); }) {
526 return Traits::IsDuplicateColumn(e);
527 }
528 return false;
529 }
530
531 ConnectionSource source_;
532 std::string catalog_;
533};
534
535} // namespace iceberg::sql
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