crushdataai 1.2.17 → 1.2.18
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 +4 -4
- package/assets/.agent/workflows/data-analyst.md +30 -2
- package/assets/.claude/skills/data-analyst/SKILL.md +37 -0
- package/assets/.cursor/commands/data-analyst.md +30 -2
- package/assets/.github/prompts/data-analyst.prompt.md +30 -2
- package/assets/.kiro/steering/data-analyst.md +30 -2
- package/assets/.windsurf/workflows/data-analyst.md +30 -2
- package/dist/connectors/clickhouse/index.d.ts +12 -0
- package/dist/connectors/clickhouse/index.js +140 -0
- package/dist/connectors/databricks/index.d.ts +12 -0
- package/dist/connectors/databricks/index.js +105 -0
- package/dist/connectors/mongodb/index.d.ts +12 -0
- package/dist/connectors/mongodb/index.js +156 -0
- package/dist/connectors/mssql/index.d.ts +12 -0
- package/dist/connectors/mssql/index.js +181 -0
- package/dist/connectors/redshift/index.d.ts +12 -0
- package/dist/connectors/redshift/index.js +178 -0
- package/dist/routes/dashboard.js +84 -2
- package/dist/services/query-executor.js +67 -16
- package/dist/types/dashboard.d.ts +2 -1
- package/package.json +9 -3
- package/ui-dashboard-dist/assets/index-Ch04z44X.css +1 -0
- package/ui-dashboard-dist/assets/{index-SkyAs8Zl.js → index-DHr1UjyZ.js} +148 -148
- package/ui-dashboard-dist/index.html +2 -2
- package/assets/images/crushdataai-dashboard-simple-charts.png +0 -0
- package/assets/images/crushdataai-dashboard.png +0 -0
- package/assets/images/crushdataai-data-connection-ui.png +0 -0
- package/assets/images/crushdataai-landing-page.png +0 -0
- package/ui-dashboard-dist/assets/index-uepFwkLY.css +0 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
An AI skill that provides structured, professional data analysis workflows with built-in validation - helping AI coding assistants perform data analysis like a careful human analyst.
|
|
6
6
|
|
|
7
|
-

|
|
8
8
|
|
|
9
9
|
## 🎯 What It Does
|
|
10
10
|
|
|
@@ -83,7 +83,7 @@ crushdataai connect
|
|
|
83
83
|
- **Supported Types**: CSV, MySQL, PostgreSQL, Shopify, BigQuery, Snowflake
|
|
84
84
|
- **Private & Secure**: Credentials are stored **locally** on your machine (`~/.crushdataai/connections.json`). They are **never** uploaded to any server or included in the npm package.
|
|
85
85
|
|
|
86
|
-

|
|
87
87
|
|
|
88
88
|
> [!NOTE]
|
|
89
89
|
> **Persistence**: Once you add a connection, you can **close the UI** (Ctrl+C). The AI IDE reads the saved connection details directly from your local config file, so the server does NOT need to keep running.
|
|
@@ -108,10 +108,10 @@ crushdataai dashboard
|
|
|
108
108
|
npx crushdataai dashboard
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-

|
|
112
112
|
*Advanced charts visualization (Funnel, Gauge, Radar, etc.)*
|
|
113
113
|
|
|
114
|
-

