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: 40 additions & 2 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,23 @@

### 用法

#### 1. 输入用于授权的 databaseURI。目前支持 `mysql`、`postgresql`、`sqlite`、`sqlserver`、`oracle`,示例格式如下:
#### 1. 输入用于授权的 databaseURI。目前支持 `mysql`、`postgresql`、`sqlite`、`sqlserver`、`oracle`、`clickhouse`,示例格式如下:
```shell
mysql+pymysql://root:123456@localhost:3306/test
postgresql+psycopg2://postgres:123456@localhost:5432/test
sqlite:///test.db
mssql+pymssql://<username>:<password>@<freetds_name>/?charset=utf8
oracle+oracledb://user:pass@hostname:port[/dbname][?service_name=<service>[&key=value&key=value...]]
clickhouse://default:password@localhost:8123/default
clickhouse+connect://default:password@localhost:8123/mydatabase
```

**ClickHouse/MyScale 特别说明:**
- 支持 ClickHouse 和 MyScale(基于 ClickHouse 的向量数据库)
- 使用 `clickhouse-connect` Python 驱动
- 默认端口:8123(HTTP)、9000(Native)
- MyScale 是一个兼容 ClickHouse 的向量数据库,支持向量相似性搜索功能

> **注意:** 此插件总是在 Docker 中运行,因此 `localhost` 始终指 Docker 内部网络,请尝试使用 `host.docker.internal` 代替。

#### 2. 使用 `SQL 执行` (SQL Execute) 工具从数据库查询数据。
Expand Down Expand Up @@ -55,8 +63,38 @@ URL 请求格式示例:
curl -X POST 'https://daemon-plugin.dify.dev/o3wvwZfYFLU5iGopr5CxYmGaM5mWV7xf/sql' -H 'Content-Type: application/json' -d '{"query":"select * from test", "format": "md"}'
```

#### 7. 如何打包plugin。

Step 1:install homebrew-dify
```shell
brew install langgenius/dify/dify
```

Step 2:install Dify cli
```shell
brew tap langgenius/dify
brew install dify
```

Step 3: 验证dify 安装
```shell
dify --version
```

Step 4:package Dify database plugin
```shell
dify plugin package ./dify-plugin-database
```

### 更新日志

#### 0.0.7
1. 新增 ClickHouse/MyScale 数据库支持
2. 使用 clickhouse-connect 0.10.0+ 驱动
3. 支持所有现有的输出格式(JSON、CSV、YAML、XLSX、HTML、Markdown)
4. 支持表结构获取,包括 ClickHouse 特有的引擎、排序键、分区键等信息
5. 完全兼容 MyScale 向量数据库

#### 0.0.6
1. 支持在 `get table schema` 工具中获取更多信息,例如表和字段的注释、外键关联索引等
2. support special `schema` of `get table schema` tool
Expand Down Expand Up @@ -86,4 +124,4 @@ curl -X POST 'https://daemon-plugin.dify.dev/o3wvwZfYFLU5iGopr5CxYmGaM5mWV7xf/sq


### 加群
![1](_assets/contact.jpg)
![1](_assets/contact.jpg)
2 changes: 1 addition & 1 deletion manifest.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: 0.0.6
version: 0.0.7
type: plugin
author: hjlarry
name: database
Expand Down
42 changes: 34 additions & 8 deletions provider/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,43 @@
from dify_plugin import ToolProvider
from dify_plugin.errors.tool import ToolProviderCredentialValidationError
from tools.sql_execute import SQLExecuteTool
from tools.db_utils import is_clickhouse_uri, parse_clickhouse_uri


class DatabaseProvider(ToolProvider):
def _validate_credentials(self, credentials: dict[str, Any]) -> None:
if not credentials.get("db_uri"):
return
query = "SELECT 1 FROM DUAL" if "oracle" in credentials.get("db_uri") else "SELECT 1"
try:
for _ in SQLExecuteTool.from_credentials(credentials).invoke(
tool_parameters={"query": query}
):
pass
except Exception as e:
raise ToolProviderCredentialValidationError(str(e))

db_uri = credentials.get("db_uri")

# 对于 ClickHouse/MyScale,使用原生验证
if is_clickhouse_uri(db_uri):
try:
import clickhouse_connect

config = parse_clickhouse_uri(db_uri)

# 对于 8443 端口,自动添加 SSL 支持
if config.get('port') == 8443:
config['secure'] = True

# 尝试建立连接并执行测试查询
client = clickhouse_connect.get_client(**config)
client.command("SELECT 1")
client.close()

except ImportError:
raise ToolProviderCredentialValidationError("ClickHouse driver (clickhouse-connect) is not installed")
except Exception as e:
raise ToolProviderCredentialValidationError(f"ClickHouse connection failed: {str(e)}")
else:
# 对于其他数据库,使用原有的验证逻辑
query = "SELECT 1 FROM DUAL" if "oracle" in db_uri else "SELECT 1"
try:
for _ in SQLExecuteTool.from_credentials(credentials).invoke(
tool_parameters={"query": query}
):
pass
except Exception as e:
raise ToolProviderCredentialValidationError(str(e))
8 changes: 4 additions & 4 deletions provider/database.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ extra:
credentials_for_provider:
db_uri:
help:
en_US: For example `mysql+pymysql://<user>:<password>@<host>:<port>/<database>`
zh_Hans: 例如 `mysql+pymysql://<user>:<password>@<host>:<port>/<database>`
en_US: For example `mysql+pymysql://<user>:<password>@<host>:<port>/<database>`, `clickhouse://<user>:<password>@<host>:<port>/<database>`, `myscale://<user>:<password>@<host>:<port>/<database>`
zh_Hans: 例如 `mysql+pymysql://<user>:<password>@<host>:<port>/<database>`、`clickhouse://<user>:<password>@<host>:<port>/<database>`、`myscale://<user>:<password>@<host>:<port>/<database>`
label:
en_US: Database URI
zh_Hans: 数据库 URI
placeholder:
en_US: Please enter the database URI
zh_Hans: 请输入数据库 URI
en_US: Please enter the database URI (supports MySQL, PostgreSQL, SQLite, SQL Server, Oracle, ClickHouse, MyScale)
zh_Hans: 请输入数据库 URI(支持 MySQL、PostgreSQL、SQLite、SQL Server、Oracle、ClickHouse、MyScale)
required: false
type: secret-input
6 changes: 4 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
dify_plugin>=0.1.0,<0.2.0
records[all]~=0.6.0
sqlalchemy~=2.0.0
pymysql~=1.1.1
pymssql~=2.3.2
oracledb~=2.2.1
psycopg2-binary~=2.9.10
cryptography~=44.0.2
pandas~=2.2.3
pandas~=2.2.3
clickhouse-connect>=0.10.0
tabulate>=0.9.0
107 changes: 106 additions & 1 deletion tools/db_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,109 @@ def fix_db_uri_encoding(db_uri: str) -> str:

