Skip to content

Commit bd3462d

Browse files
authored
Merge branch 'main' into write
2 parents 20169b3 + 00b7758 commit bd3462d

29 files changed

+1586
-881
lines changed

.clang-format

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
BasedOnStyle: LLVM
3+
SortIncludes: false
4+
TabWidth: 4
5+
IndentWidth: 4
6+
ColumnLimit: 120
7+
AllowShortFunctionsOnASingleLine: false
8+
---
9+
UseTab: ForIndentation
10+
DerivePointerAlignment: false
11+
PointerAlignment: Right
12+
AlignConsecutiveMacros: true
13+
AlignTrailingComments: true
14+
AllowAllArgumentsOnNextLine: true
15+
AllowAllConstructorInitializersOnNextLine: true
16+
AllowAllParametersOfDeclarationOnNextLine: true
17+
AlignAfterOpenBracket: Align
18+
SpaceBeforeCpp11BracedList: true
19+
SpaceBeforeCtorInitializerColon: true
20+
SpaceBeforeInheritanceColon: true
21+
SpacesInAngles: false
22+
SpacesInCStyleCastParentheses: false
23+
SpacesInConditionalStatement: false
24+
AllowShortLambdasOnASingleLine: Inline
25+
AllowShortLoopsOnASingleLine: false
26+
AlwaysBreakTemplateDeclarations: Yes
27+
IncludeBlocks: Regroup
28+
Language: Cpp
29+
AccessModifierOffset: -4
30+
---
31+
Language: Java
32+
SpaceAfterCStyleCast: true
33+
---

.github/workflows/MainDistributionPipeline.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ jobs:
1616
name: Build extension binaries
1717
uses: duckdb/extension-ci-tools/.github/workflows/_extension_distribution.yml@main
1818
with:
19-
duckdb_version: main
20-
ci_tools_version: main
2119
extension_name: httpfs
20+
duckdb_version: v1.3.1
21+
ci_tools_version: main
2222

2323
duckdb-stable-deploy:
2424
name: Deploy extension binaries
@@ -27,6 +27,6 @@ jobs:
2727
secrets: inherit
2828
with:
2929
extension_name: httpfs
30-
duckdb_version: main
30+
duckdb_version: v1.3.1
3131
ci_tools_version: main
3232
deploy_latest: ${{ startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' }}

.github/workflows/MinioTests.yml

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
name: Minio Tests
2+
on: [push, pull_request,repository_dispatch]
3+
concurrency:
4+
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || '' }}-${{ github.base_ref || '' }}-${{ github.ref != 'refs/heads/main' || github.sha }}
5+
cancel-in-progress: true
6+
defaults:
7+
run:
8+
shell: bash
9+
10+
jobs:
11+
minio-tests:
12+
name: Minio Tests
13+
runs-on: ubuntu-24.04
14+
env:
15+
S3_TEST_SERVER_AVAILABLE: 1
16+
AWS_DEFAULT_REGION: eu-west-1
17+
AWS_ACCESS_KEY_ID: minio_duckdb_user
18+
AWS_SECRET_ACCESS_KEY: minio_duckdb_user_password
19+
DUCKDB_S3_ENDPOINT: duckdb-minio.com:9000
20+
DUCKDB_S3_USE_SSL: false
21+
GEN: ninja
22+
VCPKG_TARGET_TRIPLET: x64-linux
23+
24+
steps:
25+
- uses: actions/checkout@v4
26+
with:
27+
fetch-depth: 0
28+
submodules: 'true'
29+
30+
- uses: actions/setup-python@v4
31+
with:
32+
python-version: '3.10'
33+
34+
- name: Install Ninja
35+
shell: bash
36+
run: sudo apt-get update -y -qq && sudo apt-get install -y -qq ninja-build
37+
38+
- name: Setup Ccache
39+
uses: hendrikmuhs/ccache-action@main
40+
with:
41+
key: ${{ github.job }}
42+
save: ${{ github.ref == 'refs/heads/main' || github.repository != 'duckdb/duckdb' }}
43+
44+
- name: Setup vcpkg
45+
uses: lukka/run-vcpkg@v11.1
46+
with:
47+
vcpkgGitCommitId: 5e5d0e1cd7785623065e77eff011afdeec1a3574
48+
49+
- name: Build
50+
shell: bash
51+
run: make
52+
53+
- name: Start S3/HTTP test server
54+
shell: bash
55+
run: |
56+
cd duckdb
57+
mkdir data/attach_test
58+
touch data/attach_test/attach.db
59+
sudo ./scripts/install_s3_test_server.sh
60+
source ./scripts/run_s3_test_server.sh
61+
sleep 30
62+
63+
- name: Test
64+
shell: bash
65+
run: |
66+
make test