|
|
115
115
|
*Standard charts visualization (Line, Bar, Pie, etc.)*
|
|
116
116
|
|
|
117
117
|
### 2. Features
|
|
@@ -130,10 +130,38 @@ dashboard = {
|
|
|
130
130
|
"charts": [{"id": "chart-1", "type": "line", "title": "Trend", "data": {"labels": ["Jan","Feb"], "datasets": [{"label": "Revenue", "values": [10000,20000]}]}}]
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
with open("reports/dashboards/dashboard.json", "w") as f:
|
|
133
|
+
with open("reports/dashboards/dashboard.json", "w") as f:
|
|
134
134
|
json.dump(dashboard, f, indent=2)
|
|
135
135
|
```
|
|
136
|
-
|
|
136
|
+
|
|
137
|
+
### 5b. Making Charts Refreshable (Recommended)
|
|
138
|
+
|
|
139
|
+
To allow the user to refresh data directly from the dashboard:
|
|
140
|
+
1. Include a `query` object in the chart definition.
|
|
141
|
+
2. Run `npx crushdataai connections` to list available connection names (secure - no passwords shown).
|
|
142
|
+
3. Set `connection` to one of the listed names.
|
|
143
|
+
4. Set `sql` to the query used to generate the data.
|
|
144
|
+
|
|
145
|
+
> **SECURITY**: Never read `.env` directly to find connection names. Always use `npx crushdataai connections`.
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
"query": {
|
|
149
|
+
"connection": "my_postgres_db",
|
|
150
|
+
"sql": "SELECT date, revenue FROM sales WHERE..."
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Database Specifics:**
|
|
155
|
+
- **SQL/Databases**: Provide the full SQL query.
|
|
156
|
+
- **Shopify**: Provide the resource name (e.g. `orders`).
|
|
157
|
+
- **CSV**: Provide the connection name. `sql` is ignored but required (set to "default").
|
|
158
|
+
- **MongoDB**: Provide the collection name in the `sql` field.
|
|
159
|
+
|
|
160
|
+
**Script-Based Refresh (for Python-aggregated charts):**
|
|
161
|
+
```json
|
|
162
|
+
"query": { "script": "analysis/my_dashboard_script.py" }
|
|
163
|
+
```
|
|
164
|
+
Use `script` for Shopify/API charts that need aggregation. CLI re-runs the script on Refresh.
|
|
137
165
|
|
|
138
166
|
---
|
|
139
167
|
|
|
@@ -213,6 +213,43 @@ FROM table;
|
|
|
213
213
|
json.dump(dashboard, f, indent=2)
|
|
214
214
|
```
|
|
215
215
|
|
|
216
|
+
### 5b. Making Charts Refreshable (Recommended)
|
|
217
|
+
|
|
218
|
+
To allow the user to refresh data directly from the dashboard:
|
|
219
|
+
1. Include a `query` object in the chart definition.
|
|
220
|
+
2. Run `npx crushdataai connections` to list available connection names (this is secure - no passwords shown).
|
|
221
|
+
3. Set `connection` to one of the listed names.
|
|
222
|
+
4. Set `sql` to the query used to generate the data.
|
|
223
|
+
|
|
224
|
+
> **SECURITY**: Never read `.env` directly to find connection names. Always use `npx crushdataai connections`.
|
|
225
|
+
|
|
226
|
+
```json
|
|
227
|
+
"query": {
|
|
228
|
+
"connection": "my_postgres_db",
|
|
229
|
+
"sql": "SELECT date, revenue FROM sales WHERE..."
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**Database Specifics:**
|
|
234
|
+
- **SQL/Databases**: Provide the full SQL query.
|
|
235
|
+
- **Shopify**: Provide the resource name (e.g. `orders`).
|
|
236
|
+
- **CSV**: Provide the connection name. `sql` is ignored but required (set to "default").
|
|
237
|
+
- **MongoDB**: Provide the collection name in the `sql` field.
|
|
238
|
+
|
|
239
|
+
**Script-Based Refresh (for Python-aggregated charts):**
|
|
240
|
+
|
|
241
|
+
If your chart requires Python aggregation (e.g., grouping, custom calculations), use `script` instead of `connection`:
|
|
242
|
+
|
|
243
|
+
```json
|
|
244
|
+
"query": {
|
|
245
|
+
"script": "analysis/my_dashboard_script.py"
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
When the user clicks Refresh, the CLI will **re-run your Python script**. The script should update the dashboard JSON file with fresh aggregated data.
|
|
250
|
+
|
|
251
|
+
> **TIP**: Use `script` for Shopify/API charts that need aggregation. Use `connection` + `sql` only for SQL databases where the query returns pre-formatted chart data.
|
|
252
|
+
|
|
216
253
|
3. **Tell user:**
|
|
217
254
|
> "Dashboard ready! Run `npx crushdataai dashboard` to view."
|
|
218
255
|
|
|
@@ -75,10 +75,38 @@ dashboard = {
|
|
|
75
75
|
"charts": [{"id": "chart-1", "type": "line", "title": "Trend", "data": {"labels": ["Jan","Feb"], "datasets": [{"label": "Revenue", "values": [10000,20000]}]}}]
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
with open("reports/dashboards/dashboard.json", "w") as f:
|
|
78
|
+
with open("reports/dashboards/dashboard.json", "w") as f:
|
|
79
79
|
json.dump(dashboard, f, indent=2)
|
|
80
80
|
```
|
|
81
|
-
|
|
81
|
+
|
|
82
|
+
### 5b. Making Charts Refreshable (Recommended)
|
|
83
|
+
|
|
84
|
+
To allow the user to refresh data directly from the dashboard:
|
|
85
|
+
1. Include a `query` object in the chart definition.
|
|
86
|
+
2. Run `npx crushdataai connections` to list available connection names (secure - no passwords shown).
|
|
87
|
+
3. Set `connection` to one of the listed names.
|
|
88
|
+
4. Set `sql` to the query used to generate the data.
|
|
89
|
+
|
|
90
|
+
> **SECURITY**: Never read `.env` directly to find connection names. Always use `npx crushdataai connections`.
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
"query": {
|
|
94
|
+
"connection": "my_postgres_db",
|
|
95
|
+
"sql": "SELECT date, revenue FROM sales WHERE..."
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Database Specifics:**
|
|
100
|
+
- **SQL/Databases**: Provide the full SQL query.
|
|
101
|
+
- **Shopify**: Provide the resource name (e.g. `orders`).
|
|
102
|
+
- **CSV**: Provide the connection name. `sql` is ignored but required (set to "default").
|
|
103
|
+
- **MongoDB**: Provide the collection name in the `sql` field.
|
|
104
|
+
|
|
105
|
+
**Script-Based Refresh (for Python-aggregated charts):**
|
|
106
|
+
```json
|
|
107
|
+
"query": { "script": "analysis/my_dashboard_script.py" }
|
|
108
|
+
```
|
|
109
|
+
Use `script` for Shopify/API charts that need aggregation. CLI re-runs the script on Refresh.
|
|
82
110
|
|
|
83
111
|
---
|
|
84
112
|
|
|
@@ -72,7 +72,35 @@ dashboard = {
|
|
|
72
72
|
"kpis": [{"id": "kpi-1", "label": "Total", "value": "$50K", "trend": "+12%"}],
|
|
73
73
|
"charts": [{"id": "chart-1", "type": "line", "title": "Trend", "data": {"labels": ["Jan","Feb"], "datasets": [{"label": "Revenue", "values": [10000,20000]}]}}]
|
|
74
74
|
}
|
|
75
|
-
with open("reports/dashboards/dashboard.json", "w") as f:
|
|
75
|
+
with open("reports/dashboards/dashboard.json", "w") as f:
|
|
76
76
|
json.dump(dashboard, f, indent=2)
|
|
77
77
|
```
|
|
78
|
-
|
|
78
|
+
|
|
79
|
+
### 5b. Making Charts Refreshable (Recommended)
|
|
80
|
+
|
|
81
|
+
To allow the user to refresh data directly from the dashboard:
|
|
82
|
+
1. Include a `query` object in the chart definition.
|
|
83
|
+
2. Run `npx crushdataai connections` to list available connection names (secure - no passwords shown).
|
|
84
|
+
3. Set `connection` to one of the listed names.
|
|
85
|
+
4. Set `sql` to the query used to generate the data.
|
|
86
|
+
|
|
87
|
+
> **SECURITY**: Never read `.env` directly to find connection names. Always use `npx crushdataai connections`.
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
"query": {
|
|
91
|
+
"connection": "my_postgres_db",
|
|
92
|
+
"sql": "SELECT date, revenue FROM sales WHERE..."
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Database Specifics:**
|
|
97
|
+
- **SQL/Databases**: Provide the full SQL query.
|
|
98
|
+
- **Shopify**: Provide the resource name (e.g. `orders`).
|
|
99
|
+
- **CSV**: Provide the connection name. `sql` is ignored but required (set to "default").
|
|
100
|
+
- **MongoDB**: Provide the collection name in the `sql` field.
|
|
101
|
+
|
|
102
|
+
**Script-Based Refresh (for Python-aggregated charts):**
|
|
103
|
+
```json
|
|
104
|
+
"query": { "script": "analysis/my_dashboard_script.py" }
|
|
105
|
+
```
|
|
106
|
+
Use `script` for Shopify/API charts that need aggregation. CLI re-runs the script on Refresh.
|
|
@@ -74,7 +74,35 @@ dashboard = {
|
|
|
74
74
|
"kpis": [{"id": "kpi-1", "label": "Total", "value": "$50K", "trend": "+12%"}],
|
|
75
75
|
"charts": [{"id": "chart-1", "type": "line", "title": "Trend", "data": {"labels": ["Jan","Feb"], "datasets": [{"label": "Revenue", "values": [10000,20000]}]}}]
|
|
76
76
|
}
|
|
77
|
-
with open("reports/dashboards/dashboard.json", "w") as f:
|
|
77
|
+
with open("reports/dashboards/dashboard.json", "w") as f:
|
|
78
78
|
json.dump(dashboard, f, indent=2)
|
|
79
79
|
```
|
|
80
|
-
|
|
80
|
+
|
|
81
|
+
### 5b. Making Charts Refreshable (Recommended)
|
|
82
|
+
|
|
83
|
+
To allow the user to refresh data directly from the dashboard:
|
|
84
|
+
1. Include a `query` object in the chart definition.
|
|
85
|
+
2. Run `npx crushdataai connections` to list available connection names (secure - no passwords shown).
|
|
86
|
+
3. Set `connection` to one of the listed names.
|
|
87
|
+
4. Set `sql` to the query used to generate the data.
|
|
88
|
+
|
|
89
|
+
> **SECURITY**: Never read `.env` directly to find connection names. Always use `npx crushdataai connections`.
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
"query": {
|
|
93
|
+
"connection": "my_postgres_db",
|
|
94
|
+
"sql": "SELECT date, revenue FROM sales WHERE..."
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Database Specifics:**
|
|
99
|
+
- **SQL/Databases**: Provide the full SQL query.
|
|
100
|
+
- **Shopify**: Provide the resource name (e.g. `orders`).
|
|
101
|
+
- **CSV**: Provide the connection name. `sql` is ignored but required (set to "default").
|
|
102
|
+
- **MongoDB**: Provide the collection name in the `sql` field.
|
|
103
|
+
|
|
104
|
+
**Script-Based Refresh (for Python-aggregated charts):**
|
|
105
|
+
```json
|
|
106
|
+
"query": { "script": "analysis/my_dashboard_script.py" }
|
|
107
|
+
```
|
|
108
|
+
Use `script` for Shopify/API charts that need aggregation. CLI re-runs the script on Refresh.
|
|
@@ -72,7 +72,35 @@ dashboard = {
|
|
|
72
72
|
"kpis": [{"id": "kpi-1", "label": "Total", "value": "$50K", "trend": "+12%"}],
|
|
73
73
|
"charts": [{"id": "chart-1", "type": "line", "title": "Trend", "data": {"labels": ["Jan","Feb"], "datasets": [{"label": "Revenue", "values": [10000,20000]}]}}]
|
|
74
74
|
}
|
|
75
|
-
with open("reports/dashboards/dashboard.json", "w") as f:
|
|
75
|
+
with open("reports/dashboards/dashboard.json", "w") as f:
|
|
76
76
|
json.dump(dashboard, f, indent=2)
|
|
77
77
|
```
|
|
78
|
-
|
|
78
|
+
|
|
79
|
+
### 5b. Making Charts Refreshable (Recommended)
|
|
80
|
+
|
|
81
|
+
To allow the user to refresh data directly from the dashboard:
|
|
82
|
+
1. Include a `query` object in the chart definition.
|
|
83
|
+
2. Run `npx crushdataai connections` to list available connection names (secure - no passwords shown).
|
|
84
|
+
3. Set `connection` to one of the listed names.
|
|
85
|
+
4. Set `sql` to the query used to generate the data.
|
|
86
|
+
|
|
87
|
+
> **SECURITY**: Never read `.env` directly to find connection names. Always use `npx crushdataai connections`.
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
"query": {
|
|
91
|
+
"connection": "my_postgres_db",
|
|
92
|
+
"sql": "SELECT date, revenue FROM sales WHERE..."
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Database Specifics:**
|
|
97
|
+
- **SQL/Databases**: Provide the full SQL query.
|
|
98
|
+
- **Shopify**: Provide the resource name (e.g. `orders`).
|
|
99
|
+
- **CSV**: Provide the connection name. `sql` is ignored but required (set to "default").
|
|
100
|
+
- **MongoDB**: Provide the collection name in the `sql` field.
|
|
101
|
+
|
|
102
|
+
**Script-Based Refresh (for Python-aggregated charts):**
|
|
103
|
+
```json
|
|
104
|
+
"query": { "script": "analysis/my_dashboard_script.py" }
|
|
105
|
+
```
|
|
106
|
+
Use `script` for Shopify/API charts that need aggregation. CLI re-runs the script on Refresh.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Connector, Table, TableData } from '../index';
|
|
2
|
+
import { Connection } from '../../connections';
|
|
3
|
+
export declare class ClickHouseConnector implements Connector {
|
|
4
|
+
type: string;
|
|
5
|
+
private createClient;
|
|
6
|
+
test(connection: Connection): Promise<boolean>;
|
|
7
|
+
getTables(connection: Connection): Promise<Table[]>;
|
|
8
|
+
getData(connection: Connection, tableName: string, page: number, limit: number): Promise<TableData>;
|
|
9
|
+
getSchema(connection: Connection, tableName: string): Promise<import('../index').ColumnInfo[]>;
|
|
10
|
+
getSnippet(connection: Connection, lang: string): string;
|
|
11
|
+
executeQuery(connection: Connection, query: string): Promise<any[]>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ClickHouseConnector = void 0;
|
|
4
|
+
const client_1 = require("@clickhouse/client");
|
|
5
|
+
class ClickHouseConnector {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.type = 'clickhouse';
|
|
8
|
+
}
|
|
9
|
+
createClient(connection) {
|
|
10
|
+
let url = connection.host || 'http://localhost:8123';
|
|
11
|
+
if (!url.startsWith('http')) {
|
|
12
|
+
url = `http://${url}`;
|
|
13
|
+
}
|
|
14
|
+
return (0, client_1.createClient)({
|
|
15
|
+
url,
|
|
16
|
+
username: connection.user || 'default',
|
|
17
|
+
password: connection.password || '',
|
|
18
|
+
database: connection.database || 'default',
|
|
19
|
+
request_timeout: 10000,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
async test(connection) {
|
|
23
|
+
console.log(`[ClickHouse] Testing connection for ${connection.name}`);
|
|
24
|
+
try {
|
|
25
|
+
const client = this.createClient(connection);
|
|
26
|
+
await client.query({ query: 'SELECT 1' });
|
|
27
|
+
console.log(`[ClickHouse] Connection test successful`);
|
|
28
|
+
await client.close();
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.error(`[ClickHouse] Connection test failed:`, error.message);
|
|
33
|
+
throw new Error(`ClickHouse connection failed: ${error.message}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async getTables(connection) {
|
|
37
|
+
console.log(`[ClickHouse] getTables called for ${connection.name}`);
|
|
38
|
+
const client = this.createClient(connection);
|
|
39
|
+
try {
|
|
40
|
+
const resultSet = await client.query({
|
|
41
|
+
query: `
|
|
42
|
+
SELECT name, 'table' as type
|
|
43
|
+
FROM system.tables
|
|
44
|
+
WHERE database = '${connection.database || 'default'}'
|
|
45
|
+
`,
|
|
46
|
+
format: 'JSONEachRow'
|
|
47
|
+
});
|
|
48
|
+
const rows = await resultSet.json();
|
|
49
|
+
return rows.map((row) => ({
|
|
50
|
+
name: row.name,
|
|
51
|
+
type: 'table',
|
|
52
|
+
rowCount: null
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
throw new Error(`Failed to fetch tables: ${error.message}`);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
await client.close();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async getData(connection, tableName, page, limit) {
|
|
63
|
+
console.log(`[ClickHouse] getData called for ${connection.name}`);
|
|
64
|
+
const client = this.createClient(connection);
|
|
65
|
+
try {
|
|
66
|
+
const offset = (page - 1) * limit;
|
|
67
|
+
// Get total count
|
|
68
|
+
const countResult = await client.query({
|
|
69
|
+
query: `SELECT count() as total FROM "${tableName}"`,
|
|
70
|
+
format: 'JSONEachRow'
|
|
71
|
+
});
|
|
72
|
+
const countRows = await countResult.json();
|
|
73
|
+
const totalRows = parseInt(countRows[0]?.total || '0', 10);
|
|
74
|
+
// Get data
|
|
75
|
+
const resultSet = await client.query({
|
|
76
|
+
query: `SELECT * FROM "${tableName}" LIMIT ${limit} OFFSET ${offset}`,
|
|
77
|
+
format: 'JSONEachRow'
|
|
78
|
+
});
|
|
79
|
+
const rows = await resultSet.json();
|
|
80
|
+
const columns = rows.length > 0 ? Object.keys(rows[0]) : [];
|
|
81
|
+
const totalPages = Math.ceil(totalRows / limit) || 1;
|
|
82
|
+
return {
|
|
83
|
+
columns,
|
|
84
|
+
rows,
|
|
85
|
+
pagination: {
|
|
86
|
+
page, limit, totalRows, totalPages,
|
|
87
|
+
startIdx: offset + 1, endIdx: offset + rows.length
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
throw new Error(`Failed to fetch data: ${error.message}`);
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
await client.close();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async getSchema(connection, tableName) {
|
|
99
|
+
const client = this.createClient(connection);
|
|
100
|
+
try {
|
|
101
|
+
const resultSet = await client.query({
|
|
102
|
+
query: `DESCRIBE "${tableName}"`,
|
|
103
|
+
format: 'JSONEachRow'
|
|
104
|
+
});
|
|
105
|
+
const rows = await resultSet.json();
|
|
106
|
+
return rows.map((row) => ({
|
|
107
|
+
name: row.name,
|
|
108
|
+
type: row.type,
|
|
109
|
+
nullable: false // ClickHouse nullable is explicit type wrapper usually
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
throw new Error(`Failed to fetch schema: ${error.message}`);
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
await client.close();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
getSnippet(connection, lang) {
|
|
120
|
+
// ... ClickHouse python snippet ...
|
|
121
|
+
return '';
|
|
122
|
+
}
|
|
123
|
+
async executeQuery(connection, query) {
|
|
124
|
+
const client = this.createClient(connection);
|
|
125
|
+
try {
|
|
126
|
+
const resultSet = await client.query({
|
|
127
|
+
query: query,
|
|
128
|
+
format: 'JSONEachRow'
|
|
129
|
+
});
|
|
130
|
+
return await resultSet.json();
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
throw new Error(`Failed to execute query: ${error.message}`);
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
await client.close();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
exports.ClickHouseConnector = ClickHouseConnector;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Connector, Table, TableData } from '../index';
|
|
2
|
+
import { Connection } from '../../connections';
|
|
3
|
+
export declare class DatabricksConnector implements Connector {
|
|
4
|
+
type: string;
|
|
5
|
+
private createSession;
|
|
6
|
+
test(connection: Connection): Promise<boolean>;
|
|
7
|
+
getTables(connection: Connection): Promise<Table[]>;
|
|
8
|
+
getData(connection: Connection, tableName: string, page: number, limit: number): Promise<TableData>;
|
|
9
|
+
getSchema(connection: Connection, tableName: string): Promise<import('../index').ColumnInfo[]>;
|
|
10
|
+
getSnippet(connection: Connection, lang: string): string;
|
|
11
|
+
executeQuery(connection: Connection, query: string): Promise<any[]>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DatabricksConnector = void 0;
|
|
4
|
+
const sql_1 = require("@databricks/sql");
|
|
5
|
+
class DatabricksConnector {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.type = 'databricks';
|
|
8
|
+
}
|
|
9
|
+
async createSession(connection) {
|
|
10
|
+
const client = new sql_1.DBSQLClient();
|
|
11
|
+
const token = connection.apiKey || connection.password;
|
|
12
|
+
const host = connection.host;
|
|
13
|
+
const path = connection.connectionString; // mapped from HTTP Path
|
|
14
|
+
if (!token || !host || !path) {
|
|
15
|
+
throw new Error('Host, Token (API Key), and HTTP Path (Connection String) are required for Databricks');
|
|
16
|
+
}
|
|
17
|
+
await client.connect({ token, host, path });
|
|
18
|
+
const session = await client.openSession();
|
|
19
|
+
return { client, session };
|
|
20
|
+
}
|
|
21
|
+
async test(connection) {
|
|
22
|
+
console.log(`[Databricks] Testing connection for ${connection.name}`);
|
|
23
|
+
try {
|
|
24
|
+
const { client, session } = await this.createSession(connection);
|
|
25
|
+
const op = await session.executeStatement('SELECT 1');
|
|
26
|
+
await op.close();
|
|
27
|
+
await session.close();
|
|
28
|
+
await client.close();
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
throw new Error(`Databricks connection failed: ${error.message}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async getTables(connection) {
|
|
36
|
+
console.log(`[Databricks] getTables called for ${connection.name}`);
|
|
37
|
+
try {
|
|
38
|
+
const { client, session } = await this.createSession(connection);
|
|
39
|
+
const op = await session.executeStatement('SHOW TABLES');
|
|
40
|
+
const rows = await op.fetchAll();
|
|
41
|
+
await op.close();
|
|
42
|
+
await session.close();
|
|
43
|
+
await client.close();
|
|
44
|
+
return rows.map((row) => ({
|
|
45
|
+
name: row.tableName || row.name,
|
|
46
|
+
type: 'table',
|
|
47
|
+
rowCount: null
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
throw new Error(`Failed to fetch tables: ${error.message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async getData(connection, tableName, page, limit) {
|
|
55
|
+
console.log(`[Databricks] getData called for ${connection.name}`);
|
|
56
|
+
try {
|
|
57
|
+
const { client, session } = await this.createSession(connection);
|
|
58
|
+
// Count
|
|
59
|
+
const countOp = await session.executeStatement(`SELECT COUNT(*) as total FROM ${tableName}`);
|
|
60
|
+
const countRows = await countOp.fetchAll();
|
|
61
|
+
await countOp.close();
|
|
62
|
+
const totalRows = parseInt(countRows[0]?.total || '0', 10);
|
|
63
|
+
// Data
|
|
64
|
+
const dataOp = await session.executeStatement(`SELECT * FROM ${tableName} LIMIT ${limit}`);
|
|
65
|
+
const rows = await dataOp.fetchAll();
|
|
66
|
+
await dataOp.close();
|
|
67
|
+
await session.close();
|
|
68
|
+
await client.close();
|
|
69
|
+
const columns = rows.length > 0 ? Object.keys(rows[0]) : [];
|
|
70
|
+
const totalPages = Math.ceil(totalRows / limit) || 1;
|
|
71
|
+
return {
|
|
72
|
+
columns,
|
|
73
|
+
rows,
|
|
74
|
+
pagination: {
|
|
75
|
+
page, limit, totalRows, totalPages,
|
|
76
|
+
startIdx: 1, endIdx: rows.length
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
throw new Error(`Failed to fetch data: ${error.message}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async getSchema(connection, tableName) {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
getSnippet(connection, lang) {
|
|
88
|
+
return '';
|
|
89
|
+
}
|
|
90
|
+
async executeQuery(connection, query) {
|
|
91
|
+
try {
|
|
92
|
+
const { client, session } = await this.createSession(connection);
|
|
93
|
+
const op = await session.executeStatement(query);
|
|
94
|
+
const rows = await op.fetchAll();
|
|
95
|
+
await op.close();
|
|
96
|
+
await session.close();
|
|
97
|
+
await client.close();
|
|
98
|
+
return rows;
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
throw new Error(`Failed to execute query: ${error.message}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
exports.DatabricksConnector = DatabricksConnector;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Connector, Table, TableData } from '../index';
|
|
2
|
+
import { Connection } from '../../connections';
|
|
3
|
+
export declare class MongoDBConnector implements Connector {
|
|
4
|
+
type: string;
|
|
5
|
+
private getUrl;
|
|
6
|
+
test(connection: Connection): Promise<boolean>;
|
|
7
|
+
getTables(connection: Connection): Promise<Table[]>;
|
|
8
|
+
getData(connection: Connection, tableName: string, page: number, limit: number): Promise<TableData>;
|
|
9
|
+
getSchema(connection: Connection, tableName: string): Promise<import('../index').ColumnInfo[]>;
|
|
10
|
+
getSnippet(connection: Connection, lang: string): string;
|
|
11
|
+
executeQuery(connection: Connection, query: string): Promise<any[]>;
|
|
12
|
+
}
|