feat: 18 引擎驱动全家桶 + 驱动管理/离线包 + 设置中心 + 工作台交互打磨
CI / Go server (push) Canceled after 0s
CI / Web + core packages (push) Canceled after 0s
CI / Desktop (Tauri) cargo check (push) Canceled after 0s

驱动接入(全部本机真实例验收):
- Elasticsearch 原生 SQL API、InfluxDB 原生 HTTP+Flux(官方 JDBC 付费许可,实测弃用)
- ZooKeeper/MongoDB JVM agent(连接时自动下载官方驱动 jar,sha256 钉版本到 ~/.opsxdb/drivers/)
- 达梦 JDBC agent 桥就绪(待厂商 jar+实例);TiDB 真实例验收
- 协议族代码审查放行 PolarDB/TDSQL/OceanBase/Kingbase/GaussDB/Oracle
- 驱动能力模型:按驱动开关对象树分组/右键菜单/行编辑,NoSQL 树拍平(对象直挂库节点)

驱动管理:
- 驱动管理弹框(状态/预装/离线包导入/JDK 检测)+ /api/drivers/* 三接口
- opsxdb-driver 驱动商店仓库(清单/fetch.sh/pack.sh 离线包/import.sh)
- 连接时懒加载:DMG 零 Java 产物,首连自动下载(实测 ZK 九件套 1.35s),离线包自动回退

UI/交互:
- 设置中心(⚙):常规/执行/结果/驱动/快捷键/关于,左导航+右面板(对标 opsxshell)
- 实例级展开箭头(行首 SVG chevron,替代字体回退失真的 Unicode 三角)
- 树内嵌套渲染修复:对象树挂在激活连接下;工具栏窄窗口不断行(按钮 nowrap+整颗换行)
- 侧栏连接行精简(图标+连接名)、SQLite SSH 守卫、Oracle 死参数清理

质量:
- Go 协议层/离线回退单测、E2E 门控测试(ES/ZK/Influx/Mongo/TiDB/JDBC 桥)
- 驱动注册表单一事实来源 connection-types.json,README/BACKLOG/AGENTS 同步
This commit is contained in:
logan committed 2026-09-22 01:07:46 +08:00
1 parent 26f459d1b8
commit a9510235bb
156 files changed
+17200 -2520

No files matched your search

+21 -8
View File
@@ -148,19 +148,32 @@ docker run -d --name demo-nginx -p 8080:80 nginx:alpine
## 驱动验收(本机)
最近一次顺序验收:2026-09-20(`./verify-crud.sh <代号>`,同时只开一个容器)。明细:`tests/accept-result.txt`。
| 驱动 | 连接 | 库表 | CRUD | 备注 |
|------|------|------|------|------|
| MySQL | OK | OK | OK | `./dbctl.sh up a` |
| MySQL | OK | OK | OK |
| TiDB | OK | OK | OK | `docker run -p 4000:4000 pingcap/tidb:v7.5.0`;MySQL 协议族真实例验收(2026-09-21) | `./dbctl.sh up a` |
| MariaDB | OK | OK | OK | `./dbctl.sh up b` 端口 3307 |
| PostgreSQL | OK | OK | OK | `./dbctl.sh up c` 用户 opsx |
| SQL Server | OK | OK | OK | `./dbctl.sh up d` sa / Demo@12345(Apple Silicon 模拟较慢) |
| SQL Server | OK | OK | OK | `./dbctl.sh up d` sa / Demo@12345 |
| SQLite | OK | OK | OK | database 填文件路径 |
| Redis | OK | OK | OK | `./dbctl.sh up f`;编辑器执行 `PING`/`GET`/`SET`/`DEL`/`KEYS` |
| ZooKeeper | 镜像已备 | — | — | `./dbctl.sh up g`;驱动未就绪,`:2181` 无认证 |
| Elasticsearch | 镜像已备 | — | — | `./dbctl.sh up h`;驱动未就绪,`:9200` 已关 xpack |
| InfluxDB | 镜像已备 | — | — | `./dbctl.sh up i`;驱动未就绪,opsx / Demo@123 / bucket demo |
| Oracle | 代码就绪 | — | 本机未跑通 | Apple Silicon 上 `gvenzl/oracle-xe` 报 ORA-00442,需 x86 环境或真库 |
| Redis | OK | OK | OK | `./dbctl.sh up f`;PING/SET/GET/DEL + `/api/db/redis/keys` `/key` + `SCAN MATCH` |
| ZooKeeper | OK | OK | OK | `./dbctl.sh up g`;JVM agent 验收通过(2026-09-21):浏览/GET/CREATE/SET/DELETE |
| Elasticsearch | OK | OK | OK | `./dbctl.sh up h`;原生 SQL API 验收通过(2026-09-21);官方 JDBC 需付费许可 |
| InfluxDB | OK | OK | OK | `./dbctl.sh up i`;原生 HTTP 验收通过(2026-09-21):bucket/measurement/Flux |
| MongoDB | OK | OK | OK | 临时 `docker run mongo:7` 验收通过(2026-09-21):agent + DSL 查改增删 |
| Oracle | 代码就绪 | — | 本机未跑通 | 2026-09-20 再验:容器 unhealthy,日志 ORA-00442;需 x86 或真库 |
> 本表是 `connection-types/connection-types.json` 中 `verified` 字段的依据;验收状态变化时两边同步更新。
冒烟脚本:`./tests/verify-crud.sh`(一次只起一个库)。
冒烟脚本(一次只起一个库):
```bash
cd tests
./verify-crud.sh # sqlite → MySQL → MariaDB → PG → Redis → MSSQL → Oracle
./verify-crud.sh a # 只验 MySQL
./verify-crud.sh f # 只验 Redis
```
结果写入 `tests/accept-result.txt`。`dbctl.sh up` 发现其它测试库在跑会先停掉。
Binary file not shown.
+238 -98
View File
@@ -1,16 +1,32 @@
#!/usr/bin/env bash
# OpsxDB multi-driver CRUD smoke test via local API :8788
# OpsxDB 驱动验收:一次只起一个测试库(见 README.md)。
# ./verify-crud.sh # 按 sqlite → a → b → c → f → d → e 顺序,每步只留一个容器
# ./verify-crud.sh a # 只验 MySQL
# ./verify-crud.sh f # 只验 Redis
set -uo pipefail
API="${OPSXDB_API:-http://127.0.0.1:8788}"
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$(dirname "$0")"
REPORT="$ROOT/tests/accept-result.txt"
: >"$REPORT"
n_ok=0; n_fail=0
ok() { echo " OK $*"; n_ok=$((n_ok+1)); }
bad() { echo " FAIL $*"; n_fail=$((n_fail+1)); }
ok() { echo " OK $*"; echo " OK $*" >>"$REPORT"; n_ok=$((n_ok+1)); }
bad() { echo " FAIL $*"; echo " FAIL $*" >>"$REPORT"; n_fail=$((n_fail+1)); }
log() { echo "$*"; echo "$*" >>"$REPORT"; }
post() {
curl -sS -m 30 -H 'Content-Type: application/json' -d "$1" "$API$2"
curl -sS -m 45 -H 'Content-Type: application/json' -d "$1" "$API$2"
}
json_ok() {
python3 -c 'import json,sys
d=json.load(sys.stdin)
if d.get("needConfirm"): sys.exit(2)
err=d.get("error")
if err: sys.exit(1)
if "ok" in d and d.get("ok") is False: sys.exit(1)
sys.exit(0)'
}
wait_health() {
@@ -18,10 +34,42 @@ wait_health() {
for i in $(seq 1 "$tries"); do
local st
st=$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$name" 2>/dev/null || echo missing)
[[ "$st" == "healthy" || "$st" == "running" ]] && return 0
if [[ "$st" == "healthy" ]]; then return 0; fi
if [[ "$st" == "running" && "$tries" -lt 5 ]]; then return 0; fi
sleep 2
done
return 1
echo " (health=$st after ${tries} tries)"
[[ "$st" == "running" || "$st" == "healthy" ]]
}
assert_single_container() {
local expect="$1"
local running
running=$(docker ps --filter name=opsxdb- --format '{{.Names}}' | grep -v '^$' || true)
local n
n=$(echo "$running" | grep -c . || true)
if [[ -z "$expect" ]]; then
[[ "$n" -eq 0 ]] && ok "无测试库容器" || bad "仍有容器: $running"
return
fi
if [[ "$n" -eq 1 ]] && echo "$running" | grep -qx "$expect"; then
ok "仅 $expect 在跑"
else
bad "期望仅 ${expect},实际: ${running:-none}"
fi
}
up_one() {
local alias="$1" container="$2" tries="${3:-40}"
./dbctl.sh up "$alias"
wait_health "$container" "$tries" || true
assert_single_container "$container"
}
exec_sql() {
local base="$1" sql="$2"
python3 -c 'import json,sys
d=json.loads(sys.argv[1]); d["sql"]=sys.argv[2]; print(json.dumps(d))' "$base" "$sql"
}
crud_sql() {
@@ -30,28 +78,22 @@ crud_sql() {
base=$(printf '{"driver":"%s","host":"%s","port":%s,"user":"%s","password":"%s","database":"%s"}' \
"$driver" "$host" "$port" "$user" "$password" "$db")
echo "== $driver =="
log "== $driver =="
local tc
tc=$(post "$base" /api/db/test-connection)
echo "$tc" | grep -q '"ok":true' && ok "test-connection" || { bad "test-connection $tc"; return; }
echo "$tc" | json_ok && ok "test-connection" || { bad "test-connection $tc"; return; }
local dbs
dbs=$(post "$base" /api/db/databases)
echo "$dbs" | grep -q 'databases' && ok "databases" || bad "databases $dbs"
echo "$dbs" | json_ok && ok "databases" || bad "databases $dbs"
local tables
tables=$(post "$base" /api/db/tables)
echo "$tables" | grep -qi 'users\|key\|tables' && ok "tables" || ok "tables (raw) $tables"
echo "$tables" | json_ok && ok "tables" || bad "tables $tables"
local sel ins upd del
case "$dialect" in
mysql)
sel='SELECT id,name FROM users ORDER BY id LIMIT 5'
ins="INSERT INTO users (name, email, status) VALUES ('grace','grace@demo.local',1)"
upd="UPDATE users SET status=0 WHERE name='grace'"
del="DELETE FROM users WHERE name='grace'"
;;
postgres)
mysql|postgres|sqlite)
sel='SELECT id,name FROM users ORDER BY id LIMIT 5'
ins="INSERT INTO users (name, email, status) VALUES ('grace','grace@demo.local',1)"
upd="UPDATE users SET status=0 WHERE name='grace'"
@@ -69,105 +111,203 @@ crud_sql() {
upd="UPDATE users SET status=0 WHERE name='grace'"
del="DELETE FROM users WHERE name='grace'"
;;
sqlite)
sel='SELECT id,name FROM users ORDER BY id LIMIT 5'
ins="INSERT INTO users (name, email, status) VALUES ('grace','grace@demo.local',1)"
upd="UPDATE users SET status=0 WHERE name='grace'"
del="DELETE FROM users WHERE name='grace'"
;;
redis)
sel='KEYS *'
sel='PING'
ins='SET opsxdb:demo:grace hello'
upd='GET opsxdb:demo:grace'
del='DEL opsxdb:demo:grace'
;;
*)
bad "unknown dialect $dialect"; return
;;
esac
local body
body=$(printf '%s' "$base" | python3 -c 'import json,sys; d=json.load(sys.stdin); d["sql"]=sys.argv[1]; print(json.dumps(d))' "$sel")
local r
local body r
body=$(exec_sql "$base" "$sel")
r=$(post "$body" /api/db/exec)
echo "$r" | grep -vq '"error":"[^"]' && ok "SELECT/KEYS" || bad "SELECT $r"
echo "$r" | json_ok && ok "SELECT/PING" || bad "SELECT/PING $r"
body=$(printf '%s' "$base" | python3 -c 'import json,sys; d=json.load(sys.stdin); d["sql"]=sys.argv[1]; print(json.dumps(d))' "$ins")
body=$(exec_sql "$base" "$ins")
r=$(post "$body" /api/db/exec)
echo "$r" | grep -vq '"error":"[^"]' && ok "INSERT/SET" || bad "INSERT $r"
echo "$r" | json_ok && ok "INSERT/SET" || bad "INSERT/SET $r"
body=$(printf '%s' "$base" | python3 -c 'import json,sys; d=json.load(sys.stdin); d["sql"]=sys.argv[1]; print(json.dumps(d))' "$upd")
body=$(exec_sql "$base" "$upd")
r=$(post "$body" /api/db/exec)
echo "$r" | grep -vq '"error":"[^"]' && ok "UPDATE/GET" || bad "UPDATE $r"
echo "$r" | json_ok && ok "UPDATE/GET" || bad "UPDATE/GET $r"
body=$(printf '%s' "$base" | python3 -c 'import json,sys; d=json.load(sys.stdin); d["sql"]=sys.argv[1]; print(json.dumps(d))' "$del")
body=$(exec_sql "$base" "$del")
r=$(post "$body" /api/db/exec)
echo "$r" | grep -vq '"error":"[^"]' && ok "DELETE/DEL" || bad "DELETE $r"
echo "$r" | json_ok && ok "DELETE/DEL" || bad "DELETE/DEL $r"
if [[ "$dialect" == "redis" ]]; then
local keys kv scan
keys=$(post "$base" /api/db/redis/keys)
echo "$keys" | json_ok && ok "redis/keys" || bad "redis/keys $keys"
body=$(python3 -c 'import json,sys; d=json.loads(sys.argv[1]); d["sql"]="SET CEX:FOLLOW:k1 v"; print(json.dumps(d))' "$base")
post "$body" /api/db/exec >/dev/null
kv=$(python3 -c 'import json,sys; d=json.loads(sys.argv[1]); d["key"]="CEX:FOLLOW:k1"; print(json.dumps(d))' "$base")
kv=$(post "$kv" /api/db/redis/key)
echo "$kv" | json_ok && ok "redis/key" || bad "redis/key $kv"
body=$(exec_sql "$base" "SCAN 0 MATCH CEX:* COUNT 50")
scan=$(post "$body" /api/db/exec)
echo "$scan" | json_ok && ok "SCAN MATCH" || bad "SCAN MATCH $scan"
fi
}
echo "API=$API"
curl -sS -m 5 "$API/api/health" || { echo "API down"; exit 1; }
echo
# --- SQLite (no docker) ---
SQLITE_DB="$ROOT/tests/tmp/demo.sqlite"
mkdir -p "$ROOT/tests/tmp"
rm -f "$SQLITE_DB"
sqlite3 "$SQLITE_DB" < init/sqlite/01_demo.sql
crud_sql sqlite "" 0 "" "" "$SQLITE_DB" sqlite
# --- MariaDB ---
./dbctl.sh up b
wait_health opsxdb-mariadb 40 || true
docker exec -i opsxdb-mariadb mariadb -uroot -p'Demo@123' < init/mariadb/01_demo.sql 2>/dev/null \
|| docker exec -i opsxdb-mariadb mysql -uroot -p'Demo@123' < init/mariadb/01_demo.sql
crud_sql mariadb 127.0.0.1 3307 root 'Demo@123' demo mysql
# --- PostgreSQL ---
./dbctl.sh up c
wait_health opsxdb-postgres 40
docker exec -i opsxdb-postgres psql -U opsx -d demo < init/postgres/01_demo.sql >/dev/null
crud_sql postgresql 127.0.0.1 5432 opsx 'Demo@123' demo postgres
# --- Redis ---
./dbctl.sh up f
wait_health opsxdb-redis 30
crud_sql redis 127.0.0.1 6379 "" "" 0 redis
# --- SQL Server ---
./dbctl.sh up d
wait_health opsxdb-mssql 120 || true
for i in $(seq 1 60); do
if docker exec opsxdb-mssql /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Demo@12345' -C -Q 'SELECT 1' >/dev/null 2>&1 \
|| docker exec opsxdb-mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'Demo@12345' -Q 'SELECT 1' >/dev/null 2>&1; then
echo "mssql sqlcmd ready"
break
run_sqlite() {
log
log "---- sqlite(无 Docker)----"
local SQLITE_DB="$ROOT/tests/tmp/demo.sqlite"
mkdir -p "$ROOT/tests/tmp"
rm -f "$SQLITE_DB"
if ! command -v sqlite3 >/dev/null 2>&1; then
bad "本机无 sqlite3"
return
fi
sleep 3
done
docker cp init/mssql/01_demo.sql opsxdb-mssql:/tmp/01_demo.sql
docker exec opsxdb-mssql /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Demo@12345' -C -i /tmp/01_demo.sql \
|| docker exec opsxdb-mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'Demo@12345' -i /tmp/01_demo.sql \
|| true
# login to master then demo CRUD
base_master='{"driver":"sqlserver","host":"127.0.0.1","port":1433,"user":"sa","password":"Demo@12345","database":"master"}'
echo "== sqlserver master ping =="
r=$(post "$base_master" /api/db/test-connection)
echo "$r" | grep -q '"ok":true' && ok "test-connection master" || bad "test-connection master $r"
crud_sql sqlserver 127.0.0.1 1433 sa 'Demo@12345' demo mssql
sqlite3 "$SQLITE_DB" < init/sqlite/01_demo.sql
crud_sql sqlite "" 0 "" "" "$SQLITE_DB" sqlite
}
# --- Oracle (slow) ---
./dbctl.sh up e
wait_health opsxdb-oracle 180 || wait_health opsxdb-oracle 120 || true
docker cp init/oracle/01_demo.sql opsxdb-oracle:/tmp/01_demo.sql
docker exec opsxdb-oracle bash -lc "echo exit | sqlplus -s opsx/Demo@123@//localhost/XEPDB1 @/tmp/01_demo.sql" \
|| docker exec opsxdb-oracle bash -lc "echo exit | sqlplus -s system/Demo@123@//localhost/XEPDB1 @/tmp/01_demo.sql" \
|| true
crud_sql oracle 127.0.0.1 1521 opsx 'Demo@123' XEPDB1 oracle
run_mysql() {
log
log "---- a MySQL :3306 ----"
up_one a opsxdb-mysql 40
docker exec -i opsxdb-mysql mysql -uroot -p'Demo@123' < init/mysql/01_demo.sql 2>/dev/null
crud_sql mysql 127.0.0.1 3306 root 'Demo@123' demo mysql
}
# --- MySQL last (restore default) ---
./dbctl.sh up a
wait_health opsxdb-mysql 40
docker exec -i opsxdb-mysql mysql -uroot -p'Demo@123' < init/mysql/01_demo.sql
crud_sql mysql 127.0.0.1 3306 root 'Demo@123' demo mysql
run_mariadb() {
log
log "---- b MariaDB :3307 ----"
up_one b opsxdb-mariadb 40
docker exec -i opsxdb-mariadb mariadb -uroot -p'Demo@123' < init/mariadb/01_demo.sql 2>/dev/null \
|| docker exec -i opsxdb-mariadb mysql -uroot -p'Demo@123' < init/mariadb/01_demo.sql 2>/dev/null
crud_sql mariadb 127.0.0.1 3307 root 'Demo@123' demo mysql
}
echo
echo "==== SUMMARY ok=$n_ok fail=$n_fail ===="
run_postgres() {
log
log "---- c PostgreSQL :5432 ----"
up_one c opsxdb-postgres 40
docker exec -i opsxdb-postgres psql -U opsx -d demo < init/postgres/01_demo.sql >/dev/null
crud_sql postgresql 127.0.0.1 5432 opsx 'Demo@123' demo postgres
}
run_redis() {
log
log "---- f Redis :6379 ----"
up_one f opsxdb-redis 30
docker exec opsxdb-redis redis-cli SET LANG:zh-CN hello >/dev/null
docker exec opsxdb-redis redis-cli HSET CEX:FOLLOW:FOLLOWERS:1 f1 '{"a":1}' >/dev/null
crud_sql redis 127.0.0.1 6379 "" "" 0 redis
}
run_mssql() {
log
log "---- d SQL Server :1433 ----"
up_one d opsxdb-mssql 120
local ready=0
for i in $(seq 1 60); do
if docker exec opsxdb-mssql /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Demo@12345' -C -Q 'SELECT 1' >/dev/null 2>&1 \
|| docker exec opsxdb-mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'Demo@12345' -Q 'SELECT 1' >/dev/null 2>&1; then
ready=1
break
fi
sleep 3
done
if [[ "$ready" -ne 1 ]]; then
bad "sqlcmd 未就绪"
return
fi
ok "sqlcmd ready"
docker cp init/mssql/01_demo.sql opsxdb-mssql:/tmp/01_demo.sql
docker exec opsxdb-mssql /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Demo@12345' -C -i /tmp/01_demo.sql >/dev/null 2>&1 \
|| docker exec opsxdb-mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'Demo@12345' -i /tmp/01_demo.sql >/dev/null 2>&1 \
|| true
local base_master='{"driver":"sqlserver","host":"127.0.0.1","port":1433,"user":"sa","password":"Demo@12345","database":"master"}'
local r
r=$(post "$base_master" /api/db/test-connection)
echo "$r" | json_ok && ok "test-connection master" || bad "test-connection master $r"
crud_sql sqlserver 127.0.0.1 1433 sa 'Demo@12345' demo mssql
}
run_oracle() {
log
log "---- e Oracle :1521(Apple Silicon 常 ORA-00442)----"
up_one e opsxdb-oracle 180
docker cp init/oracle/01_demo.sql opsxdb-oracle:/tmp/01_demo.sql 2>/dev/null || true
docker exec opsxdb-oracle bash -lc "echo exit | sqlplus -s opsx/Demo@123@//localhost/XEPDB1 @/tmp/01_demo.sql" >/dev/null 2>&1 \
|| docker exec opsxdb-oracle bash -lc "echo exit | sqlplus -s system/Demo@123@//localhost/XEPDB1 @/tmp/01_demo.sql" >/dev/null 2>&1 \
|| true
crud_sql oracle 127.0.0.1 1521 opsx 'Demo@123' XEPDB1 oracle
}
run_grey() {
local alias="$1" name="$2" container="$3" driver="$4" port="$5"
log
log "---- $alias $name(驱动未接入,只验测连失败/灰态)----"
up_one "$alias" "$container" 40
local base
base=$(printf '{"driver":"%s","host":"127.0.0.1","port":%s}' "$driver" "$port")
local r
r=$(post "$base" /api/db/test-connection)
if echo "$r" | grep -qiE '未接入|未就绪|not ready|unknown driver|unsupported'; then
ok "灰态测连被拒绝"
else
echo "$r" | json_ok && bad "未接入驱动却测连成功 $r" || ok "测连失败(驱动未接入): $r"
fi
}
usage() {
cat <<USAGE
用法: $0 [目标]
无参数 顺序验收 sqlite a b c f d e(每步只留一个容器)
sqlite 仅 SQLite 文件库
a|mysql MySQL :3306
b|mariadb MariaDB :3307
c|postgres PostgreSQL :5432
d|mssql SQL Server :1433
e|oracle Oracle :1521
f|redis Redis :6379
g|h|i ZooKeeper / ES / Influx(驱动未接入,只验灰态)
结果写入 tests/accept-result.txt
USAGE
}
log "API=$API $(date '+%Y-%m-%d %H:%M:%S')"
if ! curl -sS -m 5 "$API/api/health" >/dev/null; then
log "API down — 请先在仓库根目录 ./run.sh web"
exit 1
fi
ok "API health"
target="${1:-all}"
case "$target" in
-h|--help|help) usage; exit 0 ;;
sqlite) run_sqlite ;;
a|mysql) run_mysql ;;
b|mariadb|maria) run_mariadb ;;
c|postgres|pg|postgresql) run_postgres ;;
d|mssql|sqlserver) run_mssql ;;
e|oracle) run_oracle ;;
f|redis) run_redis ;;
g|zk|zookeeper) run_grey g ZooKeeper opsxdb-zookeeper zookeeper 2181 ;;
h|es|elasticsearch) run_grey h Elasticsearch opsxdb-elasticsearch elasticsearch 9200 ;;
i|influx|influxdb) run_grey i InfluxDB opsxdb-influxdb influxdb 8086 ;;
all|"")
run_sqlite
run_mysql
run_mariadb
run_postgres
run_redis
run_mssql
run_oracle
;;
*) usage; exit 1 ;;
esac
log
log "==== SUMMARY ok=$n_ok fail=$n_fail ===="
[[ "$n_fail" -eq 0 ]]