Skip to content
Open
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
252 changes: 252 additions & 0 deletions src/language/sql/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@ import Statement from "./statement";
import SQLTokeniser from "./tokens";
import { CallableReference, Definition, IRange, ParsedEmbeddedStatement, StatementGroup, StatementType, StatementTypeWord, Token } from "./types";



export interface ParsedColumn {
columnName: string; // SQL-facing or physical name
aliasName?: string; // always the real DB2 column name (zztype, zzvaleur, etc.)
isAlias: boolean; // true if alias name, false if physical name
type?: string; // optional: char(5), varchar(20), etc.
}

export interface ParsedTableEntry {
tableName: string;
systemTableName?:string; // "mouni", "mouni3", "mylib/zz01pf", etc.
columns: ParsedColumn[]; // array of columns for that table
}
export interface ParsedTable {
columns: ParsedColumn[];
}
export default class Document {
content: string;
statements: Statement[];
Expand Down Expand Up @@ -188,6 +205,21 @@ export default class Document {

return groups;
}
getColumnsAndTable():ParsedTableEntry[] {
const groups = this.getStatementGroups();

const result:ParsedTableEntry[] = [];

for (const group of groups) {
if(group.statements[0].type === StatementType.Create) {
const info:ParsedTableEntry = getCreateTableInfo(group.statements[0].tokens);
result.push(info);

}
}

return result;
}

getDefinitions(): Definition[] {
const groups = this.getStatementGroups();
Expand Down Expand Up @@ -331,4 +363,224 @@ function getSymbolsForStatements(statements: Statement[]) {
}

return defintions;
}


//-----------------------------------------------------------
// UNIVERSAL SQL COLUMN PARSER FOR ALL CREATE TABLE STYLES
//-----------------------------------------------------------
//-----------------------------------------------------------
// UNIVERSAL SQL PARSER (TABLE NAME + COLUMNS)
//-----------------------------------------------------------
export function getCreateTableInfo(tokens: any[]):ParsedTableEntry {
const {tableName,systemName} = extractTableNames(tokens);
const columnGroups = extractColumnGroups(tokens);
const columnsValues = extractColumnNames(columnGroups);
const {columns,ColumnNames}= columnsValues;
return {
tableName: tableName,
systemTableName:systemName ?? tableName,
columns: ColumnNames
};
}

//-----------------------------------------------------------
// 0) Extract TABLE NAME from CREATE TABLE statement
//-----------------------------------------------------------
function extractTableNames(tokens: any[]) {
let foundTable = false;
let tableNameParts: string[] = [];
let systemName: string | null = null;

for (let i = 0; i < tokens.length; i++) {
const v = tokens[i].value?.toLowerCase();

// Detect TABLE keyword
if (v === "table") {
foundTable = true;
continue;
}

if (!foundTable) continue;

// STOP collecting table name if "(" begins
if (tokens[i].value === "(") break;

// Detect "FOR SYSTEM NAME <xxx>"
if (
v === "for" &&
tokens[i + 1]?.value?.toLowerCase() === "system" &&
tokens[i + 2]?.value?.toLowerCase() === "name"
) {
// system name is the next token after NAME
systemName = tokens[i + 3]?.value ?? null;
break; // STOP reading table name
}

// Skip noise keywords
const skip = ["if", "not", "exists", "or", "replace"];
if (skip.includes(v)) continue;

// Collect table name parts
if (
tokens[i].type === "word" ||
tokens[i].type === "string" ||
/[\/\.]/.test(tokens[i].value)
) {
tableNameParts.push(tokens[i].value);
}
}

const tableName = tableNameParts.length > 0 ? tableNameParts.join("") : null;

return { tableName, systemName };
}




//-----------------------------------------------------------
// 1) Extract column groups inside the main ( ... ) block
//-----------------------------------------------------------
function extractColumnGroups(tokens: any[]) {
let startIndex = -1;
let depth = 0;
for (let i = 0; i < tokens.length; i++) {
const t = tokens[i];

if (t.value === "(" && startIndex === -1) {
startIndex = i;
depth = 1;
i++;

// find matching closing )
while (i < tokens.length && depth > 0) {
if (tokens[i].value === "(") depth++;
else if (tokens[i].value === ")") depth--;
i++;
}

const endIndex = i - 1;
const innerTokens = tokens.slice(startIndex + 1, endIndex);

// ---------------------------------------------------
// Split by commas ONLY on depth=0
// ---------------------------------------------------
const groups: any[][] = [];
let current: any[] = [];
let d = 0;

for (const token of innerTokens) {
if (token.value === "," && d === 0) {
if (current.length > 0) {
groups.push(current);
current = [];
}
continue;
}

current.push(token);

if (token.value === "(") d++;
else if (token.value === ")") d--;
}

if (current.length > 0) groups.push(current);

// REMOVE TABLE CONSTRAINT GROUPS
const unwanted = [
"constraint",
"primary",
"foreign",
"unique",
"check",
"references",
"key"
];

return groups.filter(group => {
const text = group.map(t => t.value.toLowerCase()).join(" ");
return !unwanted.some(word => text.startsWith(word));
});
}
}

return [];
}

//-----------------------------------------------------------
// 2) Extract column names from each group
//-----------------------------------------------------------



function extractColumnNames(groups: any[][]) {
const columns: string[] = [];
const ColumnNames: ParsedColumn[] = [];


for (const group of groups) {
const result = parseSingleColumn(group);
if (!result) continue;

const { aliasForColumn, normalIdentifiers } = result;
if(aliasForColumn.length===1&& normalIdentifiers.length===1)
{
ColumnNames.push({columnName:normalIdentifiers[0], aliasName:aliasForColumn[0], isAlias: true});
ColumnNames.push({columnName:aliasForColumn[0], isAlias: false});
ColumnNames.map(col=> columns.push(col.columnName));
// columns.push(columnNames);
//
}
}

return {columns,
ColumnNames
};
}


//-----------------------------------------------------------
// 3) Parse a single column definition
//-----------------------------------------------------------
function parseSingleColumn(tokens: any[]) {
if (!tokens.length) return null;

const aliasForColumn: string[] = [];
const normalIdentifiers: string[] = [];

// -------------------------------
// A) Collect DB2 alias-for-column
// -------------------------------
for (let i = 0; i < tokens.length; i++) {
const t = tokens[i].value?.toLowerCase();
// IF first token is identifier/word/string → it's the alias name
if(tokens[i].value===tokens[0].value && tokens[0].type==="word")
{
aliasForColumn.push(cleanIdentifier(tokens[0].value));
}

// IF (FOR COLUMN realName)
else if (t === "for" && tokens[i + 1]?.value?.toLowerCase() === "column") {
const real = tokens[i + 2];
if (real) normalIdentifiers.push(cleanIdentifier(real.value));
}

}

// ------------------------
// B) Return everything
// ------------------------
return {
aliasForColumn,
normalIdentifiers,
};
}

//-----------------------------------------------------------
// Helpers
//-----------------------------------------------------------

function cleanIdentifier(name: string) {
return name.replace(/^[`\["']+|[`"\]']+$/g, "");
}