|
16 | 16 | * limitations under the License. |
17 | 17 | */ |
18 | 18 | import apiClient from './client'; |
19 | | -import type { SchemaInfo, TableInfo, ColumnInfo, PluginInfo, NestedFieldInfo } from '../types'; |
| 19 | +import type { SchemaInfo, TableInfo, ColumnInfo, PluginInfo, NestedFieldInfo, SubTableInfo } from '../types'; |
20 | 20 | import { executeQuery } from './queries'; |
21 | 21 |
|
22 | 22 | const METADATA_BASE = '/api/v1/metadata'; |
23 | 23 |
|
| 24 | +/** |
| 25 | + * Format a compound schema name for use in SQL queries. |
| 26 | + * Plugin name stays unquoted; workspace parts are backtick-quoted. |
| 27 | + * e.g. "dfs.test" → "dfs.`test`", "dfs" → "dfs" |
| 28 | + */ |
| 29 | +function formatSchema(schema: string): string { |
| 30 | + const parts = schema.split('.'); |
| 31 | + if (parts.length <= 1) { |
| 32 | + return schema; |
| 33 | + } |
| 34 | + return parts[0] + '.' + parts.slice(1).map((p) => `\`${p}\``).join('.'); |
| 35 | +} |
| 36 | + |
24 | 37 | export interface PluginsResponse { |
25 | 38 | plugins: PluginInfo[]; |
26 | 39 | } |
@@ -147,14 +160,24 @@ export async function getFiles(schema: string, subPath?: string): Promise<FileIn |
147 | 160 | } |
148 | 161 |
|
149 | 162 | /** |
150 | | - * Fetch columns from a file by executing SELECT * LIMIT 1 |
| 163 | + * Fetch columns from a file by executing SELECT * LIMIT 1 via the query API. |
| 164 | + * Uses the same code path as the SQL editor so format plugins work consistently. |
151 | 165 | */ |
152 | 166 | export async function getFileColumns(schema: string, filePath: string): Promise<ColumnInfo[]> { |
153 | | - const response = await apiClient.get<ColumnsResponse>( |
154 | | - `${METADATA_BASE}/schemas/${encodeURIComponent(schema)}/files/columns`, |
155 | | - { params: { path: filePath } } |
156 | | - ); |
157 | | - return response.data.columns; |
| 167 | + const query = `SELECT * FROM ${formatSchema(schema)}.\`${filePath}\` LIMIT 1`; |
| 168 | + const result = await executeQuery({ query, queryType: 'SQL', autoLimitRowCount: 1 }); |
| 169 | + |
| 170 | + if (!result.columns || result.columns.length === 0) { |
| 171 | + return []; |
| 172 | + } |
| 173 | + |
| 174 | + return result.columns.map((colName, idx) => ({ |
| 175 | + name: colName, |
| 176 | + type: result.metadata?.[idx] || 'ANY', |
| 177 | + nullable: true, |
| 178 | + schema, |
| 179 | + table: filePath, |
| 180 | + })); |
158 | 181 | } |
159 | 182 |
|
160 | 183 | /** |
@@ -192,7 +215,7 @@ export async function getNestedColumns( |
192 | 215 |
|
193 | 216 | const query = |
194 | 217 | `SELECT getMapSchema(${columnExpr}) AS \`schema\`` + |
195 | | - ` FROM \`${schema}\`.\`${tableOrFile}\` LIMIT 1`; |
| 218 | + ` FROM ${formatSchema(schema)}.\`${tableOrFile}\` LIMIT 1`; |
196 | 219 |
|
197 | 220 | const result = await executeQuery({ |
198 | 221 | query, |
@@ -221,3 +244,105 @@ export async function getNestedColumns( |
221 | 244 |
|
222 | 245 | return []; |
223 | 246 | } |
| 247 | + |
| 248 | +/** |
| 249 | + * Fetch sub-tables (sheets, datasets, tables) within a multi-table file. |
| 250 | + * |
| 251 | + * @param schema the schema name (e.g. "dfs.tmp") |
| 252 | + * @param filePath the file path (e.g. "data.xlsx") |
| 253 | + * @param formatType "excel" | "hdf5" | "msaccess" |
| 254 | + */ |
| 255 | +export async function getSubTables( |
| 256 | + schema: string, |
| 257 | + filePath: string, |
| 258 | + formatType: string, |
| 259 | +): Promise<SubTableInfo[]> { |
| 260 | + let query: string; |
| 261 | + |
| 262 | + switch (formatType) { |
| 263 | + case 'excel': |
| 264 | + query = `SELECT _sheets FROM ${formatSchema(schema)}.\`${filePath}\` LIMIT 1`; |
| 265 | + break; |
| 266 | + case 'hdf5': |
| 267 | + query = `SELECT path, data_type FROM ${formatSchema(schema)}.\`${filePath}\``; |
| 268 | + break; |
| 269 | + case 'msaccess': |
| 270 | + query = `SELECT \`table\` FROM ${formatSchema(schema)}.\`${filePath}\``; |
| 271 | + break; |
| 272 | + default: |
| 273 | + return []; |
| 274 | + } |
| 275 | + |
| 276 | + const result = await executeQuery({ query, queryType: 'SQL', autoLimitRowCount: 1000 }); |
| 277 | + |
| 278 | + if (!result.rows || result.rows.length === 0) { |
| 279 | + return []; |
| 280 | + } |
| 281 | + |
| 282 | + if (formatType === 'excel') { |
| 283 | + const sheetsVal = result.rows[0]['_sheets']; |
| 284 | + if (Array.isArray(sheetsVal)) { |
| 285 | + return sheetsVal.map((s) => ({ name: String(s) })); |
| 286 | + } |
| 287 | + if (typeof sheetsVal === 'string') { |
| 288 | + // Could be JSON array string or comma-separated |
| 289 | + try { |
| 290 | + const parsed = JSON.parse(sheetsVal); |
| 291 | + if (Array.isArray(parsed)) { |
| 292 | + return parsed.map((s: unknown) => ({ name: String(s) })); |
| 293 | + } |
| 294 | + } catch { |
| 295 | + // Treat as comma-separated |
| 296 | + return sheetsVal.split(',').map((s) => ({ name: s.trim() })).filter((s) => s.name.length > 0); |
| 297 | + } |
| 298 | + } |
| 299 | + return []; |
| 300 | + } |
| 301 | + |
| 302 | + if (formatType === 'hdf5') { |
| 303 | + return result.rows |
| 304 | + .filter((row) => String(row['data_type']).toUpperCase() === 'DATASET') |
| 305 | + .map((row) => ({ name: String(row['path']), dataType: String(row['data_type']) })); |
| 306 | + } |
| 307 | + |
| 308 | + if (formatType === 'msaccess') { |
| 309 | + return result.rows.map((row) => ({ name: String(row['table']) })); |
| 310 | + } |
| 311 | + |
| 312 | + return []; |
| 313 | +} |
| 314 | + |
| 315 | +/** |
| 316 | + * Fetch columns for a sub-table within a multi-table file using table function syntax. |
| 317 | + * |
| 318 | + * @param schema the schema name (e.g. "dfs.tmp") |
| 319 | + * @param filePath the file path (e.g. "data.xlsx") |
| 320 | + * @param formatType "excel" | "hdf5" | "msaccess" |
| 321 | + * @param paramName the table-function parameter name (e.g. "sheetName") |
| 322 | + * @param subTableName the specific sub-table name (e.g. "Sheet1") |
| 323 | + */ |
| 324 | +export async function getSubTableColumns( |
| 325 | + schema: string, |
| 326 | + filePath: string, |
| 327 | + formatType: string, |
| 328 | + paramName: string, |
| 329 | + subTableName: string, |
| 330 | +): Promise<ColumnInfo[]> { |
| 331 | + const query = |
| 332 | + `SELECT * FROM table( ${formatSchema(schema)}.\`${filePath}\`` + |
| 333 | + ` (type => '${formatType}', ${paramName} => '${subTableName}')) LIMIT 1`; |
| 334 | + |
| 335 | + const result = await executeQuery({ query, queryType: 'SQL', autoLimitRowCount: 1 }); |
| 336 | + |
| 337 | + if (!result.columns || result.columns.length === 0) { |
| 338 | + return []; |
| 339 | + } |
| 340 | + |
| 341 | + return result.columns.map((colName, idx) => ({ |
| 342 | + name: colName, |
| 343 | + type: result.metadata?.[idx] || 'ANY', |
| 344 | + nullable: true, |
| 345 | + schema, |
| 346 | + table: `${filePath}/${subTableName}`, |
| 347 | + })); |
| 348 | +} |
0 commit comments