CMakeLists.txt

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ project(HTTPFsExtension)
66

77
add_extension_definitions()
88

9-
include_directories(extension/httpfs/include ${DUCKDB_MODULE_BASE_DIR}/third_party/httplib)
9+
include_directories(extension/httpfs/include
10+
${DUCKDB_MODULE_BASE_DIR}/third_party/httplib)
1011

1112
build_static_extension(
1213
httpfs
1314
extension/httpfs/hffs.cpp
1415
extension/httpfs/s3fs.cpp
1516
extension/httpfs/httpfs.cpp
17+
extension/httpfs/httpfs_client.cpp
1618
extension/httpfs/http_state.cpp
1719
extension/httpfs/crypto.cpp
1820
extension/httpfs/create_secret_functions.cpp
@@ -22,10 +24,10 @@ set(PARAMETERS "-warnings")
2224
build_loadable_extension(
2325
httpfs
2426
${PARAMETERS}
25-
extension/httpfs/httpfs
2627
extension/httpfs/hffs.cpp
2728
extension/httpfs/s3fs.cpp
2829
extension/httpfs/httpfs.cpp
30+
extension/httpfs/httpfs_client.cpp
2931
extension/httpfs/http_state.cpp
3032
extension/httpfs/crypto.cpp
3133
extension/httpfs/create_secret_functions.cpp
@@ -38,16 +40,17 @@ endif()
3840
find_package(OpenSSL REQUIRED)
3941
include_directories(${OPENSSL_INCLUDE_DIR})
4042
if(EMSCRIPTEN)
41-
else()
42-
target_link_libraries(httpfs_loadable_extension duckdb_mbedtls
43-
${OPENSSL_LIBRARIES})
44-
target_link_libraries(httpfs_extension duckdb_mbedtls ${OPENSSL_LIBRARIES})
4543

46-
if(MINGW)
47-
find_package(ZLIB)
48-
target_link_libraries(httpfs_loadable_extension ZLIB::ZLIB -lcrypt32)
49-
target_link_libraries(httpfs_extension ZLIB::ZLIB -lcrypt32)
50-
endif()
44+
else()
45+
target_link_libraries(httpfs_loadable_extension duckdb_mbedtls
46+
${OPENSSL_LIBRARIES})
47+
target_link_libraries(httpfs_extension duckdb_mbedtls ${OPENSSL_LIBRARIES})
48+
49+
if(MINGW)
50+
find_package(ZLIB)
51+
target_link_libraries(httpfs_loadable_extension ZLIB::ZLIB -lcrypt32)
52+
target_link_libraries(httpfs_extension ZLIB::ZLIB -lcrypt32)
53+
endif()
5154
endif()
5255

5356
install(

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,7 @@ PROJ_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
44
EXT_NAME=httpfs
55
EXT_CONFIG=${PROJ_DIR}extension_config.cmake
66

7+
CORE_EXTENSIONS=''
8+
79
# Include the Makefile from extension-ci-tools
810
include extension-ci-tools/makefiles/duckdb_extension.Makefile

duckdb

Submodule duckdb updated 3898 files

extension/httpfs/create_secret_functions.cpp

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,25 @@ namespace duckdb {
77

88
void CreateS3SecretFunctions::Register(DatabaseInstance &instance) {
99
RegisterCreateSecretFunction(instance, "s3");
10+
RegisterCreateSecretFunction(instance, "aws");
1011
RegisterCreateSecretFunction(instance, "r2");
1112
RegisterCreateSecretFunction(instance, "gcs");
1213
}
1314

15+
static Value MapToStruct(const Value &map) {
16+
auto children = MapValue::GetChildren(map);
17+
18+
child_list_t<Value> struct_fields;
19+
for (const auto &kv_child : children) {
20+
auto kv_pair = StructValue::GetChildren(kv_child);
21+
if (kv_pair.size() != 2) {
22+
throw InvalidInputException("Invalid input passed to refresh_info");
23+
}
24+
25+
struct_fields.push_back({kv_pair[0].ToString(), kv_pair[1]});
26+
}
27+
return Value::STRUCT(struct_fields);
28+
}
1429
unique_ptr<BaseSecret> CreateS3SecretFunctions::CreateSecretFunctionInternal(ClientContext &context,
1530
CreateSecretInput &input) {
1631
// Set scope to user provided scope or the default
@@ -25,6 +40,8 @@ unique_ptr<BaseSecret> CreateS3SecretFunctions::CreateSecretFunctionInternal(Cli
2540
} else if (input.type == "gcs") {
2641
scope.push_back("gcs://");
2742
scope.push_back("gs://");
43+
} else if (input.type == "aws") {
44+
scope.push_back("");
2845
} else {
2946
throw InternalException("Unknown secret type found in httpfs extension: '%s'", input.type);
3047
}
@@ -39,6 +56,8 @@ unique_ptr<BaseSecret> CreateS3SecretFunctions::CreateSecretFunctionInternal(Cli
3956
secret->secret_map["url_style"] = "path";
4057
}
4158

59+
bool refresh = false;
60+
4261
// apply any overridden settings
4362
for (const auto &named_param : input.options) {
4463
auto lower_name = StringUtil::Lower(named_param.first);
@@ -61,6 +80,8 @@ unique_ptr<BaseSecret> CreateS3SecretFunctions::CreateSecretFunctionInternal(Cli
6180
lower_name, named_param.second.type().ToString());
6281
}
6382
secret->secret_map["use_ssl"] = Value::BOOLEAN(named_param.second.GetValue<bool>());
83+
} else if (lower_name == "kms_key_id") {
84+
secret->secret_map["kms_key_id"] = named_param.second.ToString();
6485
} else if (lower_name == "url_compatibility_mode") {
6586
if (named_param.second.type() != LogicalType::BOOLEAN) {
6687
throw InvalidInputException("Invalid type past to secret option: '%s', found '%s', expected: 'BOOLEAN'",
@@ -69,14 +90,86 @@ unique_ptr<BaseSecret> CreateS3SecretFunctions::CreateSecretFunctionInternal(Cli
6990
secret->secret_map["url_compatibility_mode"] = Value::BOOLEAN(named_param.second.GetValue<bool>());
7091
} else if (lower_name == "account_id") {
7192
continue; // handled already
93+
} else if (lower_name == "refresh") {
94+
if (refresh) {
95+
throw InvalidInputException("Can not set `refresh` and `refresh_info` at the same time");
96+
}
97+
refresh = named_param.second.GetValue<string>() == "auto";
98+
secret->secret_map["refresh"] = Value("auto");
99+
child_list_t<Value> struct_fields;
100+
for (const auto &named_param : input.options) {
101+
auto lower_name = StringUtil::Lower(named_param.first);
102+
struct_fields.push_back({lower_name, named_param.second});
103+
}
104+
secret->secret_map["refresh_info"] = Value::STRUCT(struct_fields);
105+
} else if (lower_name == "refresh_info") {
106+
if (refresh) {
107+
throw InvalidInputException("Can not set `refresh` and `refresh_info` at the same time");
108+
}
109+
refresh = true;
110+
secret->secret_map["refresh_info"] = MapToStruct(named_param.second);
72111
} else {
73-
throw InternalException("Unknown named parameter passed to CreateSecretFunctionInternal: " + lower_name);
112+
throw InvalidInputException("Unknown named parameter passed to CreateSecretFunctionInternal: " +
113+
lower_name);
74114
}
75115
}
76116

77117
return std::move(secret);
78118
}
79119

120+
CreateSecretInput CreateS3SecretFunctions::GenerateRefreshSecretInfo(const SecretEntry &secret_entry,
121+
Value &refresh_info) {
122+
const auto &kv_secret = dynamic_cast<const KeyValueSecret &>(*secret_entry.secret);
123+
124+
CreateSecretInput result;
125+
result.on_conflict = OnCreateConflict::REPLACE_ON_CONFLICT;
126+
result.persist_type = SecretPersistType::TEMPORARY;
127+
128+
result.type = kv_secret.GetType();
129+
result.name = kv_secret.GetName();
130+
result.provider = kv_secret.GetProvider();
131+
result.storage_type = secret_entry.storage_mode;
132+
result.scope = kv_secret.GetScope();
133+
134+
auto result_child_count = StructType::GetChildCount(refresh_info.type());
135+
auto refresh_info_children = StructValue::GetChildren(refresh_info);
136+
D_ASSERT(refresh_info_children.size() == result_child_count);
137+
for (idx_t i = 0; i < result_child_count; i++) {
138+
auto &key = StructType::GetChildName(refresh_info.type(), i);
139+
auto &value = refresh_info_children[i];
140+
result.options[key] = value;
141+
}
142+
143+
return result;
144+
}
145+
146+
//! Function that will automatically try to refresh a secret
147+
bool CreateS3SecretFunctions::TryRefreshS3Secret(ClientContext &context, const SecretEntry &secret_to_refresh) {
148+
const auto &kv_secret = dynamic_cast<const KeyValueSecret &>(*secret_to_refresh.secret);
149+
150+
Value refresh_info;
151+
if (!kv_secret.TryGetValue("refresh_info", refresh_info)) {
152+
return false;
153+
}
154+
auto &secret_manager = context.db->GetSecretManager();
155+
auto refresh_input = GenerateRefreshSecretInfo(secret_to_refresh, refresh_info);
156+
157+
// TODO: change SecretManager API to avoid requiring catching this exception
158+
try {
159+
auto res = secret_manager.CreateSecret(context, refresh_input);
160+
auto &new_secret = dynamic_cast<const KeyValueSecret &>(*res->secret);
161+
DUCKDB_LOG_INFO(context, "Successfully refreshed secret: %s, new key_id: %s",
162+
secret_to_refresh.secret->GetName(), new_secret.TryGetValue("key_id").ToString());
163+
return true;
164+
} catch (std::exception &ex) {
165+
ErrorData error(ex);
166+
string new_message = StringUtil::Format("Exception thrown while trying to refresh secret %s. To fix this, "
167+
"please recreate or remove the secret and try again. Error: '%s'",
168+
secret_to_refresh.secret->GetName(), error.Message());
169+
throw Exception(error.Type(), new_message);
170+
}
171+
}
172+
80173
unique_ptr<BaseSecret> CreateS3SecretFunctions::CreateS3SecretFromConfig(ClientContext &context,
81174
CreateSecretInput &input) {
82175
return CreateSecretFunctionInternal(context, input);
@@ -90,8 +183,23 @@ void CreateS3SecretFunctions::SetBaseNamedParams(CreateSecretFunction &function,
90183
function.named_parameters["endpoint"] = LogicalType::VARCHAR;
91184
function.named_parameters["url_style"] = LogicalType::VARCHAR;
92185
function.named_parameters["use_ssl"] = LogicalType::BOOLEAN;
186+
function.named_parameters["kms_key_id"] = LogicalType::VARCHAR;
93187
function.named_parameters["url_compatibility_mode"] = LogicalType::BOOLEAN;
94188

189+
// Whether a secret refresh attempt should be made when the secret appears to be incorrect
190+
function.named_parameters["refresh"] = LogicalType::VARCHAR;
191+
192+
// Refresh Modes
193+
// - auto
194+
// - disabled
195+
// - on_error
196+
// - on_timeout
197+
198+
// - on_use: every time a secret is used, it will refresh.
199+
200+
// Debugging/testing option: it allows specifying how the secret will be refreshed using a manually specfied MAP
201+
function.named_parameters["refresh_info"] = LogicalType::MAP(LogicalType::VARCHAR, LogicalType::VARCHAR);
202+
95203
if (type == "r2") {
96204
function.named_parameters["account_id"] = LogicalType::VARCHAR;
97205
}
@@ -103,6 +211,7 @@ void CreateS3SecretFunctions::RegisterCreateSecretFunction(DatabaseInstance &ins
103211
secret_type.name = type;
104212
secret_type.deserializer = KeyValueSecret::Deserialize<KeyValueSecret>;
105213
secret_type.default_provider = "config";
214+
secret_type.extension = "httpfs";
106215

107216
ExtensionUtil::RegisterSecretType(instance, secret_type);
108217

@@ -117,6 +226,7 @@ void CreateBearerTokenFunctions::Register(DatabaseInstance &instance) {
117226
secret_type_hf.name = HUGGINGFACE_TYPE;
118227
secret_type_hf.deserializer = KeyValueSecret::Deserialize<KeyValueSecret>;
119228
secret_type_hf.default_provider = "config";
229+
secret_type_hf.extension = "httpfs";
120230
ExtensionUtil::RegisterSecretType(instance, secret_type_hf);
121231

122232
// Huggingface config provider

0 commit comments

Comments
 (0)