except Exception:
# 如果解析失败,返回原始 URI
return db_uri
return db_uri


def is_clickhouse_uri(db_uri: str) -> bool:
"""
检查是否为 ClickHouse/MyScale 数据库 URI

Args:
db_uri: 数据库 URI

Returns:
bool: 是否为 ClickHouse URI
"""
return db_uri.startswith(('clickhouse://', 'clickhouse+connect://', 'myscale://', 'myscale+connect://'))


def parse_clickhouse_uri(db_uri: str) -> dict:
"""
解析 ClickHouse/MyScale 连接字符串

Args:
db_uri: ClickHouse/MyScale URI,格式如:
clickhouse://user:password@host:port/database
clickhouse+connect://user:password@host:port/database
myscale://user:password@host:port/database
myscale+connect://user:password@host:port/database

Returns:
dict: 包含连接参数的字典
"""
try:
# 移除协议前缀
if db_uri.startswith('clickhouse+connect://'):
db_uri = db_uri.replace('clickhouse+connect://', '')
elif db_uri.startswith('clickhouse://'):
db_uri = db_uri.replace('clickhouse://', '')
elif db_uri.startswith('myscale+connect://'):
db_uri = db_uri.replace('myscale+connect://', '')
elif db_uri.startswith('myscale://'):
db_uri = db_uri.replace('myscale://', '')

# 解析用户名、密码、主机、端口、数据库
if '@' in db_uri:
auth_part, host_part = db_uri.split('@', 1)

# 解析用户名和密码
if ':' in auth_part:
username, password = auth_part.split(':', 1)
else:
username, password = auth_part, ''

# 解析主机、端口和数据库
if '/' in host_part:
host_db_part = host_part.split('/', 1)
host_port = host_db_part[0]
database = host_db_part[1] if len(host_db_part) > 1 else 'default'
else:
host_port = host_part
database = 'default'

# 解析主机和端口
if ':' in host_port:
host, port = host_port.split(':', 1)
else:
host = host_port
port = 8123 # ClickHouse 默认端口

return {
'host': host,
'port': int(port),
'username': username,
'password': password,
'database': database
}
else:
# 没有认证信息的情况
if '/' in db_uri:
host_db_part = db_uri.split('/', 1)
host_port = host_db_part[0]
database = host_db_part[1] if len(host_db_part) > 1 else 'default'
else:
host_port = db_uri
database = 'default'

if ':' in host_port:
host, port = host_port.split(':', 1)
else:
host = host_port
port = 8123

return {
'host': host,
'port': int(port),
'username': 'default',
'password': '',
'database': database
}
except Exception:
# 解析失败时返回默认配置
return {
'host': 'localhost',
'port': 8123,
'username': 'default',
'password': '',
'database': 'default'
}
Loading