@nine-lab/nine-connector 0.1.8 → 0.1.10
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nine-lab/nine-connector",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "NineQuery AI Connector for Database",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"express": "^4.22.1",
|
|
23
23
|
"mariadb": "^3.5.2",
|
|
24
24
|
"mysql2": "^3.9.7",
|
|
25
|
+
"oracledb": "^6.10.0",
|
|
25
26
|
"pg": "^8.11.5"
|
|
26
27
|
},
|
|
27
28
|
"publishConfig": {
|
package/src/ai/AIProcessor.js
CHANGED
|
@@ -24,6 +24,7 @@ export class AIProcessor {
|
|
|
24
24
|
|
|
25
25
|
async filterTables(question, schemaSummary) {
|
|
26
26
|
if (!this.#chains['table-filter']) throw new Error("table-filter 체인 미등록");
|
|
27
|
+
|
|
27
28
|
return await this.#chains['table-filter'].invoke({
|
|
28
29
|
question,
|
|
29
30
|
schema_summary: JSON.stringify(schemaSummary, null, 2)
|
package/src/ai/AIService.js
CHANGED
|
@@ -13,14 +13,19 @@ export class AIService {
|
|
|
13
13
|
|
|
14
14
|
// 2) AI: 필요한 테이블 필터링
|
|
15
15
|
// AI 결과가 문자열일 경우를 대비해 파싱 로직이 AIProcessor에 없다면 여기서 보완 가능
|
|
16
|
-
const {
|
|
16
|
+
//const { selected_tables } = await this.ai.filterTables(question, schemaSummary);
|
|
17
|
+
const { selected_tables } = await this.ai.filterTables(question, schemaSummary);
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
console.log(selected_tables);
|
|
20
|
+
|
|
21
|
+
if (!selected_tables || selected_tables.length === 0) {
|
|
19
22
|
throw new Error("질문과 관련된 테이블을 찾을 수 없습니다.");
|
|
20
23
|
}
|
|
21
24
|
|
|
25
|
+
console.log(schemaSummary);
|
|
26
|
+
|
|
22
27
|
// 3) 상세 스키마 필터링 및 SQL 생성
|
|
23
|
-
const detailedSchema = schemaSummary.filter(t =>
|
|
28
|
+
const detailedSchema = schemaSummary.filter(t => selected_tables.includes(t.tableName));
|
|
24
29
|
const { sql, explanation } = await this.ai.generateSql(
|
|
25
30
|
question,
|
|
26
31
|
detailedSchema,
|
package/src/core/init.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url'; // 이 라인이 반드시 있어야 합니다.
|
|
3
4
|
import readline from 'readline';
|
|
4
5
|
//import { DEFAULT_PROMPTS } from './prompts.js'; // 분리한 프롬프트 가져오기
|
|
5
6
|
|
|
@@ -17,7 +18,7 @@ async function createEnvFile(rl) {
|
|
|
17
18
|
{ key: 'SERVER_PORT', label: '1. 커넥터 서버 포트', default: '3000' },
|
|
18
19
|
{ key: 'GEMINI_API_KEY', label: '2. Gemini API Key', default: '' },
|
|
19
20
|
{ key: 'GEMINI_MODEL', label: '3. 사용할 모델', default: 'gemini-2.5-flash' },
|
|
20
|
-
{ key: 'DB_TYPE', label: '4. DB 종류 (mysql,
|
|
21
|
+
{ key: 'DB_TYPE', label: '4. DB 종류 (mysql, mariadb, postgres, oracle)', default: 'mysql' },
|
|
21
22
|
{ key: 'DB_HOST', label: '5. DB 호스트', default: '127.0.0.1' },
|
|
22
23
|
{ key: 'DB_PORT', label: '6. DB 포트', default: '3306' },
|
|
23
24
|
{ key: 'DB_USER', label: '7. DB 사용자 계정', default: 'root' },
|
|
@@ -16,13 +16,14 @@ class DatabaseManager {
|
|
|
16
16
|
/**
|
|
17
17
|
* DatabaseFactory의 init() 역할을 수행합니다.
|
|
18
18
|
*/
|
|
19
|
+
// DatabaseManager.js 내부
|
|
19
20
|
async connect() {
|
|
20
21
|
try {
|
|
21
|
-
//
|
|
22
|
-
this.pool = PoolManager.createPool(this.config);
|
|
22
|
+
// 이제 createPool이 async이므로 await를 붙입니다.
|
|
23
|
+
this.pool = await PoolManager.createPool(this.config);
|
|
23
24
|
|
|
24
|
-
//
|
|
25
|
-
await this.query('SELECT 1');
|
|
25
|
+
// 연결 확인
|
|
26
|
+
await this.query(this.dialect.testQuery || 'SELECT 1');
|
|
26
27
|
console.log(`[${this.type}] Database connection successful.`);
|
|
27
28
|
} catch (error) {
|
|
28
29
|
console.error(`[${this.type}] Connection failed:`, error.message);
|
|
@@ -39,6 +40,7 @@ class DatabaseManager {
|
|
|
39
40
|
|
|
40
41
|
const result = await this.pool.query(sql, params);
|
|
41
42
|
|
|
43
|
+
/**
|
|
42
44
|
if (this.type === 'postgres') {
|
|
43
45
|
return result.rows;
|
|
44
46
|
} else {
|
|
@@ -47,7 +49,20 @@ class DatabaseManager {
|
|
|
47
49
|
return (Array.isArray(result) && Array.isArray(result[0]))
|
|
48
50
|
? result[0]
|
|
49
51
|
: result;
|
|
52
|
+
} */
|
|
53
|
+
if (this.type === 'postgres' || this.type === 'postgresql') {
|
|
54
|
+
return result.rows;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// MariaDB/MySQL 대응:
|
|
58
|
+
// DDL(INSERT/UPDATE) 결과와 DQL(SELECT) 결과를 구분해야 함
|
|
59
|
+
if (Array.isArray(result)) {
|
|
60
|
+
// mysql2 대응: rows가 0번에 담겨오는 경우
|
|
61
|
+
if (Array.isArray(result[0])) return result[0];
|
|
62
|
+
return result;
|
|
50
63
|
}
|
|
64
|
+
|
|
65
|
+
return result;
|
|
51
66
|
}
|
|
52
67
|
|
|
53
68
|
/**
|
|
@@ -63,18 +78,18 @@ class DatabaseManager {
|
|
|
63
78
|
async getTableSchema() {
|
|
64
79
|
const rows = await this.query(this.dialect.schemaQuery);
|
|
65
80
|
|
|
66
|
-
|
|
81
|
+
// 1. 우선 객체 형태로 그룹화 (중복 방지 및 효율적 정리를 위해)
|
|
82
|
+
const grouped = rows.reduce((acc, row) => {
|
|
67
83
|
const { table_name, table_comment, column_name, data_type } = row;
|
|
68
84
|
|
|
69
|
-
// 테이블이 처음 등장하면 초기화
|
|
70
85
|
if (!acc[table_name]) {
|
|
71
86
|
acc[table_name] = {
|
|
72
|
-
|
|
87
|
+
tableName: table_name, // 배열로 변환 시 식별을 위해 추가
|
|
88
|
+
description: table_comment || "",
|
|
73
89
|
columns: []
|
|
74
90
|
};
|
|
75
91
|
}
|
|
76
92
|
|
|
77
|
-
// 컬럼 정보 push
|
|
78
93
|
acc[table_name].columns.push({
|
|
79
94
|
column: column_name,
|
|
80
95
|
type: data_type
|
|
@@ -82,6 +97,9 @@ class DatabaseManager {
|
|
|
82
97
|
|
|
83
98
|
return acc;
|
|
84
99
|
}, {});
|
|
100
|
+
|
|
101
|
+
// 2. 최종적으로 객체의 값들만 뽑아서 배열로 변환
|
|
102
|
+
return Object.values(grouped);
|
|
85
103
|
}
|
|
86
104
|
}
|
|
87
105
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const Dialects = {
|
|
2
2
|
// MySQL 추가 (MariaDB와 동일)
|
|
3
3
|
mysql: {
|
|
4
|
+
testQuery: 'SELECT 1',
|
|
4
5
|
schemaQuery: `
|
|
5
6
|
SELECT
|
|
6
7
|
c.TABLE_NAME as table_name,
|
|
@@ -15,6 +16,7 @@ const Dialects = {
|
|
|
15
16
|
ORDER BY c.TABLE_NAME, c.ORDINAL_POSITION`
|
|
16
17
|
},
|
|
17
18
|
mariadb: {
|
|
19
|
+
testQuery: 'SELECT 1',
|
|
18
20
|
schemaQuery: `
|
|
19
21
|
SELECT
|
|
20
22
|
c.TABLE_NAME as table_name,
|
|
@@ -29,19 +31,24 @@ const Dialects = {
|
|
|
29
31
|
ORDER BY c.TABLE_NAME, c.ORDINAL_POSITION`
|
|
30
32
|
},
|
|
31
33
|
postgres: {
|
|
34
|
+
testQuery: 'SELECT 1',
|
|
32
35
|
schemaQuery: `
|
|
33
36
|
SELECT
|
|
34
37
|
t.table_name,
|
|
35
|
-
obj_description(pgc.oid, 'pg_class')
|
|
38
|
+
(SELECT obj_description(pgc.oid, 'pg_class')
|
|
39
|
+
FROM pg_class pgc
|
|
40
|
+
WHERE pgc.relname = t.table_name
|
|
41
|
+
AND pgc.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = t.table_schema)
|
|
42
|
+
) AS table_comment,
|
|
36
43
|
c.column_name,
|
|
37
44
|
c.data_type
|
|
38
45
|
FROM information_schema.tables t
|
|
39
|
-
JOIN information_schema.columns c ON t.table_name = c.table_name
|
|
40
|
-
|
|
41
|
-
WHERE t.table_schema = 'public'
|
|
46
|
+
JOIN information_schema.columns c ON t.table_name = c.table_name AND t.table_schema = c.table_schema
|
|
47
|
+
WHERE t.table_schema = CURRENT_SCHEMA() -- 현재 스키마 자동 대응
|
|
42
48
|
ORDER BY t.table_name, c.ordinal_position`
|
|
43
49
|
},
|
|
44
50
|
oracle: {
|
|
51
|
+
testQuery: 'SELECT 1 FROM DUAL',
|
|
45
52
|
schemaQuery: `
|
|
46
53
|
SELECT
|
|
47
54
|
t.TABLE_NAME as table_name,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as mariadb from 'mariadb';
|
|
2
2
|
import pg from 'pg';
|
|
3
|
+
//import oracledb from 'oracledb';
|
|
3
4
|
|
|
4
5
|
class PoolManager {
|
|
5
6
|
/**
|
|
@@ -15,11 +16,49 @@ class PoolManager {
|
|
|
15
16
|
case 'postgres':
|
|
16
17
|
case 'postgresql':
|
|
17
18
|
return this.#createPostgresPool(config);
|
|
19
|
+
case 'oracle':
|
|
20
|
+
return this.#createOraclePool(config); // 추가
|
|
18
21
|
default:
|
|
19
22
|
throw new Error(`[PoolManager] Unsupported DB_TYPE: ${type}`);
|
|
20
23
|
}
|
|
21
24
|
}
|
|
22
25
|
|
|
26
|
+
static async #createOraclePool(config) {
|
|
27
|
+
try {
|
|
28
|
+
const { default: oracledb } = await import('oracledb');
|
|
29
|
+
|
|
30
|
+
oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT;
|
|
31
|
+
oracledb.autoCommit = true;
|
|
32
|
+
|
|
33
|
+
const pool = await oracledb.createPool({
|
|
34
|
+
user: config.user,
|
|
35
|
+
password: config.password,
|
|
36
|
+
connectString: `${config.host}:${config.port}/${config.database}`,
|
|
37
|
+
poolMax: 10,
|
|
38
|
+
poolMin: 2,
|
|
39
|
+
poolIncrement: 1
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// 다른 DB들과 사용법을 맞추기 위해 래핑(Wrapping)
|
|
43
|
+
return {
|
|
44
|
+
query: async (sql, params = []) => {
|
|
45
|
+
let conn;
|
|
46
|
+
try {
|
|
47
|
+
conn = await pool.getConnection();
|
|
48
|
+
const result = await conn.execute(sql, params);
|
|
49
|
+
return result.rows; // 객체 배열 반환
|
|
50
|
+
} finally {
|
|
51
|
+
if (conn) await conn.close();
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
close: () => pool.close()
|
|
55
|
+
};
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.error("Oracle Pool 생성 실패:", err.message);
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
23
62
|
static #createMariaDBPool(config) {
|
|
24
63
|
return mariadb.createPool({
|
|
25
64
|
host: config.host,
|
package/src/index.js
CHANGED