Skip to content
Open
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
42 changes: 42 additions & 0 deletions be/src/service/backend_service.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
#include "runtime/routine_load/routine_load_task_executor.h"
#include "runtime/stream_load/stream_load_context.h"
#include "runtime/stream_load/stream_load_recorder.h"
#include "udf/python/python_env.h"
#include "util/arrow/row_batch.h"
#include "util/defer_op.h"
#include "util/runtime_profile.h"
Expand Down Expand Up @@ -1311,5 +1312,46 @@ void BaseBackendService::test_storage_connectivity(TTestStorageConnectivityRespo
response.__set_status(status.to_thrift());
}

void BaseBackendService::get_python_envs(std::vector<TPythonEnvInfo>& result) {
auto& manager = PythonVersionManager::instance();
const auto& envs = manager.get_envs();

std::string env_type_str;
switch (manager.env_type()) {
case PythonEnvType::CONDA:
env_type_str = "conda";
break;
case PythonEnvType::VENV:
env_type_str = "venv";
break;
}

for (const auto& env : envs) {
TPythonEnvInfo info;
info.__set_env_name(env.env_name);
info.__set_full_version(env.python_version.full_version);
info.__set_env_type(env_type_str);
info.__set_base_path(env.python_version.base_path);
info.__set_executable_path(env.python_version.executable_path);
result.push_back(std::move(info));
}
}

void BaseBackendService::get_python_packages(std::vector<TPythonPackageInfo>& result,
const std::string& python_version) {
PythonVersion version;
THROW_IF_ERROR(PythonVersionManager::instance().get_version(python_version, &version));

std::vector<std::pair<std::string, std::string>> packages;
THROW_IF_ERROR(list_installed_packages(version, &packages));

for (const auto& [name, ver] : packages) {
TPythonPackageInfo info;
info.__set_package_name(name);
info.__set_version(ver);
result.push_back(std::move(info));
}
}

#include "common/compile_check_end.h"
} // namespace doris
5 changes: 5 additions & 0 deletions be/src/service/backend_service.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ class BaseBackendService : public BackendServiceIf {
void test_storage_connectivity(TTestStorageConnectivityResponse& response,
const TTestStorageConnectivityRequest& request) override;

void get_python_envs(std::vector<TPythonEnvInfo>& result) override;

void get_python_packages(std::vector<TPythonPackageInfo>& result,
const std::string& python_version) override;

////////////////////////////////////////////////////////////////////////////
// begin cloud backend functions
////////////////////////////////////////////////////////////////////////////
Expand Down
42 changes: 42 additions & 0 deletions be/src/udf/python/python_env.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -288,4 +288,46 @@ Status PythonVersionManager::init(PythonEnvType env_type, const fs::path& python
return Status::OK();
}

Status list_installed_packages(const PythonVersion& version,
std::vector<std::pair<std::string, std::string>>* packages) {
DCHECK(packages != nullptr);
if (!version.is_valid()) {
return Status::InvalidArgument("Invalid python version: {}", version.to_string());
}

// Run pip list --format=json to get installed packages
std::string cmd =
fmt::format("\"{}\" -m pip list --format=json 2>/dev/null", version.executable_path);
FILE* pipe = popen(cmd.c_str(), "r");
if (!pipe) {
return Status::InternalError("Failed to run pip list for python version: {}",
version.full_version);
}

std::string result;
char buf[4096];
while (fgets(buf, sizeof(buf), pipe)) {
result += buf;
}
int ret = pclose(pipe);
if (ret != 0) {
return Status::InternalError(
"pip list failed for python version: {}, exit code: {}, output: {}",
version.full_version, ret, result);
}

// Parse JSON output: [{"name": "pkg", "version": "1.0"}, ...]
// Simple JSON parsing without external library
// Each entry looks like: {"name": "package_name", "version": "1.2.3"}
static std::regex entry_re(
R"REGEX(\{\s*"name"\s*:\s*"([^"]+)"\s*,\s*"version"\s*:\s*"([^"]+)"\s*\})REGEX");
auto begin = std::sregex_iterator(result.begin(), result.end(), entry_re);
auto end = std::sregex_iterator();
for (auto it = begin; it != end; ++it) {
packages->emplace_back((*it)[1].str(), (*it)[2].str());
}

return Status::OK();
}

} // namespace doris
12 changes: 12 additions & 0 deletions be/src/udf/python/python_env.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#pragma once

#include <filesystem>
#include <utility>

#include "common/status.h"

