Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,10 @@ jobs:
;;
macOS)
echo "DUCKDB_LIB=libduckdb.dylib" >> "$GITHUB_ENV"
echo "DUCKDB_ARCH=osx-universal" >> "$GITHUB_ENV"
case "${{ runner.arch }}" in
X64|AMD64) echo "DUCKDB_ARCH=osx-amd64" >> "$GITHUB_ENV" ;;
ARM64) echo "DUCKDB_ARCH=osx-arm64" >> "$GITHUB_ENV" ;;
esac
;;
Windows)
echo "DUCKDB_LIB=duckdb.dll" >> "$GITHUB_ENV"
Expand All @@ -182,6 +185,11 @@ jobs:
;;
esac

# Pinned: 2.0.0 renamed release assets (libduckdb-*.zip ->
# duckdb-shared-libs-*.tar.gz) and dropped the amalgamated
# duckdb.hpp, whose headers now come from a source checkout.
echo "DUCKDB_VERSION=v2.0.0" >> "$GITHUB_ENV"

# --- Ladybug artifact arch (maps runner arch to release names) ---
case "${{ runner.arch }}" in
X64|AMD64) echo "LBUG_ARTIFACT_ARCH=x86_64" >> "$GITHUB_ENV" ;;
Expand Down Expand Up @@ -282,10 +290,16 @@ jobs:
- name: Install DuckDB (Linux)
if: runner.os == 'Linux'
run: |
wget "https://github.com/duckdb/duckdb/releases/latest/download/libduckdb-${{ env.DUCKDB_ARCH }}.zip"
unzip "libduckdb-${{ env.DUCKDB_ARCH }}.zip" -d duckdb
cp duckdb/duckdb.h /usr/local/include/
cp duckdb/duckdb.hpp /usr/local/include/
wget "https://github.com/duckdb/duckdb/releases/download/${{ env.DUCKDB_VERSION }}/duckdb-shared-libs-${{ env.DUCKDB_ARCH }}.tar.gz"
mkdir -p duckdb && tar xzf "duckdb-shared-libs-${{ env.DUCKDB_ARCH }}.tar.gz" -C duckdb
# 2.0.0 dropped the amalgamated duckdb.hpp from release assets;
# fetch the C++ headers from source (C headers there are identical
# to the ones shipped in the tarball - verified by diff).
git clone -c advice.detachedHead=false --filter=blob:none --sparse --depth 1 --branch "${{ env.DUCKDB_VERSION }}" https://github.com/duckdb/duckdb duckdb-src
git -C duckdb-src sparse-checkout set src/include
cp -r duckdb-src/src/include/. duckdb/
cp duckdb/duckdb*.h duckdb/duckdb.hpp /usr/local/include/
cp -r duckdb/duckdb /usr/local/include/
cp duckdb/${{ env.DUCKDB_LIB }} /usr/local/lib/
ldconfig
# CMake config so find_package(DuckDB) works
Expand Down
6 changes: 3 additions & 3 deletions azure/src/function/azure_scan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ static std::unique_ptr<TableFuncBindData> bindFunc(main::ClientContext* context,
auto result = connector->executeQuery(query + " LIMIT 1");
std::vector<LogicalType> returnTypes;
std::vector<std::string> returnColumnNames = scanInput->expectedColumnNames;
for (auto type : result->types) {
for (auto type : result->GetTypes()) {
returnTypes.push_back(
duckdb_extension::DuckDBTypeConverter::convertDuckDBType(type.ToString()));
}

if (scanInput->expectedColumnNames.empty()) {
for (auto name : result->names) {
returnColumnNames.push_back(name);
for (auto name : result->GetNames()) {
returnColumnNames.push_back(name.GetIdentifierName());
}
}
DASSERT(returnTypes.size() == returnColumnNames.size());
Expand Down
6 changes: 3 additions & 3 deletions delta/src/function/delta_scan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ static std::unique_ptr<TableFuncBindData> bindFunc(main::ClientContext* context,
auto result = connector->executeQuery(query + " LIMIT 1");
std::vector<LogicalType> returnTypes;
std::vector<std::string> returnColumnNames = scanInput->expectedColumnNames;
for (auto type : result->types) {
for (auto type : result->GetTypes()) {
returnTypes.push_back(
duckdb_extension::DuckDBTypeConverter::convertDuckDBType(type.ToString()));
}

if (scanInput->expectedColumnNames.empty()) {
for (auto name : result->names) {
returnColumnNames.push_back(name);
for (auto name : result->GetNames()) {
returnColumnNames.push_back(name.GetIdentifierName());
}
}
DASSERT(returnTypes.size() == returnColumnNames.size());
Expand Down
54 changes: 37 additions & 17 deletions duckdb/src/catalog/duckdb_catalog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,9 @@ void DuckDBCatalog::createForeignRelTable(const std::string& tableName, bool int

std::string srcTableName, dstTableName;
std::string srcColName, dstColName;
for (auto i = 0u; i < fkResult->RowCount(); i++) {
auto colName = fkResult->GetValue(0, i).GetValue<std::string>();
auto refTable = fkResult->GetValue(1, i).GetValue<std::string>();
for (auto& row : *fkResult) {
auto colName = row.GetValue<std::string>(0);
auto refTable = row.GetValue<std::string>(1);
auto lowerCol = colName;
common::StringUtils::toLower(lowerCol);
if (lowerCol == "src_id" || lowerCol.find("src") == 0) {
Expand Down Expand Up @@ -322,8 +322,12 @@ static bool getTableInfo(const DuckDBConnector& connector, const std::string& ta
if (result->RowCount() == 0) {
return false;
}
if (isPlaceholderSchema(*result)) {
std::unique_ptr<duckdb::MaterializedQueryResult> schemaResult;
auto firstChunk = result->Fetch();
if (!firstChunk || firstChunk->size() == 0) {
return false;
}
if (isPlaceholderSchema(result->GetNames(), *firstChunk, result->RowCount())) {
std::unique_ptr<duckdb::QueryResult> schemaResult;
try {
schemaResult = connector.executeQuery(
std::format("SELECT * FROM {}.{}.{} LIMIT 0", quoteDuckDBIdentifier(catalogName),
Expand All @@ -334,9 +338,12 @@ static bool getTableInfo(const DuckDBConnector& connector, const std::string& ta
}
throw;
}
columnTypes.reserve(schemaResult->types.size());
columnNames = schemaResult->names;
for (auto& type : schemaResult->types) {
columnTypes.reserve(schemaResult->GetTypes().size());
columnNames.reserve(schemaResult->GetNames().size());
for (auto& name : schemaResult->GetNames()) {
columnNames.push_back(name.GetIdentifierName());
}
for (auto& type : schemaResult->GetTypes()) {
try {
columnTypes.push_back(DuckDBTypeConverter::convertDuckDBType(type.ToString()));
} catch (common::BinderException&) {
Expand All @@ -350,17 +357,30 @@ static bool getTableInfo(const DuckDBConnector& connector, const std::string& ta
}
columnTypes.reserve(result->RowCount());
columnNames.reserve(result->RowCount());
for (auto i = 0u; i < result->RowCount(); i++) {
try {
columnTypes.push_back(DuckDBTypeConverter::convertDuckDBType(
result->GetValue(0, i).GetValue<std::string>()));
} catch (common::BinderException& e) {
if (skipUnsupportedTable) {
return false;
// QueryResult is a forward-only stream: the first chunk was already
// fetched above for the placeholder check, so process it first.
auto processChunk = [&](const duckdb::DataChunk& chunk) {
for (auto i = 0u; i < chunk.size(); i++) {
try {
columnTypes.push_back(DuckDBTypeConverter::convertDuckDBType(
chunk.GetValue(0, i).GetValue<std::string>()));
} catch (common::BinderException& e) {
if (skipUnsupportedTable) {
return false;
}
throw;
}
throw;
columnNames.push_back(chunk.GetValue(1, i).GetValue<std::string>());
}
return true;
};
if (!processChunk(*firstChunk)) {
return false;
}
while (auto chunk = result->Fetch()) {
if (chunk->size() != 0 && !processChunk(*chunk)) {
return false;
}
columnNames.push_back(result->GetValue(1, i).GetValue<std::string>());
}
return true;
}
Expand Down
22 changes: 21 additions & 1 deletion duckdb/src/connector/duckdb_connector.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
#include "connector/duckdb_connector.h"

#include <algorithm>
#include <format>

#include "common/exception/binder.h"

namespace lbug {
namespace duckdb_extension {

std::unique_ptr<duckdb::MaterializedQueryResult> DuckDBConnector::executeQuery(
std::unique_ptr<duckdb::QueryResult> DuckDBConnector::executeQuery(
std::string query) const {
DASSERT(instance != nullptr && connection != nullptr);
auto result = connection->Query(query);
Expand All @@ -13,5 +18,20 @@ std::unique_ptr<duckdb::MaterializedQueryResult> DuckDBConnector::executeQuery(
return result;
}

void DuckDBConnector::validateSelectQuery(const std::string& query) const {
DASSERT(instance != nullptr && connection != nullptr);
auto statements = connection->ExtractStatements(query);
const auto isSelectOnly = !statements.empty() &&
std::all_of(statements.begin(), statements.end(), [](const auto& stmt) {
return stmt->type == duckdb::StatementType::SELECT_STATEMENT;
});
if (!isSelectOnly) {
throw common::BinderException{std::format("No fields returned by query \"{}\" - "
"the query must be a SELECT statement that "
"returns at least one column",
query)};
}
}

} // namespace duckdb_extension
} // namespace lbug
19 changes: 12 additions & 7 deletions duckdb/src/connector/duckdb_result_converter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

#include <numeric>

#include "duckdb/common/vector/list_vector.hpp"
#include "duckdb/common/vector/struct_vector.hpp"

#include "common/type_utils.h"
#include "common/types/types.h"

Expand All @@ -13,7 +16,7 @@ using namespace common;
template<typename T>
void convertDuckDBVectorToVector(duckdb::Vector& duckDBVector, ValueVector& result,
uint64_t numValuesToCopy) {
auto duckDBData = (T*)duckDBVector.GetData();
auto duckDBData = (T*)duckdb::FlatVector::GetDataMutable(duckDBVector);
auto validityMasks = duckdb::FlatVector::Validity(duckDBVector);
memcpy(result.getData(), duckDBData, numValuesToCopy * result.getNumBytesPerValue());
for (auto i = 0u; i < numValuesToCopy; i++) {
Expand All @@ -31,7 +34,8 @@ void convertDuckDBVectorToVector<list_entry_t>(duckdb::Vector& duckDBVector, Val
template<>
void convertDuckDBVectorToVector<string_t>(duckdb::Vector& duckDBVector, ValueVector& result,
uint64_t numValuesToCopy) {
auto strs = reinterpret_cast<duckdb::string_t*>(duckDBVector.GetData());
auto strs =
reinterpret_cast<duckdb::string_t*>(duckdb::FlatVector::GetDataMutable(duckDBVector));
auto validityMasks = duckdb::FlatVector::Validity(duckDBVector);
for (auto i = 0u; i < numValuesToCopy; i++) {
result.setNull(i, !validityMasks.RowIsValid(i));
Expand Down Expand Up @@ -63,9 +67,9 @@ void convertDuckDBVectorToVector<list_entry_t>(duckdb::Vector& duckDBVector, Val
} break;
case duckdb::LogicalTypeId::MAP:
case duckdb::LogicalTypeId::LIST: {
memcpy(result.getData(), duckDBVector.GetData(),
memcpy(result.getData(), duckdb::FlatVector::GetData(duckDBVector),
numValuesToCopy * result.getNumBytesPerValue());
auto listEntries = reinterpret_cast<duckdb::list_entry_t*>(duckDBVector.GetData());
auto listEntries = (duckdb::list_entry_t*)duckdb::FlatVector::GetDataMutable(duckDBVector);
for (auto i = 0u; i < numValuesToCopy; i++) {
result.setNull(i, !validityMasks.RowIsValid(i));
if (!result.isNull(i)) {
Expand All @@ -82,7 +86,8 @@ void convertDuckDBVectorToVector<list_entry_t>(duckdb::Vector& duckDBVector, Val
duckdb_conversion_func_t conversion_func;
DuckDBResultConverter::getDuckDBVectorConversionFunc(dataVec->dataType.getPhysicalType(),
conversion_func);
conversion_func(duckdb::ListVector::GetEntry(duckDBVector), *dataVec, numValuesInDataVec);
conversion_func(duckdb::ListVector::GetChildMutable(duckDBVector), *dataVec,
numValuesInDataVec);
}

template<>
Expand All @@ -93,7 +98,7 @@ void convertDuckDBVectorToVector<struct_entry_t>(duckdb::Vector& duckDBVector, V
if (result.dataType.getLogicalTypeID() == LogicalTypeID::UNION) {
// Special handling for union datatype, since duckdb stores tagId as INT8
idx++;
auto duckDBData = (uint8_t*)duckdbChildrenVectors[0]->GetData();
auto duckDBData = duckdb::FlatVector::GetDataMutable(duckdbChildrenVectors[0]);
auto validityMasks = duckdb::FlatVector::Validity(duckDBVector);
for (auto i = 0u; i < numValuesToCopy; i++) {
UnionVector::getTagVector(&result)->setValue(i, (uint16_t)duckDBData[i]);
Expand All @@ -106,7 +111,7 @@ void convertDuckDBVectorToVector<struct_entry_t>(duckdb::Vector& duckDBVector, V
auto fieldVector = StructVector::getFieldVector(&result, idx);
DuckDBResultConverter::getDuckDBVectorConversionFunc(
fieldVector->dataType.getPhysicalType(), conversionFunc);
conversionFunc(*duckdbChildVector, *fieldVector, numValuesToCopy);
conversionFunc(duckdbChildVector, *fieldVector, numValuesToCopy);
}
}

Expand Down
3 changes: 1 addition & 2 deletions duckdb/src/function/duckdb_scan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ std::string DuckDBScanBindData::getDescription() const {
return getSQL();
}

DuckDBScanSharedState::DuckDBScanSharedState(
std::shared_ptr<duckdb::MaterializedQueryResult> queryResult)
DuckDBScanSharedState::DuckDBScanSharedState(std::shared_ptr<duckdb::QueryResult> queryResult)
: function::TableFuncSharedState{queryResult->RowCount()}, queryResult{std::move(queryResult)} {
}

Expand Down
18 changes: 11 additions & 7 deletions duckdb/src/include/catalog/duckdb_schema_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,22 @@ inline std::string quoteDuckDBIdentifier(const std::string& value) {
return result;
}

inline bool isPlaceholderSchema(duckdb::MaterializedQueryResult& result) {
if (result.RowCount() != 1) {
// In DuckDB 2.0.0 QueryResult is a forward-only stream: Fetch() consumes.
// Take the already-fetched first chunk (plus names/row count) instead of
// fetching again so callers can keep streaming the remaining chunks.
inline bool isPlaceholderSchema(const std::vector<duckdb::Identifier>& names,
const duckdb::DataChunk& firstChunk, duckdb::idx_t totalRowCount) {
if (totalRowCount != 1 || firstChunk.size() != 1) {
return false;
}
const auto nameColumn = std::find(result.names.begin(), result.names.end(), "column_name");
const auto typeColumn = std::find(result.names.begin(), result.names.end(), "data_type");
if (nameColumn == result.names.end() || typeColumn == result.names.end()) {
const auto nameColumn = std::find(names.begin(), names.end(), "column_name");
const auto typeColumn = std::find(names.begin(), names.end(), "data_type");
if (nameColumn == names.end() || typeColumn == names.end()) {
return false;
}
return result.GetValue(std::distance(result.names.begin(), nameColumn), 0)
return firstChunk.GetValue(std::distance(names.begin(), nameColumn), 0)
.GetValue<std::string>() == "__" &&
result.GetValue(std::distance(result.names.begin(), typeColumn), 0)
firstChunk.GetValue(std::distance(names.begin(), typeColumn), 0)
.GetValue<std::string>() == "UNKNOWN";
}

Expand Down
8 changes: 7 additions & 1 deletion duckdb/src/include/connector/duckdb_connector.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,13 @@ class DuckDBConnector {
std::move(columnNames), *this);
}

std::unique_ptr<duckdb::MaterializedQueryResult> executeQuery(std::string query) const;
std::unique_ptr<duckdb::QueryResult> executeQuery(std::string query) const;

// DuckDB 2.0 executes the inner query while binding postgres_query, so a
// DDL/DML statement would run (or fail confusingly) during planning.
// Reject anything that is not a plain SELECT up front, preserving the
// historical contract that only column-returning SELECTs are accepted.
void validateSelectQuery(const std::string& query) const;

void initRemoteFSSecrets(main::ClientContext* context) const {
for (auto& fsConfig : httpfs_extension::S3FileSystemConfig::getAvailableConfigs()) {
Expand Down
4 changes: 2 additions & 2 deletions duckdb/src/include/function/duckdb_scan.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,9 @@ struct DuckDBScanBindData : function::TableFuncBindData {
};

struct DuckDBScanSharedState final : function::TableFuncSharedState {
explicit DuckDBScanSharedState(std::shared_ptr<duckdb::MaterializedQueryResult> queryResult);
explicit DuckDBScanSharedState(std::shared_ptr<duckdb::QueryResult> queryResult);

std::shared_ptr<duckdb::MaterializedQueryResult> queryResult;
std::shared_ptr<duckdb::QueryResult> queryResult;
};

function::TableFunction getScanFunction(std::shared_ptr<DuckDBTableScanInfo> scanInfo);
Expand Down
Loading
Loading