dsh-plugin-dbgraph 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +178 -2
- package/cordis.patch.yml +29 -0
- package/package.json +48 -3
package/README.md
CHANGED
|
@@ -1,3 +1,179 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-plugin-dbgraph
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Database structure the agent already knows, so it answers with one tool call instead of five.**
|
|
4
|
+
|
|
5
|
+
A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin that indexes a remote
|
|
6
|
+
database's *catalog* — every table, column, comment, key, index, and a bounded sample of enum
|
|
7
|
+
literals — into a local SQLite index under `~/.dsh/dbgraph`. The model reads that index (and a digest
|
|
8
|
+
of it placed directly in the system prompt) instead of spending round-trips on `SHOW TABLES` and
|
|
9
|
+
`DESCRIBE`, then fetches the actual values with exactly one network call.
|
|
10
|
+
|
|
11
|
+
## The problem it removes
|
|
12
|
+
|
|
13
|
+
Without an index, answering *"what is 张三's password?"* looks like this:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
1. SHOW TABLES ← network round-trip
|
|
17
|
+
2. DESCRIBE sys_user ← network round-trip
|
|
18
|
+
3. SELECT ... WHERE status = 1 ← wrong: status is a varchar
|
|
19
|
+
4. SELECT ... WHERE status = 'paid' ← guess corrected
|
|
20
|
+
5. 2,000 rows into the transcript ← context burned
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Four model round-trips, four network round-trips, two wrong guesses. **The expensive part is not the
|
|
24
|
+
database — it is the model's turns.** Each one costs seconds; a 100 ms query does not.
|
|
25
|
+
|
|
26
|
+
With dbgraph:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
1. dbgraph find {entity:'用户', fields:['密码'], where:{name:'张三'}}
|
|
30
|
+
← one row, and the answer says which table and column it used
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Structure local, data online
|
|
34
|
+
|
|
35
|
+
| | Where | Why |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| Tables, columns, comments, keys, indexes, enum samples, row estimates | **Local index** | Zero network, usable offline, no credential needed to ask what a column means |
|
|
38
|
+
| Actual row values | **Always the server** | A cached password is not a fast password, it is a wrong one |
|
|
39
|
+
|
|
40
|
+
Nothing derived from business data is persisted. The one data-shaped field is `enum_values_json`, a
|
|
41
|
+
`SELECT DISTINCT … LIMIT n` sample that teaches the model whether `status` holds `'paid'` or `1`.
|
|
42
|
+
|
|
43
|
+
## Layout
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
packages/
|
|
47
|
+
bundle cordis.patch.yml — mounts everything as one profile layer
|
|
48
|
+
service ctx.dbgraph: the vocabulary, the dialect registry, the index protocol
|
|
49
|
+
catalog one DatabaseDialect per engine (SQLite, MySQL; PostgreSQL/MongoDB/Redis to follow)
|
|
50
|
+
store ~/.dsh/dbgraph: connections.json, the index writer, the reader, the resolver
|
|
51
|
+
tool the two model tools, /dbgraph, and the prompt digest
|
|
52
|
+
crates/
|
|
53
|
+
dbgraph-indexer the index write path in Rust (normalize + terms + SQLite)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Why the write path is Rust
|
|
57
|
+
|
|
58
|
+
`node:sqlite` is a synchronous API. Writing ten thousand columns through it blocks the event loop for
|
|
59
|
+
as long as the write takes — this is the same wall `dsh-plugin-codegraph` and
|
|
60
|
+
`dsh-plugin-sessiongraph` hit, and both moved their kernels behind napi `AsyncTask` to get past it.
|
|
61
|
+
`crates/dbgraph-indexer` does the same: normalization, term generation, and the SQLite write run on
|
|
62
|
+
libuv's thread pool, so a rebuild never stalls the host.
|
|
63
|
+
|
|
64
|
+
Measured on one machine, same catalog, same fixed clock — 500 tables / 10,000 columns:
|
|
65
|
+
|
|
66
|
+
| writer | write | index file | longest event-loop block |
|
|
67
|
+
|---|---|---|---|
|
|
68
|
+
| JavaScript | 1992 ms | 8.46 MiB | **1992 ms** (the whole write) |
|
|
69
|
+
| Rust (`AsyncTask`) | 218 ms | 8.41 MiB | **33.6 ms** |
|
|
70
|
+
|
|
71
|
+
**9.1× faster, and the blocking cost drops 59×** — which is the number that matters, because a
|
|
72
|
+
two-second stall is two seconds of the host doing nothing else.
|
|
73
|
+
|
|
74
|
+
The two writers are checked against each other rather than trusted: given the same catalog and the
|
|
75
|
+
same fixed `now`, `objects`, `columns`, `edges`, `terms`, and `search` (rowids included) must match
|
|
76
|
+
row for row, and only `meta.writer` may differ. Fixture expectations are generated by *running* the
|
|
77
|
+
JavaScript writer, never hand-written, so a change on either side that is not mirrored fails the
|
|
78
|
+
build.
|
|
79
|
+
|
|
80
|
+
The JavaScript writer is therefore not an optimization fallback — it is the **parity reference** and
|
|
81
|
+
the **degradation path** (no Rust toolchain, no addon, a throwing call, an unparsable answer all fall
|
|
82
|
+
back to it, once and quietly).
|
|
83
|
+
|
|
84
|
+
### Two things that had to be measured, not assumed
|
|
85
|
+
|
|
86
|
+
- **`node:sqlite` contentless FTS5 stores nothing you can read back.** Every column — `UNINDEXED`
|
|
87
|
+
ones included — returns `NULL`; only `rowid` and `rank` survive. The index therefore uses a
|
|
88
|
+
*contentful* table, where `object_id UNINDEXED` reads back verbatim. (The FTS5 column grammar is
|
|
89
|
+
also `column-name [UNINDEXED]`: a type name in front is a parse error.)
|
|
90
|
+
- **`unicode61` treats a run of Chinese as one token.** Store `广告点击` verbatim and `MATCH '广告'`
|
|
91
|
+
finds nothing. So `text` holds a pre-tokenized stream — camel/digit boundaries for ASCII, bigrams
|
|
92
|
+
for CJK — and queries go through the same tokenizer, joined with `OR` (a space means *phrase*, not
|
|
93
|
+
*any of these*).
|
|
94
|
+
|
|
95
|
+
### Picking a writer
|
|
96
|
+
|
|
97
|
+
`dbgraph_index` takes `writer: 'auto' | 'js' | 'rust'`:
|
|
98
|
+
|
|
99
|
+
- `js` — the JavaScript writer, always. Never touches the addon.
|
|
100
|
+
- `rust` — the native writer only; a missing artifact or a throwing call fails the run rather than
|
|
101
|
+
quietly producing a slower index.
|
|
102
|
+
- `auto` (default) — native when available; a missing artifact, a rejected call, or an unreadable
|
|
103
|
+
report logs **once per process** and falls back to JavaScript.
|
|
104
|
+
|
|
105
|
+
`DSH_DBGRAPH_RUST_ADDON` points at an artifact explicitly: a test hook for the fallback path, and an
|
|
106
|
+
escape hatch for a deployment that keeps the addon outside `crates/`. It defaults to
|
|
107
|
+
`crates/dbgraph-indexer/artifacts/dbgraph-indexer.<platform>-<arch>.node`.
|
|
108
|
+
|
|
109
|
+
One acknowledged difference: the JavaScript writer counts dangling edges (an edge naming a table the
|
|
110
|
+
snapshot does not contain) into `report.warnings`; the native writer drops them silently. The stored
|
|
111
|
+
data is identical — only that one warning line differs.
|
|
112
|
+
|
|
113
|
+
## Index format
|
|
114
|
+
|
|
115
|
+
One SQLite file per connection, replaced wholesale on every run (write to a staging file named
|
|
116
|
+
`<index>.<pid>-<nonce>.tmp`, then rename). The nonce is per run, so two runs never share the staging
|
|
117
|
+
file. That single decision removes incremental diffing, deletes, and half-updated indexes.
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
meta key/value: engine, database, schemaVersion, l1IndexedAt, l2IndexedAt
|
|
121
|
+
objects one row per table / view / collection / key pattern
|
|
122
|
+
columns ordinal, type, type_class, nullability, default, comment, pk/unique, enum sample
|
|
123
|
+
edges fk | index | view_ref | code_ref
|
|
124
|
+
terms normalized words + Chinese bigrams + alias expansion, for semantic lookup
|
|
125
|
+
search FTS5 (contentless) over object and column names and comments
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`status.json` beside the indexes carries a digest the system-prompt section reads **synchronously** —
|
|
129
|
+
a section is assembled on every model request, so opening SQLite there would be a per-step cost the
|
|
130
|
+
plugin refuses to pay.
|
|
131
|
+
|
|
132
|
+
## Two levels
|
|
133
|
+
|
|
134
|
+
| Level | Content | Where |
|
|
135
|
+
|---|---|---|
|
|
136
|
+
| **L1** | object and column names, declared types | inline, seconds — usable immediately |
|
|
137
|
+
| **L2** | comments, foreign keys, indexes, row estimates, enum samples | background job |
|
|
138
|
+
|
|
139
|
+
An L1 failure to enrich never discards the structural snapshot: an index that answers "which columns
|
|
140
|
+
exist" is most of the value.
|
|
141
|
+
|
|
142
|
+
## Tools
|
|
143
|
+
|
|
144
|
+
| Tool | Timeout | What it does |
|
|
145
|
+
|---|---|---|
|
|
146
|
+
| `dbgraph` | 30 s | `connections` `schema` `objects` `columns` `find` `sql` `impact` `refs` |
|
|
147
|
+
| `dbgraph_index` | 300 s | Builds or refreshes one connection (`l1` / `l2` / `full`); hands a large run to a background job |
|
|
148
|
+
|
|
149
|
+
`find` is the one that matters: it resolves words to a table and columns **offline**, then runs one
|
|
150
|
+
parameterized `SELECT`. It answers `ambiguous` with ranked candidates rather than guessing, and
|
|
151
|
+
`missing_predicate` rather than scanning a table because the user said "this user" without saying
|
|
152
|
+
which.
|
|
153
|
+
|
|
154
|
+
## Masking
|
|
155
|
+
|
|
156
|
+
Sensitive columns (`password`, `passwd`, `pwd`, `secret`, `token`, `salt`, `id_card`, …) come back as
|
|
157
|
+
`[REDACTED]` by default. Without that, one careless question puts a password into a transcript — and
|
|
158
|
+
this harness persists transcripts and indexes them. `sensitiveColumns: 'show'` per connection turns it
|
|
159
|
+
off.
|
|
160
|
+
|
|
161
|
+
Credentials never live in `connections.json`: a connection stores a **credential reference**, and the
|
|
162
|
+
password is resolved through the host's `ctx.credentials` seam on each operation.
|
|
163
|
+
|
|
164
|
+
## Commands
|
|
165
|
+
|
|
166
|
+
```sh
|
|
167
|
+
pnpm install
|
|
168
|
+
pnpm run build # tsc -b across the four TS packages
|
|
169
|
+
pnpm test # vitest run
|
|
170
|
+
pnpm run test:coverage # per-file 100% thresholds
|
|
171
|
+
pnpm run rust:build # cargo build --release for crates/dbgraph-indexer
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Tests resolve to live `src/`, never to built `lib/` (`tsconfig.base.json`'s `paths` map is the
|
|
175
|
+
facade), so a stale build can never load a second copy of a module singleton.
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# The dsh-plugin-dbgraph bundle patch: mounts the whole database-structure capability
|
|
2
|
+
# as ONE layer over an existing dsh profile.
|
|
3
|
+
#
|
|
4
|
+
# Row order carries no load semantics — activation is service-availability driven — so the
|
|
5
|
+
# seam may appear before or after the providers that fill it. The user's own profile patch
|
|
6
|
+
# layer can address any row below by `id` to retune it (last write per row wins), e.g.
|
|
7
|
+
# disabling a dialect's `engines` list or lowering the tool's `maxRows`.
|
|
8
|
+
|
|
9
|
+
- insert:
|
|
10
|
+
# Service Definition: publishes ctx.dbgraph — the dialect registry, the single index-store
|
|
11
|
+
# reservation, and the nine normalized operations. Holds no store and reaches no server.
|
|
12
|
+
- id: dbgraph
|
|
13
|
+
name: 'dsh-plugin-dbgraph-service'
|
|
14
|
+
|
|
15
|
+
# Service Provider (dialects): one DatabaseDialect per engine. Each reaches one server family
|
|
16
|
+
# for a probe, an L1 snapshot, an L2 enrichment, and one execute. Registers dialects only.
|
|
17
|
+
- id: dbgraph-catalog
|
|
18
|
+
name: 'dsh-plugin-dbgraph-catalog'
|
|
19
|
+
|
|
20
|
+
# Service Provider (offline): the on-disk index under ~/.dsh/dbgraph — reader, JavaScript
|
|
21
|
+
# write path (the parity reference and the fallback), and the prompt-digest sidecar.
|
|
22
|
+
- id: dbgraph-store
|
|
23
|
+
name: 'dsh-plugin-dbgraph-store'
|
|
24
|
+
|
|
25
|
+
# Consumer: the model-facing `dbgraph` and `dbgraph_index` tools, their system-prompt
|
|
26
|
+
# section (the schema digest that removes the discovery round-trips), and the `/dbgraph`
|
|
27
|
+
# command.
|
|
28
|
+
- id: dbgraph-tool
|
|
29
|
+
name: 'dsh-plugin-dbgraph-tool'
|
package/package.json
CHANGED
|
@@ -1,6 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-dbgraph",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Database structure for DeepSeek Harness — indexes a remote database catalog into a local index so the agent knows every table and column before it writes a query, then answers with one tool call",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"dsh",
|
|
7
|
+
"dsh-plugin",
|
|
8
|
+
"deepseek-harness",
|
|
9
|
+
"cordis",
|
|
10
|
+
"cordis-plugin",
|
|
11
|
+
"database",
|
|
12
|
+
"sql",
|
|
13
|
+
"mysql",
|
|
14
|
+
"postgresql",
|
|
15
|
+
"sqlite",
|
|
16
|
+
"mongodb",
|
|
17
|
+
"redis",
|
|
18
|
+
"schema",
|
|
19
|
+
"ai-agent",
|
|
20
|
+
"llm-tools",
|
|
21
|
+
"agent-tools"
|
|
22
|
+
],
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": "CC ZHAO",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/CC19990113/dsh-plugin-dbgraph.git",
|
|
28
|
+
"directory": "packages/bundle"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/CC19990113/dsh-plugin-dbgraph#readme",
|
|
31
|
+
"bugs": "https://github.com/CC19990113/dsh-plugin-dbgraph/issues",
|
|
32
|
+
"type": "module",
|
|
33
|
+
"files": [
|
|
34
|
+
"cordis.patch.yml"
|
|
35
|
+
],
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public",
|
|
38
|
+
"provenance": true
|
|
39
|
+
},
|
|
40
|
+
"dsh": {
|
|
41
|
+
"bundle": {
|
|
42
|
+
"patch": "./cordis.patch.yml"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"dsh-plugin-dbgraph-tool": "^0.1.0",
|
|
47
|
+
"dsh-plugin-dbgraph-store": "^0.1.0",
|
|
48
|
+
"dsh-plugin-dbgraph-catalog": "^0.1.0",
|
|
49
|
+
"dsh-plugin-dbgraph-service": "^0.1.0"
|
|
50
|
+
}
|
|
6
51
|
}
|