diff --git a/plugins/dbgate-plugin-duckdb/src/backend/Analyser.helpers.js b/plugins/dbgate-plugin-duckdb/src/backend/Analyser.helpers.js index ace6e464e..38628d74a 100644 --- a/plugins/dbgate-plugin-duckdb/src/backend/Analyser.helpers.js +++ b/plugins/dbgate-plugin-duckdb/src/backend/Analyser.helpers.js @@ -3,6 +3,8 @@ * @property {string[]} items */ +const extractIndexColumns = require('./extractIndexColumns'); + /** * @typedef {object} DuckDbColumnRow * @property {number | null} numeric_scale @@ -353,7 +355,7 @@ function mapIndexRowToIndexInfo(duckDbIndexRow) { pureName: duckDbIndexRow.table_name, schemaName: duckDbIndexRow.schema_name, constraintType: 'index', - columns: [], + columns: extractIndexColumns(duckDbIndexRow.sql).map((columnName) => ({ columnName })), isUnique: duckDbIndexRow.is_unique, }; diff --git a/plugins/dbgate-plugin-duckdb/src/backend/extractIndexColumns.js b/plugins/dbgate-plugin-duckdb/src/backend/extractIndexColumns.js new file mode 100644 index 000000000..8d93d17a1 --- /dev/null +++ b/plugins/dbgate-plugin-duckdb/src/backend/extractIndexColumns.js @@ -0,0 +1,69 @@ +// this algoruthm is implemented by ChatGPT o3 + +function sliceColumnList(ddl) { + // ① jump to the first “(” that follows the ON qualifier + const onMatch = /ON\s+(?:"[^"]+"|\w+)(?:\s*\.\s*(?:"[^"]+"|\w+))*\s*\(/i.exec(ddl); + if (!onMatch) return ''; + const start = onMatch.index + onMatch[0].length - 1; // points at the "(" + + // ② walk forward until depth returns to 0 + let depth = 0, + i = start, + end = -1; + while (i < ddl.length) { + const ch = ddl[i++]; + if (ch === '(') depth++; + else if (ch === ')') { + depth--; + if (depth === 0) { + end = i - 1; + break; + } + } + } + return end === -1 ? '' : ddl.slice(start + 1, end); // inside the () +} + +function splitTopLevel(list) { + const parts = []; + let depth = 0, + last = 0; + for (let i = 0; i <= list.length; i++) { + const ch = list[i] ?? ','; // virtual comma at the end + if (ch === '(') depth++; + else if (ch === ')') depth--; + if (ch === ',' && depth === 0) { + parts.push(list.slice(last, i).trim()); + last = i + 1; + } + } + return parts.filter(Boolean); +} + +function clean(segment) { + return ( + segment + // drop ASC|DESC, NULLS FIRST|LAST + .replace(/\bASC\b|\bDESC\b|\bNULLS\s+(FIRST|LAST)\b/gi, '') + // un-wrap one-liner functions: LOWER(col) -> col + .replace(/^[A-Za-z_][\w$]*\(\s*([^()]+?)\s*\)$/i, '$1') + // chop any schema- or table- prefix: tbl.col -> col + .replace(/^.*\.(?=[^.)]+$)/, '') + // strip double-quotes + .replace(/"/g, '') + .trim() + ); +} + +function extractIndexColumns(sql) { + const sqlText = sql // your variable + .replace(/\s+/g, ' ') // collapse whitespace + .replace(/--.*?$/gm, '') // strip line comments + .trim(); + + const list = sliceColumnList(sqlText); + if (!list) return []; + return splitTopLevel(list).map(clean); +} + +module.exports = extractIndexColumns;