| 1 | #include "duckdb/function/table/system_functions.hpp" |
| 2 | |
| 3 | #include "duckdb/common/file_system.hpp" |
| 4 | #include "duckdb/common/map.hpp" |
| 5 | #include "duckdb/common/string_util.hpp" |
| 6 | #include "duckdb/function/function_set.hpp" |
| 7 | #include "duckdb/main/client_context.hpp" |
| 8 | #include "duckdb/main/database.hpp" |
| 9 | #include "duckdb/main/extension_helper.hpp" |
| 10 | |
| 11 | namespace duckdb { |
| 12 | |
| 13 | struct ExtensionInformation { |
| 14 | string name; |
| 15 | bool loaded = false; |
| 16 | bool installed = false; |
| 17 | string file_path; |
| 18 | string description; |
| 19 | vector<Value> aliases; |
| 20 | }; |
| 21 | |
| 22 | struct DuckDBExtensionsData : public GlobalTableFunctionState { |
| 23 | DuckDBExtensionsData() : offset(0) { |
| 24 | } |
| 25 | |
| 26 | vector<ExtensionInformation> entries; |
| 27 | idx_t offset; |
| 28 | }; |
| 29 | |
| 30 | static unique_ptr<FunctionData> DuckDBExtensionsBind(ClientContext &context, TableFunctionBindInput &input, |
| 31 | vector<LogicalType> &return_types, vector<string> &names) { |
| 32 | names.emplace_back(args: "extension_name" ); |
| 33 | return_types.emplace_back(args: LogicalType::VARCHAR); |
| 34 | |
| 35 | names.emplace_back(args: "loaded" ); |
| 36 | return_types.emplace_back(args: LogicalType::BOOLEAN); |
| 37 | |
| 38 | names.emplace_back(args: "installed" ); |
| 39 | return_types.emplace_back(args: LogicalType::BOOLEAN); |
| 40 | |
| 41 | names.emplace_back(args: "install_path" ); |
| 42 | return_types.emplace_back(args: LogicalType::VARCHAR); |
| 43 | |
| 44 | names.emplace_back(args: "description" ); |
| 45 | return_types.emplace_back(args: LogicalType::VARCHAR); |
| 46 | |
| 47 | names.emplace_back(args: "aliases" ); |
| 48 | return_types.emplace_back(args: LogicalType::LIST(child: LogicalType::VARCHAR)); |
| 49 | |
| 50 | return nullptr; |
| 51 | } |
| 52 | |
| 53 | unique_ptr<GlobalTableFunctionState> DuckDBExtensionsInit(ClientContext &context, TableFunctionInitInput &input) { |
| 54 | auto result = make_uniq<DuckDBExtensionsData>(); |
| 55 | |
| 56 | auto &fs = FileSystem::GetFileSystem(context); |
| 57 | auto &db = DatabaseInstance::GetDatabase(context); |
| 58 | |
| 59 | map<string, ExtensionInformation> installed_extensions; |
| 60 | auto extension_count = ExtensionHelper::DefaultExtensionCount(); |
| 61 | auto alias_count = ExtensionHelper::ExtensionAliasCount(); |
| 62 | for (idx_t i = 0; i < extension_count; i++) { |
| 63 | auto extension = ExtensionHelper::GetDefaultExtension(index: i); |
| 64 | ExtensionInformation info; |
| 65 | info.name = extension.name; |
| 66 | info.installed = extension.statically_loaded; |
| 67 | info.loaded = false; |
| 68 | info.file_path = extension.statically_loaded ? "(BUILT-IN)" : string(); |
| 69 | info.description = extension.description; |
| 70 | for (idx_t k = 0; k < alias_count; k++) { |
| 71 | auto alias = ExtensionHelper::GetExtensionAlias(index: k); |
| 72 | if (info.name == alias.extension) { |
| 73 | info.aliases.emplace_back(args&: alias.alias); |
| 74 | } |
| 75 | } |
| 76 | installed_extensions[info.name] = std::move(info); |
| 77 | } |
| 78 | #ifndef WASM_LOADABLE_EXTENSIONS |
| 79 | // scan the install directory for installed extensions |
| 80 | auto ext_directory = ExtensionHelper::ExtensionDirectory(context); |
| 81 | fs.ListFiles(directory: ext_directory, callback: [&](const string &path, bool is_directory) { |
| 82 | if (!StringUtil::EndsWith(str: path, suffix: ".duckdb_extension" )) { |
| 83 | return; |
| 84 | } |
| 85 | ExtensionInformation info; |
| 86 | info.name = fs.ExtractBaseName(path); |
| 87 | info.loaded = false; |
| 88 | info.file_path = fs.JoinPath(a: ext_directory, path); |
| 89 | auto entry = installed_extensions.find(x: info.name); |
| 90 | if (entry == installed_extensions.end()) { |
| 91 | installed_extensions[info.name] = std::move(info); |
| 92 | } else { |
| 93 | if (!entry->second.loaded) { |
| 94 | entry->second.file_path = info.file_path; |
| 95 | } |
| 96 | entry->second.installed = true; |
| 97 | } |
| 98 | }); |
| 99 | #endif |
| 100 | // now check the list of currently loaded extensions |
| 101 | auto &loaded_extensions = db.LoadedExtensions(); |
| 102 | for (auto &ext_name : loaded_extensions) { |
| 103 | auto entry = installed_extensions.find(x: ext_name); |
| 104 | if (entry == installed_extensions.end()) { |
| 105 | ExtensionInformation info; |
| 106 | info.name = ext_name; |
| 107 | info.loaded = true; |
| 108 | installed_extensions[ext_name] = std::move(info); |
| 109 | } else { |
| 110 | entry->second.loaded = true; |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | result->entries.reserve(n: installed_extensions.size()); |
| 115 | for (auto &kv : installed_extensions) { |
| 116 | result->entries.push_back(x: std::move(kv.second)); |
| 117 | } |
| 118 | return std::move(result); |
| 119 | } |
| 120 | |
| 121 | void DuckDBExtensionsFunction(ClientContext &context, TableFunctionInput &data_p, DataChunk &output) { |
| 122 | auto &data = data_p.global_state->Cast<DuckDBExtensionsData>(); |
| 123 | if (data.offset >= data.entries.size()) { |
| 124 | // finished returning values |
| 125 | return; |
| 126 | } |
| 127 | // start returning values |
| 128 | // either fill up the chunk or return all the remaining columns |
| 129 | idx_t count = 0; |
| 130 | while (data.offset < data.entries.size() && count < STANDARD_VECTOR_SIZE) { |
| 131 | auto &entry = data.entries[data.offset]; |
| 132 | |
| 133 | // return values: |
| 134 | // extension_name LogicalType::VARCHAR |
| 135 | output.SetValue(col_idx: 0, index: count, val: Value(entry.name)); |
| 136 | // loaded LogicalType::BOOLEAN |
| 137 | output.SetValue(col_idx: 1, index: count, val: Value::BOOLEAN(value: entry.loaded)); |
| 138 | // installed LogicalType::BOOLEAN |
| 139 | output.SetValue(col_idx: 2, index: count, val: !entry.installed && entry.loaded ? Value() : Value::BOOLEAN(value: entry.installed)); |
| 140 | // install_path LogicalType::VARCHAR |
| 141 | output.SetValue(col_idx: 3, index: count, val: Value(entry.file_path)); |
| 142 | // description LogicalType::VARCHAR |
| 143 | output.SetValue(col_idx: 4, index: count, val: Value(entry.description)); |
| 144 | // aliases LogicalType::LIST(LogicalType::VARCHAR) |
| 145 | output.SetValue(col_idx: 5, index: count, val: Value::LIST(child_type: LogicalType::VARCHAR, values: entry.aliases)); |
| 146 | |
| 147 | data.offset++; |
| 148 | count++; |
| 149 | } |
| 150 | output.SetCardinality(count); |
| 151 | } |
| 152 | |
| 153 | void DuckDBExtensionsFun::RegisterFunction(BuiltinFunctions &set) { |
| 154 | TableFunctionSet functions("duckdb_extensions" ); |
| 155 | functions.AddFunction(function: TableFunction({}, DuckDBExtensionsFunction, DuckDBExtensionsBind, DuckDBExtensionsInit)); |
| 156 | set.AddFunction(set: functions); |
| 157 | } |
| 158 | |
| 159 | } // namespace duckdb |
| 160 | |