dsh-plugin-dbgraph-store 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/lib/aliases.d.ts +34 -0
- package/lib/aliases.js +92 -0
- package/lib/connections.d.ts +42 -0
- package/lib/connections.js +172 -0
- package/lib/database.d.ts +61 -0
- package/lib/database.js +176 -0
- package/lib/index.d.ts +222 -0
- package/lib/index.js +414 -0
- package/lib/invariant.d.ts +16 -0
- package/lib/invariant.js +23 -0
- package/lib/normalize.d.ts +59 -0
- package/lib/normalize.js +158 -0
- package/lib/paths.d.ts +83 -0
- package/lib/paths.js +130 -0
- package/lib/reader.d.ts +207 -0
- package/lib/reader.js +713 -0
- package/lib/resolve.d.ts +70 -0
- package/lib/resolve.js +762 -0
- package/lib/rust-engine.d.ts +108 -0
- package/lib/rust-engine.js +148 -0
- package/lib/status.d.ts +64 -0
- package/lib/status.js +141 -0
- package/lib/terms.d.ts +93 -0
- package/lib/terms.js +146 -0
- package/lib/writer-js.d.ts +42 -0
- package/lib/writer-js.js +269 -0
- package/package.json +64 -3
- package/README.md +0 -3
package/lib/terms.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The semantic layer of the index: `terms` rows and the FTS `search` documents.
|
|
3
|
+
*
|
|
4
|
+
* Two lookups answer the same question from opposite directions:
|
|
5
|
+
*
|
|
6
|
+
* - `terms` is the resolver's table — one row per (word, object[, column]) with a weight that says how
|
|
7
|
+
* strong the evidence is: a column's name token beats its comment, an alias expansion beats neither,
|
|
8
|
+
* a Chinese bigram beats nothing at all, it is partial evidence by construction.
|
|
9
|
+
* - `search` is the FTS5 document — the tokenizer's output for an object's name and comment (one row)
|
|
10
|
+
* and for each column's name and comment (one row per column), used for free-text listing and as a
|
|
11
|
+
* recall net behind the resolver.
|
|
12
|
+
*
|
|
13
|
+
* Weights live in one exported table so the resolver's expectations and the writer's output cannot
|
|
14
|
+
* drift apart silently.
|
|
15
|
+
* @module dsh-plugin-dbgraph-store/terms
|
|
16
|
+
*/
|
|
17
|
+
import { searchDocument, tokenizeForSearch } from 'dsh-plugin-dbgraph-service';
|
|
18
|
+
import { aliasGroupTokens, aliasGroupsIn } from "./aliases.js";
|
|
19
|
+
import { isAsciiToken, normalizeComment } from "./normalize.js";
|
|
20
|
+
/**
|
|
21
|
+
* Evidence weight per token source. A column-level row is finally weighed again by the resolver
|
|
22
|
+
* (a column hit is weaker evidence about the *table* than the table's own name), so these numbers are
|
|
23
|
+
* "how much does this word describe the name it was found in", not final confidence.
|
|
24
|
+
*/
|
|
25
|
+
export const TERM_WEIGHTS = {
|
|
26
|
+
/** A token of the object's own name. */
|
|
27
|
+
objectNameAscii: 1,
|
|
28
|
+
/** A CJK bigram of the object's name — partial by nature. */
|
|
29
|
+
objectNameCjk: 0.75,
|
|
30
|
+
/** An ASCII token of the object's comment. */
|
|
31
|
+
objectCommentAscii: 0.75,
|
|
32
|
+
/** A CJK bigram of the object's comment. */
|
|
33
|
+
objectCommentCjk: 0.5,
|
|
34
|
+
/** An alias-group expansion on an object's name or comment. */
|
|
35
|
+
objectAlias: 0.6,
|
|
36
|
+
/** An ASCII token of the column's own name. */
|
|
37
|
+
columnNameAscii: 0.9,
|
|
38
|
+
/** A CJK bigram of the column's name. */
|
|
39
|
+
columnNameCjk: 0.75,
|
|
40
|
+
/** An ASCII token of the column's comment. */
|
|
41
|
+
columnCommentAscii: 0.7,
|
|
42
|
+
/** A CJK bigram of the column's comment. */
|
|
43
|
+
columnCommentCjk: 0.5,
|
|
44
|
+
/** An alias-group expansion on a column's name or comment. */
|
|
45
|
+
columnAlias: 0.65,
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Assign the ids the writer will use, so terms and rows cannot disagree about identity.
|
|
49
|
+
* @param catalog - the snapshot being written.
|
|
50
|
+
* @returns object ids by position and column ids by object/column position.
|
|
51
|
+
*/
|
|
52
|
+
export function assignCatalogIds(catalog) {
|
|
53
|
+
const objects = [];
|
|
54
|
+
const columns = [];
|
|
55
|
+
let nextColumnId = 1;
|
|
56
|
+
for (let objectIndex = 0; objectIndex < catalog.objects.length; objectIndex += 1) {
|
|
57
|
+
const object = catalog.objects[objectIndex];
|
|
58
|
+
objects.push(objectIndex + 1);
|
|
59
|
+
const ids = [];
|
|
60
|
+
for (let columnIndex = 0; columnIndex < (object?.columns.length ?? 0); columnIndex += 1) {
|
|
61
|
+
ids.push(nextColumnId);
|
|
62
|
+
nextColumnId += 1;
|
|
63
|
+
}
|
|
64
|
+
columns.push(ids);
|
|
65
|
+
}
|
|
66
|
+
return { objects, columns };
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Pick the weight for one token given whether it is an ASCII identifier part or a CJK bigram.
|
|
70
|
+
* @param token - the token.
|
|
71
|
+
* @param ascii - weight for an ASCII token.
|
|
72
|
+
* @param cjk - weight for a CJK token.
|
|
73
|
+
* @returns the chosen weight.
|
|
74
|
+
*/
|
|
75
|
+
function weightFor(token, ascii, cjk) {
|
|
76
|
+
return isAsciiToken(token) ? ascii : cjk;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Build every `terms` row and both `search` documents for one catalog.
|
|
80
|
+
* @param catalog - the snapshot being written.
|
|
81
|
+
* @param ids - ids from {@link assignCatalogIds}.
|
|
82
|
+
* @param aliases - the alias table handed in by the caller.
|
|
83
|
+
* @returns deduplicated term rows and search documents.
|
|
84
|
+
*/
|
|
85
|
+
export function buildTerms(catalog, ids, aliases) {
|
|
86
|
+
const terms = new Map();
|
|
87
|
+
const search = [];
|
|
88
|
+
const add = (term, weight, objectId, columnId) => {
|
|
89
|
+
if (term === '')
|
|
90
|
+
return;
|
|
91
|
+
const key = `${term}\u0000${objectId}\u0000${columnId ?? ''}`;
|
|
92
|
+
const existing = terms.get(key);
|
|
93
|
+
if (existing !== undefined && existing.weight >= weight)
|
|
94
|
+
return;
|
|
95
|
+
terms.set(key, { term, weight, objectId, columnId });
|
|
96
|
+
};
|
|
97
|
+
const addTokens = (tokens, asciiWeight, cjkWeight, objectId, columnId) => {
|
|
98
|
+
for (const token of tokens)
|
|
99
|
+
add(token, weightFor(token, asciiWeight, cjkWeight), objectId, columnId);
|
|
100
|
+
};
|
|
101
|
+
for (let objectIndex = 0; objectIndex < catalog.objects.length; objectIndex += 1) {
|
|
102
|
+
const object = catalog.objects[objectIndex];
|
|
103
|
+
const objectId = ids.objects[objectIndex];
|
|
104
|
+
if (object === undefined || objectId === undefined)
|
|
105
|
+
continue;
|
|
106
|
+
const comment = normalizeComment(object.comment);
|
|
107
|
+
addTokens(tokenizeForSearch(object.name), TERM_WEIGHTS.objectNameAscii, TERM_WEIGHTS.objectNameCjk, objectId, null);
|
|
108
|
+
if (comment !== undefined) {
|
|
109
|
+
addTokens(tokenizeForSearch(comment), TERM_WEIGHTS.objectCommentAscii, TERM_WEIGHTS.objectCommentCjk, objectId, null);
|
|
110
|
+
}
|
|
111
|
+
for (const group of aliasGroupsIn([object.name, comment], aliases)) {
|
|
112
|
+
for (const token of aliasGroupTokens(group)) {
|
|
113
|
+
add(token, TERM_WEIGHTS.objectAlias, objectId, null);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
search.push({
|
|
117
|
+
text: searchDocument([object.name, comment]),
|
|
118
|
+
objectId,
|
|
119
|
+
columnId: null,
|
|
120
|
+
});
|
|
121
|
+
const columnIds = ids.columns[objectIndex] ?? [];
|
|
122
|
+
for (let columnIndex = 0; columnIndex < object.columns.length; columnIndex += 1) {
|
|
123
|
+
const column = object.columns[columnIndex];
|
|
124
|
+
const columnId = columnIds[columnIndex];
|
|
125
|
+
if (column === undefined || columnId === undefined)
|
|
126
|
+
continue;
|
|
127
|
+
const columnComment = normalizeComment(column.comment);
|
|
128
|
+
addTokens(tokenizeForSearch(column.name), TERM_WEIGHTS.columnNameAscii, TERM_WEIGHTS.columnNameCjk, objectId, columnId);
|
|
129
|
+
if (columnComment !== undefined) {
|
|
130
|
+
addTokens(tokenizeForSearch(columnComment), TERM_WEIGHTS.columnCommentAscii, TERM_WEIGHTS.columnCommentCjk, objectId, columnId);
|
|
131
|
+
}
|
|
132
|
+
for (const group of aliasGroupsIn([column.name, columnComment], aliases)) {
|
|
133
|
+
for (const token of aliasGroupTokens(group)) {
|
|
134
|
+
add(token, TERM_WEIGHTS.columnAlias, objectId, columnId);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
search.push({
|
|
138
|
+
text: searchDocument([column.name, columnComment]),
|
|
139
|
+
objectId: null,
|
|
140
|
+
columnId,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return { terms: [...terms.values()], search };
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=terms.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The JavaScript write path: take one normalized catalog snapshot and produce a complete `index.db`.
|
|
3
|
+
*
|
|
4
|
+
* This path is two things at once, and both constrain how it may be written:
|
|
5
|
+
*
|
|
6
|
+
* - the **parity reference** for the native writer — fixture expectations are generated by running
|
|
7
|
+
* this code, so it must be deterministic, and it must execute {@link INDEX_SCHEMA_DDL} verbatim
|
|
8
|
+
* rather than carrying any schema shape of its own;
|
|
9
|
+
* - the **degradation path** — a host without the native addon still gets a full index, at the cost of
|
|
10
|
+
* blocking the event loop for the duration (`node:sqlite` is synchronous). `IndexReport.elapsedMs`
|
|
11
|
+
* is measured here precisely so that cost can be compared against the native kernel instead of
|
|
12
|
+
* argued about.
|
|
13
|
+
*
|
|
14
|
+
* The file is written to `<index>.<pid>-<nonce>.tmp`, closed, and only then renamed over `index.db`.
|
|
15
|
+
* The nonce keeps concurrent runs from sharing one staging file; a run that dies leaves the previous
|
|
16
|
+
* index untouched, and an abandoned staging file is swept by a later run once it is an hour old.
|
|
17
|
+
* @module dsh-plugin-dbgraph-store/writer-js
|
|
18
|
+
*/
|
|
19
|
+
import type { AliasGroup, ConnectionIdType, IndexReport, RawCatalog } from 'dsh-plugin-dbgraph-service';
|
|
20
|
+
/** What one JavaScript write needs. */
|
|
21
|
+
export interface JsWriteInput {
|
|
22
|
+
/** Connection identity; also the directory name under the root. */
|
|
23
|
+
readonly connection: ConnectionIdType;
|
|
24
|
+
/** The snapshot to write. */
|
|
25
|
+
readonly catalog: RawCatalog;
|
|
26
|
+
/** Alias table expanded into `terms`. */
|
|
27
|
+
readonly aliases: readonly AliasGroup[];
|
|
28
|
+
/** Index root (`~/.dsh/dbgraph`, or `DSH_DBGRAPH_ROOT`). */
|
|
29
|
+
readonly root: string;
|
|
30
|
+
/** Epoch milliseconds recorded as the write time; defaults to now. */
|
|
31
|
+
readonly now?: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Write a complete index for one connection.
|
|
35
|
+
*
|
|
36
|
+
* The returned counts are what was actually inserted, not what the catalog claimed, so a dropped edge
|
|
37
|
+
* or a duplicate term is visible rather than assumed away.
|
|
38
|
+
* @param input - the snapshot, identity, aliases and root.
|
|
39
|
+
* @returns the run's report; `elapsedMs` covers schema creation, every insert, and the rename.
|
|
40
|
+
*/
|
|
41
|
+
export declare function writeIndexJs(input: JsWriteInput): IndexReport;
|
|
42
|
+
//# sourceMappingURL=writer-js.d.ts.map
|
package/lib/writer-js.js
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The JavaScript write path: take one normalized catalog snapshot and produce a complete `index.db`.
|
|
3
|
+
*
|
|
4
|
+
* This path is two things at once, and both constrain how it may be written:
|
|
5
|
+
*
|
|
6
|
+
* - the **parity reference** for the native writer — fixture expectations are generated by running
|
|
7
|
+
* this code, so it must be deterministic, and it must execute {@link INDEX_SCHEMA_DDL} verbatim
|
|
8
|
+
* rather than carrying any schema shape of its own;
|
|
9
|
+
* - the **degradation path** — a host without the native addon still gets a full index, at the cost of
|
|
10
|
+
* blocking the event loop for the duration (`node:sqlite` is synchronous). `IndexReport.elapsedMs`
|
|
11
|
+
* is measured here precisely so that cost can be compared against the native kernel instead of
|
|
12
|
+
* argued about.
|
|
13
|
+
*
|
|
14
|
+
* The file is written to `<index>.<pid>-<nonce>.tmp`, closed, and only then renamed over `index.db`.
|
|
15
|
+
* The nonce keeps concurrent runs from sharing one staging file; a run that dies leaves the previous
|
|
16
|
+
* index untouched, and an abandoned staging file is swept by a later run once it is an hour old.
|
|
17
|
+
* @module dsh-plugin-dbgraph-store/writer-js
|
|
18
|
+
*/
|
|
19
|
+
import { mkdirSync, renameSync, rmSync } from 'node:fs';
|
|
20
|
+
import { dirname } from 'node:path';
|
|
21
|
+
import { performance } from 'node:perf_hooks';
|
|
22
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
23
|
+
import { DbgraphError, INDEX_META_KEYS, INDEX_SCHEMA_DDL, INDEX_SCHEMA_VERSION, isValidConnectionId, } from 'dsh-plugin-dbgraph-service';
|
|
24
|
+
import { normalizeComment, typeClassOf } from "./normalize.js";
|
|
25
|
+
import { indexPath, stagingIndexPath, sweepStaleStaging } from "./paths.js";
|
|
26
|
+
import { assignCatalogIds, buildTerms } from "./terms.js";
|
|
27
|
+
/**
|
|
28
|
+
* Execute the contract DDL and translate any failure into a structured error.
|
|
29
|
+
* @param db - the staging database.
|
|
30
|
+
*/
|
|
31
|
+
function applySchema(db) {
|
|
32
|
+
for (const statement of INDEX_SCHEMA_DDL) {
|
|
33
|
+
try {
|
|
34
|
+
db.exec(statement);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
throw new DbgraphError(`creating the index schema failed: ${messageOf(error)}`, 'DBGRAPH_WRITE_FAILED', { cause: error });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* One thrown value's message.
|
|
43
|
+
* @param error - the thrown value.
|
|
44
|
+
* @returns a non-empty message.
|
|
45
|
+
*/
|
|
46
|
+
function messageOf(error) {
|
|
47
|
+
if (error instanceof Error)
|
|
48
|
+
return error.message;
|
|
49
|
+
if (typeof error === 'string')
|
|
50
|
+
return error;
|
|
51
|
+
return JSON.stringify(error) ?? 'unknown error';
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Index the objects being written by every lookup the edge resolver needs.
|
|
55
|
+
* @param objects - the catalog objects, in write order.
|
|
56
|
+
* @param objectIds - their ids.
|
|
57
|
+
* @returns display-qualified and bare-name lookups.
|
|
58
|
+
*/
|
|
59
|
+
function buildObjectSlots(objects, objectIds, columnIds) {
|
|
60
|
+
const byDisplay = new Map();
|
|
61
|
+
const byName = new Map();
|
|
62
|
+
for (let index = 0; index < objects.length; index += 1) {
|
|
63
|
+
const object = objects[index];
|
|
64
|
+
const id = objectIds[index];
|
|
65
|
+
if (object === undefined || id === undefined)
|
|
66
|
+
continue;
|
|
67
|
+
const schema = object.schema ?? '';
|
|
68
|
+
const exact = new Map();
|
|
69
|
+
const lower = new Map();
|
|
70
|
+
for (let columnIndex = 0; columnIndex < object.columns.length; columnIndex += 1) {
|
|
71
|
+
const column = object.columns[columnIndex];
|
|
72
|
+
const columnId = (columnIds[index] ?? [])[columnIndex];
|
|
73
|
+
if (column === undefined || columnId === undefined)
|
|
74
|
+
continue;
|
|
75
|
+
exact.set(column.name, columnId);
|
|
76
|
+
lower.set(column.name.toLowerCase(), columnId);
|
|
77
|
+
}
|
|
78
|
+
const slot = { id, schema, name: object.name, columns: exact, columnsLower: lower };
|
|
79
|
+
byDisplay.set(schema === '' ? object.name : `${schema}.${object.name}`, slot);
|
|
80
|
+
const bucket = byName.get(object.name);
|
|
81
|
+
if (bucket === undefined)
|
|
82
|
+
byName.set(object.name, [slot]);
|
|
83
|
+
else
|
|
84
|
+
bucket.push(slot);
|
|
85
|
+
}
|
|
86
|
+
return { byDisplay, byName };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Resolve a referenced object name against the objects in this snapshot.
|
|
90
|
+
* @param reference - the name the catalog reported, possibly schema-qualified.
|
|
91
|
+
* @param lookups - the lookup tables from {@link buildObjectSlots}.
|
|
92
|
+
* @returns the slot, or undefined when the target was not indexed.
|
|
93
|
+
*/
|
|
94
|
+
function findReferencedObject(reference, lookups) {
|
|
95
|
+
const direct = lookups.byDisplay.get(reference);
|
|
96
|
+
if (direct !== undefined)
|
|
97
|
+
return direct;
|
|
98
|
+
const bare = lookups.byName.get(reference);
|
|
99
|
+
if (bare !== undefined && bare.length === 1)
|
|
100
|
+
return bare[0];
|
|
101
|
+
const lower = reference.toLowerCase();
|
|
102
|
+
for (const [display, slot] of lookups.byDisplay) {
|
|
103
|
+
if (display.toLowerCase() === lower)
|
|
104
|
+
return slot;
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Resolve a column name inside one object.
|
|
110
|
+
* @param slot - the object.
|
|
111
|
+
* @param name - the declared column name.
|
|
112
|
+
* @returns the column id, or undefined.
|
|
113
|
+
*/
|
|
114
|
+
function findColumnId(slot, name) {
|
|
115
|
+
return slot.columns.get(name) ?? slot.columnsLower.get(name.toLowerCase());
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Write a complete index for one connection.
|
|
119
|
+
*
|
|
120
|
+
* The returned counts are what was actually inserted, not what the catalog claimed, so a dropped edge
|
|
121
|
+
* or a duplicate term is visible rather than assumed away.
|
|
122
|
+
* @param input - the snapshot, identity, aliases and root.
|
|
123
|
+
* @returns the run's report; `elapsedMs` covers schema creation, every insert, and the rename.
|
|
124
|
+
*/
|
|
125
|
+
export function writeIndexJs(input) {
|
|
126
|
+
const startedAt = performance.now();
|
|
127
|
+
const connection = input.connection;
|
|
128
|
+
if (!isValidConnectionId(connection)) {
|
|
129
|
+
throw new DbgraphError(`"${connection}" is not a usable connection id`, 'DBGRAPH_INVALID_PROVIDER');
|
|
130
|
+
}
|
|
131
|
+
const now = input.now ?? Date.now();
|
|
132
|
+
const finalPath = indexPath(connection, input.root);
|
|
133
|
+
const stagingPath = stagingIndexPath(connection, input.root);
|
|
134
|
+
mkdirSync(dirname(finalPath), { recursive: true });
|
|
135
|
+
// A killed run leaves its per-run staging file behind; nothing would ever reuse that name.
|
|
136
|
+
sweepStaleStaging(finalPath);
|
|
137
|
+
rmSync(stagingPath, { force: true });
|
|
138
|
+
const catalog = input.catalog;
|
|
139
|
+
const ids = assignCatalogIds(catalog);
|
|
140
|
+
const built = buildTerms(catalog, ids, input.aliases);
|
|
141
|
+
const warnings = [...(catalog.warnings ?? [])];
|
|
142
|
+
const lookups = buildObjectSlots(catalog.objects, ids.objects, ids.columns);
|
|
143
|
+
let database;
|
|
144
|
+
let objectCount = 0;
|
|
145
|
+
let columnCount = 0;
|
|
146
|
+
let edgeCount = 0;
|
|
147
|
+
let skippedEdges = 0;
|
|
148
|
+
try {
|
|
149
|
+
database = new DatabaseSync(stagingPath);
|
|
150
|
+
// The staging file is disposable until the rename: there is no reader to protect and no
|
|
151
|
+
// transaction to replay. The old index stays untouched, so durability buys nothing here.
|
|
152
|
+
database.exec('PRAGMA journal_mode = OFF');
|
|
153
|
+
database.exec('PRAGMA synchronous = OFF');
|
|
154
|
+
applySchema(database);
|
|
155
|
+
database.exec('BEGIN');
|
|
156
|
+
const setMeta = database.prepare('INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)');
|
|
157
|
+
setMeta.run(INDEX_META_KEYS.schemaVersion, String(INDEX_SCHEMA_VERSION));
|
|
158
|
+
setMeta.run(INDEX_META_KEYS.connection, connection);
|
|
159
|
+
setMeta.run(INDEX_META_KEYS.engine, catalog.engine);
|
|
160
|
+
setMeta.run(INDEX_META_KEYS.database, catalog.database);
|
|
161
|
+
if (catalog.serverVersion !== undefined) {
|
|
162
|
+
setMeta.run(INDEX_META_KEYS.serverVersion, catalog.serverVersion);
|
|
163
|
+
}
|
|
164
|
+
setMeta.run(INDEX_META_KEYS.level, catalog.level);
|
|
165
|
+
setMeta.run(INDEX_META_KEYS.fetchedAt, String(catalog.fetchedAt));
|
|
166
|
+
setMeta.run(INDEX_META_KEYS.l1IndexedAt, String(catalog.fetchedAt));
|
|
167
|
+
if (catalog.level === 'l2')
|
|
168
|
+
setMeta.run(INDEX_META_KEYS.l2IndexedAt, String(catalog.fetchedAt));
|
|
169
|
+
setMeta.run(INDEX_META_KEYS.writer, 'js');
|
|
170
|
+
setMeta.run(INDEX_META_KEYS.writtenAt, String(now));
|
|
171
|
+
const insertObject = database.prepare(`INSERT INTO objects (id, kind, schema_name, name, comment, row_estimate, updated_at)
|
|
172
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`);
|
|
173
|
+
const insertColumn = database.prepare(`INSERT INTO columns (id, object_id, ordinal, name, type, type_class, nullable, default_value,
|
|
174
|
+
comment, is_primary_key, is_unique, enum_values_json, updated_at)
|
|
175
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
176
|
+
for (let index = 0; index < catalog.objects.length; index += 1) {
|
|
177
|
+
const object = catalog.objects[index];
|
|
178
|
+
const objectId = ids.objects[index];
|
|
179
|
+
if (object === undefined || objectId === undefined)
|
|
180
|
+
continue;
|
|
181
|
+
insertObject.run(objectId, object.kind, object.schema ?? '', object.name, normalizeComment(object.comment) ?? null, object.rowEstimate === undefined || !Number.isFinite(object.rowEstimate)
|
|
182
|
+
? null
|
|
183
|
+
: Math.trunc(object.rowEstimate), now);
|
|
184
|
+
objectCount += 1;
|
|
185
|
+
const columnIds = ids.columns[index] ?? [];
|
|
186
|
+
for (let columnIndex = 0; columnIndex < object.columns.length; columnIndex += 1) {
|
|
187
|
+
const column = object.columns[columnIndex];
|
|
188
|
+
const columnId = columnIds[columnIndex];
|
|
189
|
+
if (column === undefined || columnId === undefined)
|
|
190
|
+
continue;
|
|
191
|
+
const enumValues = column.enumValues !== undefined && column.enumValues.length > 0
|
|
192
|
+
? JSON.stringify(column.enumValues)
|
|
193
|
+
: null;
|
|
194
|
+
insertColumn.run(columnId, objectId, column.ordinal, column.name, column.type, typeClassOf(column.type), column.isPrimaryKey === true ? 0 : column.nullable === false ? 0 : 1, column.defaultValue ?? null, normalizeComment(column.comment) ?? null, column.isPrimaryKey === true ? 1 : 0, column.isUnique === true ? 1 : 0, enumValues, now);
|
|
195
|
+
columnCount += 1;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const insertEdge = database.prepare(`INSERT INTO edges (src_object, src_column_id, kind, dst_object, dst_column_id, name)
|
|
199
|
+
VALUES (?, ?, ?, ?, ?, ?)`);
|
|
200
|
+
for (let index = 0; index < catalog.objects.length; index += 1) {
|
|
201
|
+
const object = catalog.objects[index];
|
|
202
|
+
const srcId = ids.objects[index];
|
|
203
|
+
if (object === undefined || srcId === undefined)
|
|
204
|
+
continue;
|
|
205
|
+
const srcSlot = lookups.byDisplay.get((object.schema ?? '') === '' ? object.name : `${object.schema}.${object.name}`);
|
|
206
|
+
for (const edge of object.edges ?? []) {
|
|
207
|
+
const destination = edge.refObject === undefined ? undefined : findReferencedObject(edge.refObject, lookups);
|
|
208
|
+
if (edge.refObject !== undefined && destination === undefined) {
|
|
209
|
+
skippedEdges += 1;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const rowCount = Math.max(1, edge.columns.length);
|
|
213
|
+
for (let columnIndex = 0; columnIndex < rowCount; columnIndex += 1) {
|
|
214
|
+
const sourceColumn = edge.columns[columnIndex];
|
|
215
|
+
const srcColumnId = srcSlot === undefined || sourceColumn === undefined
|
|
216
|
+
? undefined
|
|
217
|
+
: findColumnId(srcSlot, sourceColumn);
|
|
218
|
+
if (sourceColumn !== undefined && srcColumnId === undefined) {
|
|
219
|
+
skippedEdges += 1;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
const referencedColumn = edge.refColumns?.[columnIndex];
|
|
223
|
+
const dstColumnId = destination === undefined || referencedColumn === undefined
|
|
224
|
+
? undefined
|
|
225
|
+
: findColumnId(destination, referencedColumn);
|
|
226
|
+
insertEdge.run(srcId, srcColumnId ?? null, edge.kind, destination?.id ?? null, dstColumnId ?? null, edge.name ?? null);
|
|
227
|
+
edgeCount += 1;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const insertTerm = database.prepare('INSERT INTO terms (term, weight, object_id, column_id) VALUES (?, ?, ?, ?)');
|
|
232
|
+
for (const row of built.terms)
|
|
233
|
+
insertTerm.run(row.term, row.weight, row.objectId, row.columnId);
|
|
234
|
+
const insertSearch = database.prepare('INSERT INTO search (text, object_id, column_id) VALUES (?, ?, ?)');
|
|
235
|
+
for (const row of built.search)
|
|
236
|
+
insertSearch.run(row.text, row.objectId, row.columnId);
|
|
237
|
+
database.exec('COMMIT');
|
|
238
|
+
database.close();
|
|
239
|
+
database = undefined;
|
|
240
|
+
renameSync(stagingPath, finalPath);
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
try {
|
|
244
|
+
database?.close();
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
// The staging handle is already unusable; the remaining work is cleanup, not diagnosis.
|
|
248
|
+
}
|
|
249
|
+
rmSync(stagingPath, { force: true });
|
|
250
|
+
if (error instanceof DbgraphError)
|
|
251
|
+
throw error;
|
|
252
|
+
throw new DbgraphError(`writing the index for "${connection}" failed: ${messageOf(error)}`, 'DBGRAPH_WRITE_FAILED', { cause: error });
|
|
253
|
+
}
|
|
254
|
+
if (skippedEdges > 0) {
|
|
255
|
+
warnings.push(`${skippedEdges} edge row(s) skipped: the referenced object or column is not in this snapshot`);
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
connection,
|
|
259
|
+
level: catalog.level,
|
|
260
|
+
objects: objectCount,
|
|
261
|
+
columns: columnCount,
|
|
262
|
+
edges: edgeCount,
|
|
263
|
+
terms: built.terms.length,
|
|
264
|
+
writer: 'js',
|
|
265
|
+
elapsedMs: Math.round(performance.now() - startedAt),
|
|
266
|
+
warnings,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
//# sourceMappingURL=writer-js.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-dbgraph-store",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "On-disk index for dsh-plugin-dbgraph: schema, the JavaScript write path kept as the parity reference and fallback, and the read path over the global ~/.dsh/dbgraph index",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"dsh",
|
|
7
|
+
"dsh-plugin",
|
|
8
|
+
"deepseek-harness",
|
|
9
|
+
"cordis",
|
|
10
|
+
"cordis-plugin",
|
|
11
|
+
"database",
|
|
12
|
+
"sql",
|
|
13
|
+
"sqlite",
|
|
14
|
+
"mysql",
|
|
15
|
+
"postgresql",
|
|
16
|
+
"mongodb",
|
|
17
|
+
"redis",
|
|
18
|
+
"schema",
|
|
19
|
+
"ai-agent",
|
|
20
|
+
"llm-tools"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "CC ZHAO",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/CC19990113/dsh-plugin-dbgraph.git",
|
|
27
|
+
"directory": "packages/store"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/CC19990113/dsh-plugin-dbgraph#readme",
|
|
30
|
+
"bugs": "https://github.com/CC19990113/dsh-plugin-dbgraph/issues",
|
|
31
|
+
"type": "module",
|
|
32
|
+
"main": "lib/index.js",
|
|
33
|
+
"types": "lib/index.d.ts",
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"types": "./lib/index.d.ts",
|
|
37
|
+
"default": "./lib/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./invariant": {
|
|
40
|
+
"types": "./lib/invariant.d.ts",
|
|
41
|
+
"default": "./lib/invariant.js"
|
|
42
|
+
},
|
|
43
|
+
"./package.json": "./package.json"
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"lib/**/*.js",
|
|
47
|
+
"lib/**/*.d.ts"
|
|
48
|
+
],
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public",
|
|
51
|
+
"provenance": true
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"@deepseek-ai/cordis": ">=4.0.1",
|
|
55
|
+
"@deepseek-ai/dsh-invariants": ">=0.1.0-rc.6",
|
|
56
|
+
"@deepseek-ai/dsh-llm": ">=0.1.0-rc.6"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
60
|
+
"@deepseek-ai/dsh-invariants": "0.1.0-rc.6",
|
|
61
|
+
"@deepseek-ai/dsh-llm": "0.1.0-rc.6",
|
|
62
|
+
"dsh-plugin-dbgraph-service": "^0.1.0"
|
|
63
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"dsh-plugin-dbgraph-service": "^0.1.0"
|
|
66
|
+
}
|
|
6
67
|
}
|
package/README.md
DELETED