@influxdata/influxdb3-mcp-server 1.3.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/CHANGELOG.md +194 -0
- package/Dockerfile +22 -0
- package/LICENSE +6 -0
- package/LICENSE-APACHE.txt +201 -0
- package/LICENSE-MIT.txt +25 -0
- package/README.md +318 -0
- package/build/config.js +85 -0
- package/build/helpers/enums/influx-product-types.enum.js +8 -0
- package/build/index.js +33 -0
- package/build/prompts/index.js +98 -0
- package/build/resources/index.js +223 -0
- package/build/server/index.js +104 -0
- package/build/services/base-connection.service.js +318 -0
- package/build/services/cloud-token-management.service.js +179 -0
- package/build/services/context-file.service.js +97 -0
- package/build/services/database-management.service.js +549 -0
- package/build/services/help.service.js +241 -0
- package/build/services/http-client.service.js +109 -0
- package/build/services/influxdb-master.service.js +117 -0
- package/build/services/query.service.js +499 -0
- package/build/services/serverless-schema-management.service.js +266 -0
- package/build/services/token-management.service.js +215 -0
- package/build/services/write.service.js +153 -0
- package/build/tools/categories/cloud-token.tools.js +321 -0
- package/build/tools/categories/database.tools.js +299 -0
- package/build/tools/categories/health.tools.js +75 -0
- package/build/tools/categories/help.tools.js +104 -0
- package/build/tools/categories/query.tools.js +180 -0
- package/build/tools/categories/schema.tools.js +308 -0
- package/build/tools/categories/token.tools.js +378 -0
- package/build/tools/categories/write.tools.js +104 -0
- package/build/tools/index.js +27 -0
- package/env.example +17 -0
- package/example-cloud-dedicated.mcp.json +15 -0
- package/example-cloud-serverless.mcp.json +13 -0
- package/example-clustered.mcp.json +14 -0
- package/example-docker.mcp.json +25 -0
- package/example-local.mcp.json +13 -0
- package/example-npx.mcp.json +13 -0
- package/package.json +78 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InfluxDB Query Service
|
|
3
|
+
*
|
|
4
|
+
* Handles query operations using InfluxDB v3 SQL API
|
|
5
|
+
*/
|
|
6
|
+
import { InfluxProductType } from "../helpers/enums/influx-product-types.enum.js";
|
|
7
|
+
export class QueryService {
|
|
8
|
+
baseService;
|
|
9
|
+
constructor(baseService) {
|
|
10
|
+
this.baseService = baseService;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Execute SQL query (single entrypoint for all product types)
|
|
14
|
+
* For core/enterprise: HTTP API
|
|
15
|
+
* For cloud-dedicated: influxdb3 client
|
|
16
|
+
* For clustered: HTTP API (/query)
|
|
17
|
+
*/
|
|
18
|
+
async executeQuery(query, database, options = {}) {
|
|
19
|
+
this.baseService.validateDataCapabilities();
|
|
20
|
+
const format = options.format ?? "json";
|
|
21
|
+
const connectionInfo = this.baseService.getConnectionInfo();
|
|
22
|
+
switch (connectionInfo.type) {
|
|
23
|
+
case InfluxProductType.CloudDedicated:
|
|
24
|
+
return this.executeCloudDedicatedQuery(query, database);
|
|
25
|
+
case InfluxProductType.Clustered:
|
|
26
|
+
return this.executeClusteredQuery(query, database);
|
|
27
|
+
case InfluxProductType.CloudServerless:
|
|
28
|
+
return this.executeCloudServerlessQuery(query, database);
|
|
29
|
+
case InfluxProductType.Core:
|
|
30
|
+
case InfluxProductType.Enterprise:
|
|
31
|
+
return this.executeCoreEnterpriseQuery(query, database, format);
|
|
32
|
+
default:
|
|
33
|
+
throw new Error(`Unsupported InfluxDB product type: ${connectionInfo.type}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Query for core/enterprise (HTTP API)
|
|
38
|
+
*/
|
|
39
|
+
async executeCoreEnterpriseQuery(query, database, format) {
|
|
40
|
+
try {
|
|
41
|
+
const httpClient = this.baseService.getInfluxHttpClient();
|
|
42
|
+
const payload = {
|
|
43
|
+
db: database,
|
|
44
|
+
q: query,
|
|
45
|
+
format: format,
|
|
46
|
+
};
|
|
47
|
+
let acceptHeader = "application/json";
|
|
48
|
+
switch (format) {
|
|
49
|
+
case "json":
|
|
50
|
+
acceptHeader = "application/json";
|
|
51
|
+
break;
|
|
52
|
+
case "csv":
|
|
53
|
+
acceptHeader = "text/csv";
|
|
54
|
+
break;
|
|
55
|
+
case "parquet":
|
|
56
|
+
acceptHeader = "application/vnd.apache.parquet";
|
|
57
|
+
break;
|
|
58
|
+
case "jsonl":
|
|
59
|
+
acceptHeader = "application/json";
|
|
60
|
+
break;
|
|
61
|
+
case "pretty":
|
|
62
|
+
acceptHeader = "application/json";
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
const response = await httpClient.post("/api/v3/query_sql", payload, {
|
|
66
|
+
headers: {
|
|
67
|
+
"Content-Type": "application/json",
|
|
68
|
+
Accept: acceptHeader,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
return response;
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
this.handleQueryError(error);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Query for cloud-dedicated/clustered (influxdb3 client)
|
|
79
|
+
*/
|
|
80
|
+
async executeCloudDedicatedQuery(query, database) {
|
|
81
|
+
try {
|
|
82
|
+
const client = this.baseService.getClient();
|
|
83
|
+
if (!client)
|
|
84
|
+
throw new Error("InfluxDB client not initialized");
|
|
85
|
+
const result = client.queryPoints(query, database, { type: "sql" });
|
|
86
|
+
const rows = [];
|
|
87
|
+
for await (const row of result) {
|
|
88
|
+
rows.push(row);
|
|
89
|
+
}
|
|
90
|
+
return rows;
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
this.handleQueryError(error);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Query for clustered (HTTP API)
|
|
98
|
+
*/
|
|
99
|
+
async executeClusteredQuery(query, database) {
|
|
100
|
+
try {
|
|
101
|
+
const httpClient = this.baseService.getInfluxHttpClient();
|
|
102
|
+
const response = await httpClient.get("/query", {
|
|
103
|
+
params: {
|
|
104
|
+
db: database,
|
|
105
|
+
q: query,
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
return response;
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
this.handleQueryError(error);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Query for cloud-serverless (influxdb3 client)
|
|
116
|
+
*/
|
|
117
|
+
async executeCloudServerlessQuery(query, database) {
|
|
118
|
+
try {
|
|
119
|
+
const client = this.baseService.getClient();
|
|
120
|
+
if (!client)
|
|
121
|
+
throw new Error("InfluxDB client not initialized");
|
|
122
|
+
const result = client.queryPoints(query, database, { type: "sql" });
|
|
123
|
+
const rows = [];
|
|
124
|
+
for await (const row of result) {
|
|
125
|
+
rows.push(row);
|
|
126
|
+
}
|
|
127
|
+
return rows;
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
this.handleQueryError(error);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Centralized error handler for query methods
|
|
135
|
+
*/
|
|
136
|
+
handleQueryError(error) {
|
|
137
|
+
const errorMessage = error.response?.data?.message ||
|
|
138
|
+
error.response?.data?.error ||
|
|
139
|
+
(typeof error.response?.data === "string" ? error.response.data : null) ||
|
|
140
|
+
error.response?.statusText ||
|
|
141
|
+
error.message;
|
|
142
|
+
const statusCode = error.response?.status;
|
|
143
|
+
console.error(`Status: ${statusCode} \n Message: ${errorMessage}`);
|
|
144
|
+
switch (statusCode) {
|
|
145
|
+
case 400:
|
|
146
|
+
throw new Error(`Bad request: ${errorMessage}`);
|
|
147
|
+
case 401:
|
|
148
|
+
throw new Error(`Unauthorized: ${errorMessage}`);
|
|
149
|
+
case 403:
|
|
150
|
+
throw new Error(`Access denied: ${errorMessage}`);
|
|
151
|
+
case 404:
|
|
152
|
+
throw new Error(`Database not found: ${errorMessage}`);
|
|
153
|
+
case 405:
|
|
154
|
+
throw new Error(`Method not allowed: ${errorMessage}`);
|
|
155
|
+
case 422:
|
|
156
|
+
throw new Error(`Unprocessable entity: ${errorMessage}`);
|
|
157
|
+
default:
|
|
158
|
+
throw new Error(`Query failed: ${errorMessage}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Get all measurements/tables in a database
|
|
163
|
+
* Uses SHOW MEASUREMENTS for cloud-dedicated/clustered (HTTP), information_schema for others
|
|
164
|
+
*/
|
|
165
|
+
async getMeasurements(database) {
|
|
166
|
+
this.baseService.validateDataCapabilities();
|
|
167
|
+
const connectionInfo = this.baseService.getConnectionInfo();
|
|
168
|
+
switch (connectionInfo.type) {
|
|
169
|
+
case InfluxProductType.CloudDedicated:
|
|
170
|
+
return this.getMeasurementsCloudDedicated(database);
|
|
171
|
+
case InfluxProductType.Clustered:
|
|
172
|
+
return this.getMeasurementsClustered(database);
|
|
173
|
+
case InfluxProductType.CloudServerless:
|
|
174
|
+
return this.getMeasurementsCloudServerless(database);
|
|
175
|
+
case InfluxProductType.Core:
|
|
176
|
+
case InfluxProductType.Enterprise:
|
|
177
|
+
return this.getMeasurementsCoreEnterprise(database);
|
|
178
|
+
default:
|
|
179
|
+
throw new Error(`Unsupported InfluxDB product type: ${connectionInfo.type}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Get measurements for cloud-dedicated/clustered (HTTP client with InfluxQL)
|
|
184
|
+
*/
|
|
185
|
+
async getMeasurementsCloudDedicated(database) {
|
|
186
|
+
try {
|
|
187
|
+
const httpClient = this.baseService.getInfluxHttpClient();
|
|
188
|
+
const response = await httpClient.get("/query", {
|
|
189
|
+
params: {
|
|
190
|
+
db: database,
|
|
191
|
+
q: "SHOW MEASUREMENTS",
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
if (response.results &&
|
|
195
|
+
response.results[0] &&
|
|
196
|
+
response.results[0].series) {
|
|
197
|
+
const series = response.results[0].series[0];
|
|
198
|
+
if (series.name === "measurements" && series.values) {
|
|
199
|
+
return series.values.map((value) => ({ name: value[0] }));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return [];
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
throw new Error(`Failed to get measurements: ${error.message}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Get measurements for clustered (HTTP client with InfluxQL)
|
|
210
|
+
*/
|
|
211
|
+
async getMeasurementsClustered(database) {
|
|
212
|
+
try {
|
|
213
|
+
const httpClient = this.baseService.getInfluxHttpClient();
|
|
214
|
+
const response = await httpClient.get("/query", {
|
|
215
|
+
params: {
|
|
216
|
+
db: database,
|
|
217
|
+
q: "SHOW MEASUREMENTS",
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
if (response.results &&
|
|
221
|
+
response.results[0] &&
|
|
222
|
+
response.results[0].series) {
|
|
223
|
+
const series = response.results[0].series[0];
|
|
224
|
+
if (series.name === "measurements" && series.values) {
|
|
225
|
+
return series.values.map((value) => ({ name: value[0] }));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return [];
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
throw new Error(`Failed to get measurements: ${error.message}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Get measurements for core/enterprise
|
|
236
|
+
*/
|
|
237
|
+
async getMeasurementsCoreEnterprise(database) {
|
|
238
|
+
try {
|
|
239
|
+
const query = "SELECT DISTINCT table_name FROM information_schema.columns WHERE table_schema = 'iox'";
|
|
240
|
+
const result = await this.executeQuery(query, database, {
|
|
241
|
+
format: "json",
|
|
242
|
+
});
|
|
243
|
+
if (Array.isArray(result)) {
|
|
244
|
+
return result.map((row) => ({ name: row.table_name }));
|
|
245
|
+
}
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
throw new Error(`Failed to get measurements: ${error.message}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Get measurements for cloud-serverless
|
|
254
|
+
* Parses the Cloud Serverless response format with _fields arrays
|
|
255
|
+
*/
|
|
256
|
+
async getMeasurementsCloudServerless(database) {
|
|
257
|
+
try {
|
|
258
|
+
const query = "SELECT DISTINCT table_name FROM information_schema.columns WHERE table_schema = 'iox'";
|
|
259
|
+
const result = await this.executeQuery(query, database, {
|
|
260
|
+
format: "json",
|
|
261
|
+
});
|
|
262
|
+
if (Array.isArray(result)) {
|
|
263
|
+
return result
|
|
264
|
+
.map((row) => {
|
|
265
|
+
// Cloud Serverless format: { "_fields": { "table_name": ["string", "actual_value"] } }
|
|
266
|
+
const tableName = row._fields?.table_name?.[1];
|
|
267
|
+
return { name: tableName };
|
|
268
|
+
})
|
|
269
|
+
.filter((item) => item.name); // Filter out undefined names
|
|
270
|
+
}
|
|
271
|
+
return [];
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
throw new Error(`Failed to get measurements: ${error.message}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Get schema information for a measurement/table
|
|
279
|
+
* Uses SHOW FIELD KEYS + SHOW TAG KEYS for cloud-dedicated/clustered (HTTP), information_schema for others
|
|
280
|
+
*/
|
|
281
|
+
async getMeasurementSchema(measurement, database) {
|
|
282
|
+
this.baseService.validateDataCapabilities();
|
|
283
|
+
const connectionInfo = this.baseService.getConnectionInfo();
|
|
284
|
+
switch (connectionInfo.type) {
|
|
285
|
+
case InfluxProductType.CloudDedicated:
|
|
286
|
+
return this.getMeasurementSchemaCloudDedicated(measurement, database);
|
|
287
|
+
case InfluxProductType.Clustered:
|
|
288
|
+
return this.getMeasurementSchemaClustered(measurement, database);
|
|
289
|
+
case InfluxProductType.CloudServerless:
|
|
290
|
+
return this.getMeasurementSchemaCloudServerless(measurement, database);
|
|
291
|
+
case InfluxProductType.Core:
|
|
292
|
+
case InfluxProductType.Enterprise:
|
|
293
|
+
return this.getMeasurementSchemaCoreEnterprise(measurement, database);
|
|
294
|
+
default:
|
|
295
|
+
throw new Error(`Unsupported InfluxDB product type: ${connectionInfo.type}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Get measurement schema for cloud-dedicated/clustered (HTTP client with InfluxQL)
|
|
300
|
+
*/
|
|
301
|
+
async getMeasurementSchemaCloudDedicated(measurement, database) {
|
|
302
|
+
try {
|
|
303
|
+
const httpClient = this.baseService.getInfluxHttpClient();
|
|
304
|
+
const fieldKeysResponse = await httpClient.get("/query", {
|
|
305
|
+
params: {
|
|
306
|
+
db: database,
|
|
307
|
+
q: `SHOW FIELD KEYS FROM ${measurement}`,
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
const tagKeysResponse = await httpClient.get("/query", {
|
|
311
|
+
params: {
|
|
312
|
+
db: database,
|
|
313
|
+
q: `SHOW TAG KEYS FROM ${measurement}`,
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
const columns = [];
|
|
317
|
+
if (fieldKeysResponse.results &&
|
|
318
|
+
fieldKeysResponse.results[0] &&
|
|
319
|
+
fieldKeysResponse.results[0].series) {
|
|
320
|
+
const fieldSeries = fieldKeysResponse.results[0].series[0];
|
|
321
|
+
if (fieldSeries && fieldSeries.values) {
|
|
322
|
+
fieldSeries.values.forEach((value) => {
|
|
323
|
+
columns.push({
|
|
324
|
+
name: value[0],
|
|
325
|
+
type: value[1],
|
|
326
|
+
category: "field",
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (tagKeysResponse.results &&
|
|
332
|
+
tagKeysResponse.results[0] &&
|
|
333
|
+
tagKeysResponse.results[0].series) {
|
|
334
|
+
const tagSeries = tagKeysResponse.results[0].series[0];
|
|
335
|
+
if (tagSeries && tagSeries.values) {
|
|
336
|
+
tagSeries.values.forEach((value) => {
|
|
337
|
+
columns.push({
|
|
338
|
+
name: value[0],
|
|
339
|
+
type: "string",
|
|
340
|
+
category: "tag",
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
columns.unshift({
|
|
346
|
+
name: "time",
|
|
347
|
+
type: "timestamp",
|
|
348
|
+
category: "time",
|
|
349
|
+
});
|
|
350
|
+
return { columns };
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
if (error.response?.status === 404 ||
|
|
354
|
+
error.message.includes("not found")) {
|
|
355
|
+
throw new Error(`Measurement '${measurement}' does not exist in database '${database}'`);
|
|
356
|
+
}
|
|
357
|
+
throw new Error(`Failed to get schema for measurement '${measurement}': ${error.message}`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Get measurement schema for clustered (HTTP client with InfluxQL)
|
|
362
|
+
*/
|
|
363
|
+
async getMeasurementSchemaClustered(measurement, database) {
|
|
364
|
+
try {
|
|
365
|
+
const httpClient = this.baseService.getInfluxHttpClient();
|
|
366
|
+
const fieldKeysResponse = await httpClient.get("/query", {
|
|
367
|
+
params: {
|
|
368
|
+
db: database,
|
|
369
|
+
q: `SHOW FIELD KEYS FROM ${measurement}`,
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
const tagKeysResponse = await httpClient.get("/query", {
|
|
373
|
+
params: {
|
|
374
|
+
db: database,
|
|
375
|
+
q: `SHOW TAG KEYS FROM ${measurement}`,
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
const columns = [];
|
|
379
|
+
if (fieldKeysResponse.results &&
|
|
380
|
+
fieldKeysResponse.results[0] &&
|
|
381
|
+
fieldKeysResponse.results[0].series) {
|
|
382
|
+
const fieldSeries = fieldKeysResponse.results[0].series[0];
|
|
383
|
+
if (fieldSeries && fieldSeries.values) {
|
|
384
|
+
fieldSeries.values.forEach((value) => {
|
|
385
|
+
columns.push({
|
|
386
|
+
name: value[0],
|
|
387
|
+
type: value[1],
|
|
388
|
+
category: "field",
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (tagKeysResponse.results &&
|
|
394
|
+
tagKeysResponse.results[0] &&
|
|
395
|
+
tagKeysResponse.results[0].series) {
|
|
396
|
+
const tagSeries = tagKeysResponse.results[0].series[0];
|
|
397
|
+
if (tagSeries && tagSeries.values) {
|
|
398
|
+
tagSeries.values.forEach((value) => {
|
|
399
|
+
columns.push({
|
|
400
|
+
name: value[0],
|
|
401
|
+
type: "string",
|
|
402
|
+
category: "tag",
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
columns.unshift({
|
|
408
|
+
name: "time",
|
|
409
|
+
type: "timestamp",
|
|
410
|
+
category: "time",
|
|
411
|
+
});
|
|
412
|
+
return { columns };
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
if (error.response?.status === 404 ||
|
|
416
|
+
error.message.includes("not found")) {
|
|
417
|
+
throw new Error(`Measurement '${measurement}' does not exist in database '${database}'`);
|
|
418
|
+
}
|
|
419
|
+
throw new Error(`Failed to get schema for measurement '${measurement}': ${error.message}`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Get measurement schema for core/enterprise
|
|
424
|
+
*/
|
|
425
|
+
async getMeasurementSchemaCoreEnterprise(measurement, database) {
|
|
426
|
+
try {
|
|
427
|
+
const query = `SELECT column_name, data_type FROM information_schema.columns WHERE table_name = '${measurement}' AND table_schema = 'iox'`;
|
|
428
|
+
const result = await this.executeQuery(query, database, {
|
|
429
|
+
format: "json",
|
|
430
|
+
});
|
|
431
|
+
if (Array.isArray(result)) {
|
|
432
|
+
const columns = result.map((row) => {
|
|
433
|
+
let category = "field";
|
|
434
|
+
if (row.column_name === "time") {
|
|
435
|
+
category = "time";
|
|
436
|
+
}
|
|
437
|
+
else if (row.data_type === "string" || row.data_type === "text") {
|
|
438
|
+
category = "tag";
|
|
439
|
+
}
|
|
440
|
+
return {
|
|
441
|
+
name: row.column_name,
|
|
442
|
+
type: row.data_type,
|
|
443
|
+
category,
|
|
444
|
+
};
|
|
445
|
+
});
|
|
446
|
+
return { columns };
|
|
447
|
+
}
|
|
448
|
+
return result;
|
|
449
|
+
}
|
|
450
|
+
catch (error) {
|
|
451
|
+
if (error.message.includes("not found")) {
|
|
452
|
+
throw new Error(`Table '${measurement}' does not exist in database '${database}'`);
|
|
453
|
+
}
|
|
454
|
+
throw new Error(`Failed to get schema for measurement '${measurement}': ${error.message}`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Get measurement schema for cloud-serverless
|
|
459
|
+
* Parses the Cloud Serverless response format with _fields arrays
|
|
460
|
+
*/
|
|
461
|
+
async getMeasurementSchemaCloudServerless(measurement, database) {
|
|
462
|
+
try {
|
|
463
|
+
const query = `SELECT column_name, data_type FROM information_schema.columns WHERE table_name = '${measurement}' AND table_schema = 'iox'`;
|
|
464
|
+
const result = await this.executeQuery(query, database, {
|
|
465
|
+
format: "json",
|
|
466
|
+
});
|
|
467
|
+
if (Array.isArray(result)) {
|
|
468
|
+
const columns = result
|
|
469
|
+
.map((row) => {
|
|
470
|
+
const columnName = row._fields?.column_name?.[1];
|
|
471
|
+
const dataType = row._fields?.data_type?.[1];
|
|
472
|
+
let category = "field";
|
|
473
|
+
if (columnName === "time") {
|
|
474
|
+
category = "time";
|
|
475
|
+
}
|
|
476
|
+
else if (dataType?.includes("Dictionary") ||
|
|
477
|
+
dataType === "string" ||
|
|
478
|
+
dataType === "text") {
|
|
479
|
+
category = "tag";
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
name: columnName,
|
|
483
|
+
type: dataType,
|
|
484
|
+
category,
|
|
485
|
+
};
|
|
486
|
+
})
|
|
487
|
+
.filter((col) => col.name);
|
|
488
|
+
return { columns };
|
|
489
|
+
}
|
|
490
|
+
return { columns: [] };
|
|
491
|
+
}
|
|
492
|
+
catch (error) {
|
|
493
|
+
if (error.message.includes("not found")) {
|
|
494
|
+
throw new Error(`Table '${measurement}' does not exist in database '${database}'`);
|
|
495
|
+
}
|
|
496
|
+
throw new Error(`Failed to get schema for measurement '${measurement}': ${error.message}`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|