Expand Down Expand Up @@ -90,6 +91,8 @@ class PythonEnvScanner {

Status get_version(const std::string& runtime_version, PythonVersion* version) const;

const std::vector<PythonEnvironment>& get_envs() const { return _envs; }

std::string root_path() const { return _env_root_path.string(); }

virtual PythonEnvType env_type() const = 0;
Expand Down Expand Up @@ -146,12 +149,21 @@ class PythonVersionManager {
return _env_scanner->get_version(runtime_version, version);
}

const std::vector<PythonEnvironment>& get_envs() const { return _env_scanner->get_envs(); }

PythonEnvType env_type() const { return _env_scanner->env_type(); }

std::string to_string() const { return _env_scanner->to_string(); }

private:
std::unique_ptr<PythonEnvScanner> _env_scanner;
};

// List installed pip packages for a given Python version.
// Returns pairs of (package_name, version).
Status list_installed_packages(const PythonVersion& version,
std::vector<std::pair<std::string, std::string>>* packages);

} // namespace doris

namespace std {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ PARAMETER: 'PARAMETER';
PARSED: 'PARSED';
PARTITION: 'PARTITION';
PARTITIONS: 'PARTITIONS';
PACKAGES: 'PACKAGES';
PASSWORD: 'PASSWORD';
PASSWORD_EXPIRE: 'PASSWORD_EXPIRE';
PASSWORD_HISTORY: 'PASSWORD_HISTORY';
Expand All @@ -431,6 +432,7 @@ PLAN: 'PLAN';
PLAY: 'PLAY';
PRIVILEGES: 'PRIVILEGES';
PROCESS: 'PROCESS';
PYTHON: 'PYTHON';
PLUGIN: 'PLUGIN';
PLUGINS: 'PLUGINS';
POLICY: 'POLICY';
Expand Down Expand Up @@ -603,6 +605,7 @@ VAULT: 'VAULT';
VAULTS: 'VAULTS';
VERBOSE: 'VERBOSE';
VERSION: 'VERSION';
VERSIONS: 'VERSIONS';
VIEW: 'VIEW';
VIEWS: 'VIEWS';
WARM: 'WARM';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,8 @@ supportedShowStatement
(FROM |IN) tableName=multipartIdentifier
((FROM | IN) database=multipartIdentifier)? #showIndex
| SHOW WARM UP JOB wildWhere? #showWarmUpJob
| SHOW PYTHON VERSIONS #showPythonVersions
| SHOW PYTHON PACKAGES IN STRING_LITERAL #showPythonPackages
;

supportedLoadStatement
Expand Down Expand Up @@ -2177,6 +2179,7 @@ nonReserved
| PASSWORD_LOCK_TIME
| PASSWORD_REUSE
| PARTITIONS
| PACKAGES
| PATH
| PAUSE
| PERCENT
Expand All @@ -2196,6 +2199,7 @@ nonReserved
| PROFILE
| PROPERTIES
| PROPERTY
| PYTHON
| QUANTILE_STATE
| QUANTILE_UNION
| QUARTER
Expand Down Expand Up @@ -2301,6 +2305,7 @@ nonReserved
| VAULTS
| VERBOSE
| VERSION
| VERSIONS
| VIEW
| VIEWS
| WARM
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,8 @@
import org.apache.doris.nereids.trees.plans.commands.ShowPrivilegesCommand;
import org.apache.doris.nereids.trees.plans.commands.ShowProcCommand;
import org.apache.doris.nereids.trees.plans.commands.ShowProcessListCommand;
import org.apache.doris.nereids.trees.plans.commands.ShowPythonPackagesCommand;
import org.apache.doris.nereids.trees.plans.commands.ShowPythonVersionsCommand;
import org.apache.doris.nereids.trees.plans.commands.ShowQueryProfileCommand;
import org.apache.doris.nereids.trees.plans.commands.ShowQueryStatsCommand;
import org.apache.doris.nereids.trees.plans.commands.ShowQueuedAnalyzeJobsCommand;
Expand Down Expand Up @@ -6487,6 +6489,17 @@ public LogicalPlan visitShowTrash(ShowTrashContext ctx) {
return new ShowTrashCommand();
}

@Override
public LogicalPlan visitShowPythonVersions(DorisParser.ShowPythonVersionsContext ctx) {
return new ShowPythonVersionsCommand();
}

@Override
public LogicalPlan visitShowPythonPackages(DorisParser.ShowPythonPackagesContext ctx) {
String version = stripQuotes(ctx.STRING_LITERAL().getText());
return new ShowPythonPackagesCommand(version);
}

@Override
public LogicalPlan visitAdminCleanTrash(DorisParser.AdminCleanTrashContext ctx) {
if (ctx.ON() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,8 @@ public enum PlanType {
SHOW_PROC_COMMAND,
SHOW_PLUGINS_COMMAND,
SHOW_PRIVILEGES_COMMAND,
SHOW_PYTHON_PACKAGES_COMMAND,
SHOW_PYTHON_VERSIONS_COMMAND,
SHOW_REPLICA_DISTRIBUTION_COMMAND,
SHOW_REPLICA_STATUS_COMMAND,
SHOW_REPOSITORIES_COMMAND,
Expand Down
Loading