@kiyeonjeon21/datacontext 0.3.2 → 0.4.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/dist/adapters/sqlite.d.ts.map +1 -1
- package/dist/adapters/sqlite.js +13 -0
- package/dist/adapters/sqlite.js.map +1 -1
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +115 -0
- package/dist/api/server.js.map +1 -1
- package/dist/cli/index.js +58 -14
- package/dist/cli/index.js.map +1 -1
- package/dist/core/context-service.d.ts +63 -0
- package/dist/core/context-service.d.ts.map +1 -1
- package/dist/core/context-service.js +66 -0
- package/dist/core/context-service.js.map +1 -1
- package/dist/core/harvester.d.ts +57 -5
- package/dist/core/harvester.d.ts.map +1 -1
- package/dist/core/harvester.js +86 -6
- package/dist/core/harvester.js.map +1 -1
- package/dist/core/types.d.ts +21 -5
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/knowledge/store.d.ts +186 -3
- package/dist/knowledge/store.d.ts.map +1 -1
- package/dist/knowledge/store.js +389 -5
- package/dist/knowledge/store.js.map +1 -1
- package/dist/knowledge/types.d.ts +252 -4
- package/dist/knowledge/types.d.ts.map +1 -1
- package/dist/knowledge/types.js +138 -1
- package/dist/knowledge/types.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +231 -3
- package/dist/mcp/tools.js.map +1 -1
- package/docs/KNOWLEDGE_GRAPH.md +540 -0
- package/docs/KNOWLEDGE_TYPES.md +261 -0
- package/docs/MULTI_DB_ARCHITECTURE.md +319 -0
- package/package.json +1 -1
- package/scripts/create-sqlite-testdb.sh +75 -0
- package/scripts/test-databases.sh +324 -0
- package/sqlite:./test-sqlite.db +0 -0
- package/src/adapters/sqlite.ts +16 -0
- package/src/api/server.ts +134 -0
- package/src/cli/index.ts +57 -16
- package/src/core/context-service.ts +70 -0
- package/src/core/harvester.ts +120 -8
- package/src/core/types.ts +21 -5
- package/src/index.ts +19 -1
- package/src/knowledge/store.ts +480 -6
- package/src/knowledge/types.ts +321 -4
- package/src/mcp/tools.ts +273 -3
- package/test-sqlite.db +0 -0
- package/tests/knowledge-store.test.ts +130 -0
package/src/knowledge/types.ts
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Knowledge Types
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the Knowledge Store / Knowledge Graph.
|
|
5
|
+
* This module defines the core data structures for storing business context
|
|
6
|
+
* about database schemas, including:
|
|
7
|
+
*
|
|
8
|
+
* - **TableDescription**: Business context for tables and columns
|
|
9
|
+
* - **TableRelationship**: Graph edges representing table relationships (FK, implicit, manual)
|
|
10
|
+
* - **QueryExample**: Verified SQL patterns with intent
|
|
11
|
+
* - **BusinessRule**: Domain constraints and conventions
|
|
12
|
+
* - **BusinessTerm**: Glossary entries for natural language → SQL translation
|
|
13
|
+
*
|
|
14
|
+
* ## Knowledge Graph Architecture
|
|
15
|
+
*
|
|
16
|
+
* The Knowledge Store is designed to evolve into a Knowledge Graph:
|
|
17
|
+
*
|
|
18
|
+
* ```
|
|
19
|
+
* ┌─────────────────────────────────────────────────────────────────┐
|
|
20
|
+
* │ Knowledge Graph │
|
|
21
|
+
* │ │
|
|
22
|
+
* │ [Table A] ──JOINS_WITH──> [Table B] ──JOINS_WITH──> [Table C]│
|
|
23
|
+
* │ │ │ │
|
|
24
|
+
* │ │ │ │
|
|
25
|
+
* │ [Description] [Description] │
|
|
26
|
+
* │ [Rules] [Rules] │
|
|
27
|
+
* │ [Examples] [Terms] │
|
|
28
|
+
* │ │
|
|
29
|
+
* └─────────────────────────────────────────────────────────────────┘
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* ## Multi-Database Support
|
|
33
|
+
*
|
|
34
|
+
* All references use `TableRef` which includes optional database identifier,
|
|
35
|
+
* enabling future cross-database relationship tracking.
|
|
36
|
+
*
|
|
37
|
+
* @module knowledge/types
|
|
4
38
|
*/
|
|
5
39
|
|
|
6
40
|
/** Source of the knowledge entry */
|
|
@@ -26,6 +60,127 @@ export interface KnowledgeMeta {
|
|
|
26
60
|
owner?: string;
|
|
27
61
|
}
|
|
28
62
|
|
|
63
|
+
// ============================================================
|
|
64
|
+
// Graph Types (Relationships)
|
|
65
|
+
// ============================================================
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Fully qualified table reference.
|
|
69
|
+
*
|
|
70
|
+
* Enables cross-database and cross-schema relationship tracking.
|
|
71
|
+
* The `database` field is optional for single-database scenarios.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```typescript
|
|
75
|
+
* // Single database (most common)
|
|
76
|
+
* const ref: TableRef = { schema: 'public', table: 'users' };
|
|
77
|
+
*
|
|
78
|
+
* // Multi-database (future)
|
|
79
|
+
* const ref: TableRef = { database: 'analytics', schema: 'public', table: 'events' };
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export interface TableRef {
|
|
83
|
+
/** Database identifier (optional for single-DB mode) */
|
|
84
|
+
database?: string;
|
|
85
|
+
/** Schema name */
|
|
86
|
+
schema: string;
|
|
87
|
+
/** Table name */
|
|
88
|
+
table: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Relationship type between tables.
|
|
93
|
+
*
|
|
94
|
+
* - `foreign_key`: Relationship derived from database FK constraint
|
|
95
|
+
* - `implicit_join`: Relationship inferred from query patterns (no FK)
|
|
96
|
+
* - `manual`: Relationship defined manually by user
|
|
97
|
+
*/
|
|
98
|
+
export type RelationshipType = 'foreign_key' | 'implicit_join' | 'manual';
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Cardinality of a table relationship.
|
|
102
|
+
*
|
|
103
|
+
* - `one-to-one`: Each row in A maps to exactly one row in B
|
|
104
|
+
* - `one-to-many`: Each row in A maps to multiple rows in B (most common for FK)
|
|
105
|
+
* - `many-to-one`: Multiple rows in A map to one row in B
|
|
106
|
+
* - `many-to-many`: Requires junction table
|
|
107
|
+
*/
|
|
108
|
+
export type RelationshipCardinality = 'one-to-one' | 'one-to-many' | 'many-to-one' | 'many-to-many';
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Table relationship representing a graph edge.
|
|
112
|
+
*
|
|
113
|
+
* This is the core type for the Knowledge Graph, representing how tables
|
|
114
|
+
* are connected. Relationships can be:
|
|
115
|
+
* - Auto-harvested from foreign key constraints
|
|
116
|
+
* - Inferred from query patterns (implicit joins)
|
|
117
|
+
* - Manually defined by users
|
|
118
|
+
*
|
|
119
|
+
* ## Graph Traversal
|
|
120
|
+
*
|
|
121
|
+
* Relationships enable graph queries like:
|
|
122
|
+
* - "Find all tables related to users"
|
|
123
|
+
* - "Find the shortest join path from orders to products"
|
|
124
|
+
* - "Which tables reference this column?"
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```typescript
|
|
128
|
+
* // FK-based relationship (auto-harvested)
|
|
129
|
+
* const relationship: TableRelationship = {
|
|
130
|
+
* type: 'table_relationship',
|
|
131
|
+
* from: { schema: 'public', table: 'orders' },
|
|
132
|
+
* to: { schema: 'public', table: 'users' },
|
|
133
|
+
* relationshipType: 'foreign_key',
|
|
134
|
+
* joinCondition: 'orders.user_id = users.id',
|
|
135
|
+
* cardinality: 'many-to-one',
|
|
136
|
+
* fromColumns: ['user_id'],
|
|
137
|
+
* toColumns: ['id'],
|
|
138
|
+
* isPreferred: true,
|
|
139
|
+
* // ... metadata fields
|
|
140
|
+
* };
|
|
141
|
+
*
|
|
142
|
+
* // Implicit join (learned from queries)
|
|
143
|
+
* const implicit: TableRelationship = {
|
|
144
|
+
* type: 'table_relationship',
|
|
145
|
+
* from: { schema: 'public', table: 'events' },
|
|
146
|
+
* to: { schema: 'public', table: 'users' },
|
|
147
|
+
* relationshipType: 'implicit_join',
|
|
148
|
+
* joinCondition: 'events.actor_id = users.id',
|
|
149
|
+
* cardinality: 'many-to-one',
|
|
150
|
+
* isPreferred: false,
|
|
151
|
+
* notes: 'Inferred from query patterns',
|
|
152
|
+
* // ... metadata fields
|
|
153
|
+
* };
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
export interface TableRelationship extends KnowledgeMeta {
|
|
157
|
+
type: 'table_relationship';
|
|
158
|
+
/** Source table (FK side for foreign_key type) */
|
|
159
|
+
from: TableRef;
|
|
160
|
+
/** Target table (referenced side for foreign_key type) */
|
|
161
|
+
to: TableRef;
|
|
162
|
+
/** How this relationship was discovered */
|
|
163
|
+
relationshipType: RelationshipType;
|
|
164
|
+
/** SQL join condition */
|
|
165
|
+
joinCondition: string;
|
|
166
|
+
/** Relationship cardinality */
|
|
167
|
+
cardinality?: RelationshipCardinality;
|
|
168
|
+
/** Columns in the source table involved in the join */
|
|
169
|
+
fromColumns?: string[];
|
|
170
|
+
/** Columns in the target table involved in the join */
|
|
171
|
+
toColumns?: string[];
|
|
172
|
+
/** Whether this is the preferred join path (when multiple exist) */
|
|
173
|
+
isPreferred: boolean;
|
|
174
|
+
/** FK constraint name (if from database) */
|
|
175
|
+
constraintName?: string;
|
|
176
|
+
/** Human-readable notes about this relationship */
|
|
177
|
+
notes?: string;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ============================================================
|
|
181
|
+
// Entity Types (Nodes)
|
|
182
|
+
// ============================================================
|
|
183
|
+
|
|
29
184
|
/** Column description */
|
|
30
185
|
export interface ColumnDescription {
|
|
31
186
|
/** Column name */
|
|
@@ -135,9 +290,42 @@ export interface BusinessTerm extends KnowledgeMeta {
|
|
|
135
290
|
}
|
|
136
291
|
|
|
137
292
|
/** Union type for all knowledge entries */
|
|
138
|
-
export type KnowledgeEntry =
|
|
293
|
+
export type KnowledgeEntry =
|
|
294
|
+
| TableDescription
|
|
295
|
+
| TableRelationship
|
|
296
|
+
| QueryExample
|
|
297
|
+
| BusinessRule
|
|
298
|
+
| BusinessTerm;
|
|
139
299
|
|
|
140
|
-
/**
|
|
300
|
+
/**
|
|
301
|
+
* Complete knowledge data structure.
|
|
302
|
+
*
|
|
303
|
+
* This is the root data structure for the Knowledge Store, containing:
|
|
304
|
+
* - **Nodes**: Tables, columns (within TableDescription)
|
|
305
|
+
* - **Edges**: Relationships between tables (TableRelationship)
|
|
306
|
+
* - **Annotations**: Rules, examples, terms
|
|
307
|
+
*
|
|
308
|
+
* ## Data Model (Graph Perspective)
|
|
309
|
+
*
|
|
310
|
+
* ```
|
|
311
|
+
* Nodes:
|
|
312
|
+
* - TableDescription (with embedded ColumnDescription[])
|
|
313
|
+
*
|
|
314
|
+
* Edges:
|
|
315
|
+
* - TableRelationship (from → to with join condition)
|
|
316
|
+
*
|
|
317
|
+
* Annotations (attached to nodes):
|
|
318
|
+
* - QueryExample (references tables)
|
|
319
|
+
* - BusinessRule (applies to tables/columns)
|
|
320
|
+
* - BusinessTerm (applies to tables/columns)
|
|
321
|
+
* ```
|
|
322
|
+
*
|
|
323
|
+
* ## Version History
|
|
324
|
+
*
|
|
325
|
+
* - `1.0.0`: Initial version (tables, examples, rules)
|
|
326
|
+
* - `1.1.0`: Added businessTerms (glossary)
|
|
327
|
+
* - `2.0.0`: Added tableRelationships (graph edges)
|
|
328
|
+
*/
|
|
141
329
|
export interface KnowledgeData {
|
|
142
330
|
/** Version of the knowledge format */
|
|
143
331
|
version: string;
|
|
@@ -147,8 +335,16 @@ export interface KnowledgeData {
|
|
|
147
335
|
schemaHash: string;
|
|
148
336
|
/** Last sync timestamp */
|
|
149
337
|
lastSyncAt: string;
|
|
150
|
-
|
|
338
|
+
|
|
339
|
+
// === Nodes ===
|
|
340
|
+
/** Table descriptions (graph nodes) */
|
|
151
341
|
tableDescriptions: TableDescription[];
|
|
342
|
+
|
|
343
|
+
// === Edges ===
|
|
344
|
+
/** Table relationships (graph edges) - NEW in v2.0 */
|
|
345
|
+
tableRelationships: TableRelationship[];
|
|
346
|
+
|
|
347
|
+
// === Annotations ===
|
|
152
348
|
/** Query examples */
|
|
153
349
|
queryExamples: QueryExample[];
|
|
154
350
|
/** Business rules */
|
|
@@ -157,6 +353,10 @@ export interface KnowledgeData {
|
|
|
157
353
|
businessTerms: BusinessTerm[];
|
|
158
354
|
}
|
|
159
355
|
|
|
356
|
+
// ============================================================
|
|
357
|
+
// Factory Functions
|
|
358
|
+
// ============================================================
|
|
359
|
+
|
|
160
360
|
/** Generate a unique ID */
|
|
161
361
|
export function generateId(): string {
|
|
162
362
|
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
@@ -181,3 +381,120 @@ export function createKnowledgeMeta(
|
|
|
181
381
|
};
|
|
182
382
|
}
|
|
183
383
|
|
|
384
|
+
/**
|
|
385
|
+
* Create a TableRef from table name and optional schema/database.
|
|
386
|
+
*
|
|
387
|
+
* @param table - Table name
|
|
388
|
+
* @param schema - Schema name (defaults to 'public')
|
|
389
|
+
* @param database - Database name (optional)
|
|
390
|
+
* @returns TableRef object
|
|
391
|
+
*
|
|
392
|
+
* @example
|
|
393
|
+
* ```typescript
|
|
394
|
+
* const ref = createTableRef('users');
|
|
395
|
+
* // { schema: 'public', table: 'users' }
|
|
396
|
+
*
|
|
397
|
+
* const ref2 = createTableRef('orders', 'sales', 'analytics_db');
|
|
398
|
+
* // { database: 'analytics_db', schema: 'sales', table: 'orders' }
|
|
399
|
+
* ```
|
|
400
|
+
*/
|
|
401
|
+
export function createTableRef(
|
|
402
|
+
table: string,
|
|
403
|
+
schema: string = 'public',
|
|
404
|
+
database?: string
|
|
405
|
+
): TableRef {
|
|
406
|
+
const ref: TableRef = { schema, table };
|
|
407
|
+
if (database) {
|
|
408
|
+
ref.database = database;
|
|
409
|
+
}
|
|
410
|
+
return ref;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Convert TableRef to a fully qualified string representation.
|
|
415
|
+
*
|
|
416
|
+
* @param ref - TableRef to stringify
|
|
417
|
+
* @returns String like "database.schema.table" or "schema.table"
|
|
418
|
+
*
|
|
419
|
+
* @example
|
|
420
|
+
* ```typescript
|
|
421
|
+
* tableRefToString({ schema: 'public', table: 'users' })
|
|
422
|
+
* // "public.users"
|
|
423
|
+
*
|
|
424
|
+
* tableRefToString({ database: 'analytics', schema: 'public', table: 'events' })
|
|
425
|
+
* // "analytics.public.events"
|
|
426
|
+
* ```
|
|
427
|
+
*/
|
|
428
|
+
export function tableRefToString(ref: TableRef): string {
|
|
429
|
+
if (ref.database) {
|
|
430
|
+
return `${ref.database}.${ref.schema}.${ref.table}`;
|
|
431
|
+
}
|
|
432
|
+
return `${ref.schema}.${ref.table}`;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Check if two TableRefs refer to the same table.
|
|
437
|
+
*
|
|
438
|
+
* @param a - First TableRef
|
|
439
|
+
* @param b - Second TableRef
|
|
440
|
+
* @returns true if they refer to the same table
|
|
441
|
+
*/
|
|
442
|
+
export function tableRefsEqual(a: TableRef, b: TableRef): boolean {
|
|
443
|
+
return (
|
|
444
|
+
a.table === b.table &&
|
|
445
|
+
a.schema === b.schema &&
|
|
446
|
+
(a.database ?? '') === (b.database ?? '')
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Create a TableRelationship from foreign key information.
|
|
452
|
+
*
|
|
453
|
+
* This is the standard way to create relationships from harvested FK data.
|
|
454
|
+
*
|
|
455
|
+
* @param params - Relationship parameters
|
|
456
|
+
* @returns Complete TableRelationship object
|
|
457
|
+
*
|
|
458
|
+
* @example
|
|
459
|
+
* ```typescript
|
|
460
|
+
* const relationship = createRelationshipFromFK({
|
|
461
|
+
* fromTable: 'orders',
|
|
462
|
+
* fromSchema: 'public',
|
|
463
|
+
* fromColumn: 'user_id',
|
|
464
|
+
* toTable: 'users',
|
|
465
|
+
* toSchema: 'public',
|
|
466
|
+
* toColumn: 'id',
|
|
467
|
+
* constraintName: 'orders_user_id_fkey',
|
|
468
|
+
* schemaHash: 'abc123',
|
|
469
|
+
* });
|
|
470
|
+
* ```
|
|
471
|
+
*/
|
|
472
|
+
export function createRelationshipFromFK(params: {
|
|
473
|
+
fromTable: string;
|
|
474
|
+
fromSchema?: string;
|
|
475
|
+
fromColumn: string;
|
|
476
|
+
toTable: string;
|
|
477
|
+
toSchema?: string;
|
|
478
|
+
toColumn: string;
|
|
479
|
+
constraintName?: string;
|
|
480
|
+
schemaHash: string;
|
|
481
|
+
database?: string;
|
|
482
|
+
}): TableRelationship {
|
|
483
|
+
const fromSchema = params.fromSchema ?? 'public';
|
|
484
|
+
const toSchema = params.toSchema ?? 'public';
|
|
485
|
+
|
|
486
|
+
return {
|
|
487
|
+
...createKnowledgeMeta('auto', params.schemaHash),
|
|
488
|
+
type: 'table_relationship',
|
|
489
|
+
from: createTableRef(params.fromTable, fromSchema, params.database),
|
|
490
|
+
to: createTableRef(params.toTable, toSchema, params.database),
|
|
491
|
+
relationshipType: 'foreign_key',
|
|
492
|
+
joinCondition: `${params.fromTable}.${params.fromColumn} = ${params.toTable}.${params.toColumn}`,
|
|
493
|
+
cardinality: 'many-to-one', // Default for FK
|
|
494
|
+
fromColumns: [params.fromColumn],
|
|
495
|
+
toColumns: [params.toColumn],
|
|
496
|
+
isPreferred: true,
|
|
497
|
+
constraintName: params.constraintName,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
package/src/mcp/tools.ts
CHANGED
|
@@ -355,6 +355,54 @@ export function getMcpTools(): Tool[] {
|
|
|
355
355
|
required: ['query'],
|
|
356
356
|
},
|
|
357
357
|
},
|
|
358
|
+
// === Knowledge Graph Tools ===
|
|
359
|
+
{
|
|
360
|
+
name: 'find_join_path',
|
|
361
|
+
description: 'Find the optimal join path between two tables using the Knowledge Graph. Automatically discovers relationships and suggests JOIN conditions. This is useful when you need to join tables that are not directly related.',
|
|
362
|
+
inputSchema: {
|
|
363
|
+
type: 'object',
|
|
364
|
+
properties: {
|
|
365
|
+
fromTable: {
|
|
366
|
+
type: 'string',
|
|
367
|
+
description: 'Source table name to start from.',
|
|
368
|
+
},
|
|
369
|
+
toTable: {
|
|
370
|
+
type: 'string',
|
|
371
|
+
description: 'Destination table name to reach.',
|
|
372
|
+
},
|
|
373
|
+
schema: {
|
|
374
|
+
type: 'string',
|
|
375
|
+
description: 'Database schema. Defaults to "public" for PostgreSQL, "main" for SQLite.',
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
required: ['fromTable', 'toTable'],
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
name: 'get_relationships',
|
|
383
|
+
description: 'Get all table relationships (foreign keys, joins) from the Knowledge Graph. Returns information about how tables are connected.',
|
|
384
|
+
inputSchema: {
|
|
385
|
+
type: 'object',
|
|
386
|
+
properties: {
|
|
387
|
+
table: {
|
|
388
|
+
type: 'string',
|
|
389
|
+
description: 'Optional: Filter to relationships involving this table.',
|
|
390
|
+
},
|
|
391
|
+
schema: {
|
|
392
|
+
type: 'string',
|
|
393
|
+
description: 'Database schema. Defaults to "public".',
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
name: 'get_graph_summary',
|
|
400
|
+
description: 'Get a summary of the Knowledge Graph including total tables, relationships, and connectivity statistics.',
|
|
401
|
+
inputSchema: {
|
|
402
|
+
type: 'object',
|
|
403
|
+
properties: {},
|
|
404
|
+
},
|
|
405
|
+
},
|
|
358
406
|
];
|
|
359
407
|
}
|
|
360
408
|
|
|
@@ -401,6 +449,13 @@ export async function handleToolCall(
|
|
|
401
449
|
return handleSearchTerms(args, context);
|
|
402
450
|
case 'enhance_query':
|
|
403
451
|
return handleEnhanceQuery(args, context);
|
|
452
|
+
// === Knowledge Graph Tools ===
|
|
453
|
+
case 'find_join_path':
|
|
454
|
+
return handleFindJoinPath(args, context);
|
|
455
|
+
case 'get_relationships':
|
|
456
|
+
return handleGetRelationships(args, context);
|
|
457
|
+
case 'get_graph_summary':
|
|
458
|
+
return handleGetGraphSummary(args, context);
|
|
404
459
|
default:
|
|
405
460
|
throw new Error(`Unknown tool: ${name}`);
|
|
406
461
|
}
|
|
@@ -589,6 +644,9 @@ async function handleDescribeTable(
|
|
|
589
644
|
|
|
590
645
|
/**
|
|
591
646
|
* Handle get_context tool
|
|
647
|
+
*
|
|
648
|
+
* Enhanced to include table relationships from the Knowledge Graph.
|
|
649
|
+
* This helps AI understand how tables connect and generate accurate JOINs.
|
|
592
650
|
*/
|
|
593
651
|
async function handleGetContext(
|
|
594
652
|
args: Record<string, unknown>,
|
|
@@ -600,14 +658,41 @@ async function handleGetContext(
|
|
|
600
658
|
throw new Error('Tables array is required');
|
|
601
659
|
}
|
|
602
660
|
|
|
603
|
-
|
|
661
|
+
// Use buildContextWithRelationships to include relationship information
|
|
662
|
+
const contextText = context.knowledge.buildContextWithRelationships(tables);
|
|
604
663
|
|
|
605
664
|
// Also get query examples
|
|
606
665
|
const examples = context.knowledge.getQueryExamplesForTables(tables);
|
|
607
666
|
|
|
667
|
+
// Get relationships involving these tables
|
|
668
|
+
const allRelationships = context.knowledge.getRelationships();
|
|
669
|
+
const relevantRelationships = allRelationships.filter(
|
|
670
|
+
rel => tables.includes(rel.from.table) || tables.includes(rel.to.table)
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
// Find related tables (1-hop connections)
|
|
674
|
+
const relatedTables = new Set<string>();
|
|
675
|
+
for (const table of tables) {
|
|
676
|
+
const rels = context.knowledge.getRelatedTables(table);
|
|
677
|
+
for (const rel of rels) {
|
|
678
|
+
relatedTables.add(rel.from.table);
|
|
679
|
+
relatedTables.add(rel.to.table);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
// Remove tables already in the query
|
|
683
|
+
tables.forEach(t => relatedTables.delete(t));
|
|
684
|
+
|
|
608
685
|
return {
|
|
609
686
|
context: contextText,
|
|
610
687
|
tables,
|
|
688
|
+
relatedTables: Array.from(relatedTables),
|
|
689
|
+
relationships: relevantRelationships.map(rel => ({
|
|
690
|
+
from: `${rel.from.schema}.${rel.from.table}`,
|
|
691
|
+
to: `${rel.to.schema}.${rel.to.table}`,
|
|
692
|
+
type: rel.relationshipType,
|
|
693
|
+
joinCondition: rel.joinCondition,
|
|
694
|
+
cardinality: rel.cardinality,
|
|
695
|
+
})),
|
|
611
696
|
queryExamples: examples.slice(0, 5).map(ex => ({
|
|
612
697
|
intent: ex.intent,
|
|
613
698
|
sql: ex.sql,
|
|
@@ -709,13 +794,15 @@ async function handleHarvestMetadata(
|
|
|
709
794
|
}
|
|
710
795
|
|
|
711
796
|
const result = await harvester.syncToKnowledge(schema);
|
|
797
|
+
const relMsg = result.relationshipsAdded ? `, ${result.relationshipsAdded} relationships` : '';
|
|
712
798
|
return {
|
|
713
799
|
success: true,
|
|
714
800
|
schema,
|
|
715
801
|
tablesProcessed: result.tablesProcessed,
|
|
716
802
|
descriptionsAdded: result.descriptionsAdded,
|
|
717
803
|
columnsAdded: result.columnsAdded,
|
|
718
|
-
|
|
804
|
+
relationshipsAdded: result.relationshipsAdded ?? 0,
|
|
805
|
+
message: `Harvested ${result.tablesProcessed} tables. Added ${result.descriptionsAdded} table descriptions, ${result.columnsAdded} column descriptions${relMsg}.`,
|
|
719
806
|
};
|
|
720
807
|
}
|
|
721
808
|
|
|
@@ -730,13 +817,15 @@ async function handleHarvestMetadata(
|
|
|
730
817
|
}
|
|
731
818
|
|
|
732
819
|
const result = await context.harvester.syncToKnowledge(schema);
|
|
820
|
+
const relMsg = result.relationshipsAdded ? `, ${result.relationshipsAdded} relationships` : '';
|
|
733
821
|
return {
|
|
734
822
|
success: true,
|
|
735
823
|
schema,
|
|
736
824
|
tablesProcessed: result.tablesProcessed,
|
|
737
825
|
descriptionsAdded: result.descriptionsAdded,
|
|
738
826
|
columnsAdded: result.columnsAdded,
|
|
739
|
-
|
|
827
|
+
relationshipsAdded: result.relationshipsAdded ?? 0,
|
|
828
|
+
message: `Harvested ${result.tablesProcessed} tables. Added ${result.descriptionsAdded} table descriptions, ${result.columnsAdded} column descriptions${relMsg}.`,
|
|
740
829
|
};
|
|
741
830
|
}
|
|
742
831
|
|
|
@@ -1235,6 +1324,187 @@ function calculateTermRelevance(query: string, term: { term: string; synonyms: s
|
|
|
1235
1324
|
return 0.5;
|
|
1236
1325
|
}
|
|
1237
1326
|
|
|
1327
|
+
// ============================================================
|
|
1328
|
+
// Knowledge Graph Tools
|
|
1329
|
+
// ============================================================
|
|
1330
|
+
|
|
1331
|
+
/**
|
|
1332
|
+
* Handle find_join_path tool
|
|
1333
|
+
*
|
|
1334
|
+
* Uses the Knowledge Graph to find the optimal path between two tables.
|
|
1335
|
+
* This is particularly useful for complex schemas where tables are connected
|
|
1336
|
+
* through intermediate tables (e.g., order_items → orders → users).
|
|
1337
|
+
*
|
|
1338
|
+
* @example
|
|
1339
|
+
* // Request:
|
|
1340
|
+
* { fromTable: "order_items", toTable: "users" }
|
|
1341
|
+
*
|
|
1342
|
+
* // Response:
|
|
1343
|
+
* {
|
|
1344
|
+
* path: [
|
|
1345
|
+
* { from: "order_items", to: "orders", join: "order_items.order_id = orders.id" },
|
|
1346
|
+
* { from: "orders", to: "users", join: "orders.user_id = users.id" }
|
|
1347
|
+
* ],
|
|
1348
|
+
* sqlHint: "JOIN orders ON ... JOIN users ON ..."
|
|
1349
|
+
* }
|
|
1350
|
+
*/
|
|
1351
|
+
async function handleFindJoinPath(
|
|
1352
|
+
args: Record<string, unknown>,
|
|
1353
|
+
context: ToolContext
|
|
1354
|
+
): Promise<unknown> {
|
|
1355
|
+
const fromTable = args.fromTable as string;
|
|
1356
|
+
const toTable = args.toTable as string;
|
|
1357
|
+
const schema = (args.schema as string) ?? 'public';
|
|
1358
|
+
|
|
1359
|
+
if (!fromTable || !toTable) {
|
|
1360
|
+
throw new Error('Both fromTable and toTable are required');
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// Find path using BFS in the Knowledge Graph
|
|
1364
|
+
const path = context.knowledge.findJoinPath(fromTable, toTable, schema);
|
|
1365
|
+
|
|
1366
|
+
if (path.length === 0) {
|
|
1367
|
+
// Try with 'main' schema for SQLite
|
|
1368
|
+
const pathMain = context.knowledge.findJoinPath(fromTable, toTable, 'main');
|
|
1369
|
+
if (pathMain.length === 0) {
|
|
1370
|
+
return {
|
|
1371
|
+
found: false,
|
|
1372
|
+
message: `No path found between '${fromTable}' and '${toTable}'. These tables may not be connected via foreign keys. Consider using implicit joins or check if the tables exist.`,
|
|
1373
|
+
suggestion: 'Run harvest_metadata to discover relationships, or manually define a relationship.',
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
return formatJoinPathResult(fromTable, toTable, pathMain);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
return formatJoinPathResult(fromTable, toTable, path);
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
/**
|
|
1383
|
+
* Format join path result for AI consumption
|
|
1384
|
+
*/
|
|
1385
|
+
function formatJoinPathResult(
|
|
1386
|
+
fromTable: string,
|
|
1387
|
+
toTable: string,
|
|
1388
|
+
path: Array<{ from: { schema: string; table: string }; to: { schema: string; table: string }; joinCondition: string; cardinality?: string }>
|
|
1389
|
+
): unknown {
|
|
1390
|
+
const steps = path.map((rel, idx) => ({
|
|
1391
|
+
step: idx + 1,
|
|
1392
|
+
from: `${rel.from.schema}.${rel.from.table}`,
|
|
1393
|
+
to: `${rel.to.schema}.${rel.to.table}`,
|
|
1394
|
+
joinCondition: rel.joinCondition,
|
|
1395
|
+
cardinality: rel.cardinality || 'unknown',
|
|
1396
|
+
}));
|
|
1397
|
+
|
|
1398
|
+
// Generate SQL hint
|
|
1399
|
+
const sqlParts = path.map(rel =>
|
|
1400
|
+
`JOIN ${rel.to.table} ON ${rel.joinCondition}`
|
|
1401
|
+
);
|
|
1402
|
+
const sqlHint = `FROM ${fromTable}\n${sqlParts.join('\n')}`;
|
|
1403
|
+
|
|
1404
|
+
// Generate human-readable explanation
|
|
1405
|
+
const pathDescription = path.map(rel =>
|
|
1406
|
+
`${rel.from.table} → ${rel.to.table}`
|
|
1407
|
+
).join(' → ');
|
|
1408
|
+
|
|
1409
|
+
return {
|
|
1410
|
+
found: true,
|
|
1411
|
+
from: fromTable,
|
|
1412
|
+
to: toTable,
|
|
1413
|
+
hops: path.length,
|
|
1414
|
+
path: steps,
|
|
1415
|
+
pathDescription,
|
|
1416
|
+
sqlHint,
|
|
1417
|
+
message: `Found a ${path.length}-hop path: ${pathDescription}`,
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
/**
|
|
1422
|
+
* Handle get_relationships tool
|
|
1423
|
+
*
|
|
1424
|
+
* Returns all relationships from the Knowledge Graph, optionally filtered by table.
|
|
1425
|
+
*/
|
|
1426
|
+
async function handleGetRelationships(
|
|
1427
|
+
args: Record<string, unknown>,
|
|
1428
|
+
context: ToolContext
|
|
1429
|
+
): Promise<unknown> {
|
|
1430
|
+
const table = args.table as string | undefined;
|
|
1431
|
+
const schema = (args.schema as string) ?? 'public';
|
|
1432
|
+
|
|
1433
|
+
let relationships = context.knowledge.getRelationships();
|
|
1434
|
+
|
|
1435
|
+
// Filter by table if specified
|
|
1436
|
+
if (table) {
|
|
1437
|
+
relationships = relationships.filter(
|
|
1438
|
+
rel => rel.from.table === table || rel.to.table === table
|
|
1439
|
+
);
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
if (relationships.length === 0) {
|
|
1443
|
+
return {
|
|
1444
|
+
count: 0,
|
|
1445
|
+
relationships: [],
|
|
1446
|
+
message: table
|
|
1447
|
+
? `No relationships found for table '${table}'. Run harvest_metadata to discover foreign keys.`
|
|
1448
|
+
: 'No relationships in Knowledge Graph. Run harvest_metadata to discover foreign keys.',
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
return {
|
|
1453
|
+
count: relationships.length,
|
|
1454
|
+
relationships: relationships.map(rel => ({
|
|
1455
|
+
id: rel.id,
|
|
1456
|
+
from: `${rel.from.schema}.${rel.from.table}`,
|
|
1457
|
+
to: `${rel.to.schema}.${rel.to.table}`,
|
|
1458
|
+
type: rel.relationshipType,
|
|
1459
|
+
joinCondition: rel.joinCondition,
|
|
1460
|
+
cardinality: rel.cardinality || 'unknown',
|
|
1461
|
+
isPreferred: rel.isPreferred,
|
|
1462
|
+
notes: rel.notes,
|
|
1463
|
+
})),
|
|
1464
|
+
message: `Found ${relationships.length} relationship(s)${table ? ` involving '${table}'` : ''}.`,
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
/**
|
|
1469
|
+
* Handle get_graph_summary tool
|
|
1470
|
+
*
|
|
1471
|
+
* Returns summary statistics about the Knowledge Graph.
|
|
1472
|
+
*/
|
|
1473
|
+
async function handleGetGraphSummary(
|
|
1474
|
+
args: Record<string, unknown>,
|
|
1475
|
+
context: ToolContext
|
|
1476
|
+
): Promise<unknown> {
|
|
1477
|
+
const summary = context.knowledge.getGraphSummary();
|
|
1478
|
+
const relationships = context.knowledge.getRelationships();
|
|
1479
|
+
|
|
1480
|
+
// Build a quick connectivity summary
|
|
1481
|
+
const tableConnections = new Map<string, number>();
|
|
1482
|
+
for (const rel of relationships) {
|
|
1483
|
+
const fromKey = rel.from.table;
|
|
1484
|
+
const toKey = rel.to.table;
|
|
1485
|
+
tableConnections.set(fromKey, (tableConnections.get(fromKey) || 0) + 1);
|
|
1486
|
+
tableConnections.set(toKey, (tableConnections.get(toKey) || 0) + 1);
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
// Sort by connections
|
|
1490
|
+
const sortedTables = Array.from(tableConnections.entries())
|
|
1491
|
+
.sort((a, b) => b[1] - a[1])
|
|
1492
|
+
.slice(0, 10)
|
|
1493
|
+
.map(([table, connections]) => ({ table, connections }));
|
|
1494
|
+
|
|
1495
|
+
return {
|
|
1496
|
+
nodeCount: summary.nodeCount,
|
|
1497
|
+
edgeCount: summary.edgeCount,
|
|
1498
|
+
tablesWithRelationships: summary.tablesWithRelationships,
|
|
1499
|
+
isolatedTables: summary.isolatedTables,
|
|
1500
|
+
relationshipsByType: summary.relationshipsByType,
|
|
1501
|
+
mostConnectedTables: sortedTables,
|
|
1502
|
+
message: summary.edgeCount > 0
|
|
1503
|
+
? `Knowledge Graph has ${summary.edgeCount} relationships connecting ${summary.tablesWithRelationships} tables.`
|
|
1504
|
+
: 'Knowledge Graph is empty. Run harvest_metadata to discover relationships.',
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1238
1508
|
// ============================================================
|
|
1239
1509
|
// Utility Functions
|
|
1240
1510
|
// ============================================================
|
package/test-sqlite.db
ADDED
|
Binary file
|