C++-API
Installation Um die DuckDB-C++-API zu nutzen, laden Sie das
libduckdb-Archiv für Ihre Plattform herunter.Die aktuelle stabile Version der DuckDB-C++-API ist 1.5.5.
Warnung Die C++-API von DuckDB ist intern. Sie ist nicht als stabil garantiert und kann ohne Ankündigung geändert werden. Wenn Sie eine Anwendung auf DuckDB aufbauen möchten, empfehlen wir die C-API.
Installation
Die DuckDB-C++-API kann als Teil der libduckdb-Pakete installiert werden. Einzelheiten finden Sie auf der Installationsseite.
Grundlegende API-Verwendung
DuckDB implementiert eine eigene C++-API. Sie basiert auf den Abstraktionen einer Datenbankinstanz (Klasse DuckDB), mehrerer Connections zur Datenbankinstanz und QueryResult-Instanzen als Ergebnis von Abfragen. Die Header-Datei der C++-API ist duckdb.hpp.
Start und Beenden
Um DuckDB zu nutzen, müssen Sie zuerst eine DuckDB-Instanz mit ihrem Konstruktor initialisieren. DuckDB() nimmt als Parameter die Datenbankdatei, aus der gelesen und in die geschrieben wird. Der besondere Wert nullptr erzeugt eine In-Memory-Datenbank. Beachten Sie, dass bei einer In-Memory-Datenbank keine Daten auf die Festplatte persistiert werden (d. h. alle Daten gehen verloren, wenn Sie den Prozess beenden). Der zweite Parameter des DuckDB-Konstruktors ist ein optionales Objekt DBConfig. In DBConfig können Sie verschiedene Datenbankparameter setzen, zum Beispiel den Lese-/Schreibmodus oder Speichergrenzen. Der DuckDB-Konstruktor kann Ausnahmen werfen, etwa wenn die Datenbankdatei nicht verwendbar ist.
Mit der DuckDB-Instanz können Sie eine oder mehrere Connection-Instanzen mit dem Konstruktor Connection() erzeugen. Verbindungen sollten zwar threadsicher sein, werden aber während einer Abfrage gesperrt. Es wird daher empfohlen, dass jeder Thread seine eigene Verbindung verwendet, wenn Sie in einer multithreaded Umgebung arbeiten.
DuckDB db(nullptr);Connection con(db);Abfragen
Verbindungen stellen die Methode Query() bereit, um eine SQL-Abfragezeichenkette von C++ an DuckDB zu senden. Query() materialisiert das Abfrageergebnis vollständig als MaterializedQueryResult im Speicher, bevor sie zurückkehrt; danach kann das Abfrageergebnis verarbeitet werden. Es gibt auch eine Streaming-API für Abfragen, siehe weiter unten.
// create a tablecon.Query("CREATE TABLE integers (i INTEGER, j INTEGER)");
// insert three rows into the tablecon.Query("INSERT INTO integers VALUES (3, 4), (5, 6), (7, NULL)");
auto result = con.Query("SELECT * FROM integers");if (result->HasError()) { cerr << result->GetError() << endl;} else { cout << result->ToString() << endl;}Die Instanz MaterializedQueryResult enthält zunächst zwei Felder, die anzeigen, ob die Abfrage erfolgreich war. Query wirft unter normalen Umständen keine Ausnahmen. Stattdessen führen ungültige Abfragen oder andere Probleme dazu, dass das boolesche Feld success in der Ergebnisinstanz auf false gesetzt wird. In diesem Fall kann eine Fehlermeldung als Zeichenkette in error verfügbar sein. Die Methoden GetErrorType() und GetErrorObject() stehen ebenfalls für jede QueryResult-Instanz zur Verfügung und können eine explizitere Fehlerbehandlung unterstützen.
auto result = con.Query("INSERT INTO integers VALUES (1, 2)");if (result->HasError()) { auto errorType = result->GetErrorType(); switch (errorType) { case duckdb::ExceptionType::CONSTRAINT: { // Example handling auto errorObject = result->GetErrorObject(); errorObject.ConvertErrorToJSON(); std::cout << errorObject.Message() << std::endl; break; } // More handling }} else { // Normal code}Bei Erfolg werden weitere Felder gesetzt: der Typ der gerade ausgeführten Anweisung (z. B. StatementType::INSERT_STATEMENT) steht in statement_type. Die High-Level-Typen („Logical type“/„SQL type“) der Ergebnis-Spalten stehen in types. Die Namen der Ergebnis-Spalten stehen im Zeichenkettenvektor names. Werden mehrere Ergebnismengen zurückgegeben, etwa weil die Ergebnismenge mehrere Anweisungen enthielt, kann die Ergebnismenge über das Feld next verkettet werden.
DuckDB unterstützt in der C++-API auch Prepared Statements mit der Methode Prepare(). Diese gibt eine Instanz von PreparedStatement zurück. Diese Instanz kann verwendet werden, um das Prepared Statement mit Parametern auszuführen. Nachfolgend ein Beispiel:
std::unique_ptr<PreparedStatement> prepare = con.Prepare("SELECT count(*) FROM a WHERE i = $1");std::unique_ptr<QueryResult> result = prepare->Execute(12);Warnung Verwenden Sie keine Prepared Statements, um große Datenmengen in DuckDB einzufügen. Bessere Optionen finden Sie in der Dokumentation zum Datenimport.
UDF-API
Die UDF-API ermöglicht die Definition benutzerdefinierter Funktionen. Sie ist in duckdb:Connection über die Methoden CreateScalarFunction(), CreateVectorizedFunction() und Varianten verfügbar.
Diese Methoden erzeugen UDFs im temporären Schema (TEMP_SCHEMA) der besitzenden Verbindung, die als einzige sie verwenden und ändern darf.
CreateScalarFunction
Der Benutzer kann eine gewöhnliche skalare Funktion schreiben und CreateScalarFunction() aufrufen, um die UDF zu registrieren und sie anschließend zum Beispiel in einer SELECT-Anweisung zu verwenden:
bool bigger_than_four(int value) { return value > 4;}
connection.CreateScalarFunction<bool, int>("bigger_than_four", &bigger_than_four);
connection.Query("SELECT bigger_than_four(i) FROM (VALUES (3), (5)) tbl(i)")->Print();Die Methoden CreateScalarFunction() erzeugen automatisch vektorisierte skalare UDFs, sodass sie so effizient sind wie eingebaute Funktionen. Es gibt zwei Varianten dieser Methodenschnittstelle:
1.
template<typename TR, typename... Args>void CreateScalarFunction(string name, TR (*udf_func)(Args…))- Template-Parameter:
- TR ist der Rückgabetyp der UDF-Funktion.
- Args sind die Argumente bis zu 3 für die UDF-Funktion (diese Methode unterstützt nur bis zu ternären Funktionen).
- name ist der Name, unter dem die UDF-Funktion registriert wird.
- udf_func ist ein Zeiger auf die UDF-Funktion.
Diese Methode ermittelt automatisch aus den Template-Typnamen die entsprechenden LogicalTypes:
bool→LogicalType::BOOLEANint8_t→LogicalType::TINYINTint16_t→LogicalType::SMALLINTint32_t→LogicalType::INTEGERint64_t→LogicalType::BIGINTfloat→LogicalType::FLOATdouble→LogicalType::DOUBLEstring_t→LogicalType::VARCHAR
In DuckDB werden einige primitive Typen, z. B. int32_t, auf denselben LogicalType abgebildet: INTEGER, TIME und DATE. Zur Disambiguierung können Benutzer die folgende überladene Methode verwenden.
2.
template<typename TR, typename... Args>void CreateScalarFunction(string name, vector<LogicalType> args, LogicalType ret_type, TR (*udf_func)(Args…))Ein Verwendungsbeispiel wäre:
int32_t udf_date(int32_t a) { return a;}
con.Query("CREATE TABLE dates (d DATE)");con.Query("INSERT INTO dates VALUES ('1992-01-01')");
con.CreateScalarFunction<int32_t, int32_t>("udf_date", {LogicalType::DATE}, LogicalType::DATE, &udf_date);
con.Query("SELECT udf_date(d) FROM dates")->Print();- Template-Parameter:
- TR ist der Rückgabetyp der UDF-Funktion.
- Args sind die Argumente bis zu 3 für die UDF-Funktion (diese Methode unterstützt nur bis zu ternären Funktionen).
- name ist der Name, unter dem die UDF-Funktion registriert wird.
- args sind die LogicalType-Argumente, die die Funktion verwendet, und sollten mit den Template-Typen Args übereinstimmen.
- ret_type ist der LogicalType des Rückgabewerts der Funktion und sollte mit dem Template-Typ TR übereinstimmen.
- udf_func ist ein Zeiger auf die UDF-Funktion.
Diese Funktion prüft die Template-Typen gegen die als Argumente übergebenen LogicalTypes; sie müssen wie folgt übereinstimmen:
- LogicalTypeId::BOOLEAN → bool
- LogicalTypeId::TINYINT → int8_t
- LogicalTypeId::SMALLINT → int16_t
- LogicalTypeId::DATE, LogicalTypeId::TIME, LogicalTypeId::INTEGER → int32_t
- LogicalTypeId::BIGINT, LogicalTypeId::TIMESTAMP → int64_t
- LogicalTypeId::FLOAT, LogicalTypeId::DOUBLE, LogicalTypeId::DECIMAL → double
- LogicalTypeId::VARCHAR, LogicalTypeId::CHAR, LogicalTypeId::BLOB → string_t
- LogicalTypeId::VARBINARY → blob_t
CreateVectorizedFunction
Die Methoden CreateVectorizedFunction() registrieren eine vektorisierte UDF, zum Beispiel:
/** This vectorized function copies the input values to the result vector*/template<typename TYPE>static void udf_vectorized(DataChunk &args, ExpressionState &state, Vector &result) { // set the result vector type result.vector_type = VectorType::FLAT_VECTOR; // get a raw array from the result auto result_data = FlatVector::GetData<TYPE>(result);
// get the solely input vector auto &input = args.data[0]; // now get an orrified vector VectorData vdata; input.Orrify(args.size(), vdata);
// get a raw array from the orrified input auto input_data = (TYPE *)vdata.data;
// handling the data for (idx_t i = 0; i < args.size(); i++) { auto idx = vdata.sel->get_index(i); if ((*vdata.nullmask)[idx]) { continue; } result_data[i] = input_data[idx]; }}
con.Query("CREATE TABLE integers (i INTEGER)");con.Query("INSERT INTO integers VALUES (1), (2), (3), (999)");
con.CreateVectorizedFunction<int, int>("udf_vectorized_int", &&udf_vectorized<int>);
con.Query("SELECT udf_vectorized_int(i) FROM integers")->Print();Die vektorisierte UDF ist ein Zeiger vom Typ scalar_function_t:
typedef std::function<void(DataChunk &args, ExpressionState &expr, Vector &result)> scalar_function_t;- args ist ein DataChunk, der eine Menge von Eingabevektoren für die UDF hält, die alle dieselbe Länge haben.
- expr ist ein ExpressionState, der Informationen zum Ausdruckszustand der Abfrage liefert.
- result ist ein Vector zum Speichern der Ergebniswerte.
In einer vektorisierten UDF gibt es verschiedene Vektortypen zu behandeln:
- ConstantVector
- DictionaryVector
- FlatVector
- ListVector
- StringVector
- StructVector
- SequenceVector
Die allgemeine API der Methode CreateVectorizedFunction() ist wie folgt:
1.
template<typename TR, typename... Args>void CreateVectorizedFunction(string name, scalar_function_t udf_func, LogicalType varargs = LogicalType::INVALID)- Template-Parameter:
- TR ist der Rückgabetyp der UDF-Funktion.
- Args sind die Argumente bis zu 3 für die UDF-Funktion.
- name ist der Name, unter dem die UDF-Funktion registriert wird.
- udf_func ist eine vektorisierte UDF-Funktion.
- varargs Der Typ der zu unterstützenden varargs, oder LogicalTypeId::INVALID (Standardwert), wenn die Funktion keine Argumente variabler Länge akzeptiert.
Diese Methode ermittelt automatisch aus den Template-Typnamen die entsprechenden LogicalTypes:
bool→LogicalType::BOOLEANint8_t→LogicalType::TINYINTint16_t→LogicalType::SMALLINTint32_t→LogicalType::INTEGERint64_t→LogicalType::BIGINTfloat→LogicalType::FLOATdouble→LogicalType::DOUBLEstring_t→LogicalType::VARCHAR
2.
template<typename TR, typename... Args>void CreateVectorizedFunction(string name, vector<LogicalType> args, LogicalType ret_type, scalar_function_t udf_func, LogicalType varargs = LogicalType::INVALID)