(line: string)
| 141 | * Returns null for non-column lines (constraints, empty lines). |
| 142 | */ |
| 143 | export function parseColumnLine(line: string): ParsedColumn | null { |
| 144 | const trimmed = line.trim().replace(/,$/, '').trim(); |
| 145 | if (trimmed.length === 0) return null; |
| 146 | |
| 147 | const upper = trimmed.toUpperCase(); |
| 148 | if ( |
| 149 | upper.startsWith('PRIMARY KEY') || |
| 150 | upper.startsWith('UNIQUE KEY') || |
| 151 | upper.startsWith('UNIQUE (') || |
| 152 | upper.startsWith('KEY ') || |
| 153 | upper.startsWith('INDEX ') || |
| 154 | upper.startsWith('CHECK') || |
| 155 | upper.startsWith('FOREIGN KEY') || |
| 156 | upper.startsWith('CONSTRAINT') |
| 157 | ) { |
| 158 | return null; |
| 159 | } |
| 160 | |
| 161 | // Identifier: backticked or plain. |
| 162 | let rawName: string; |
| 163 | let rest: string; |
| 164 | if (trimmed.startsWith('`')) { |
| 165 | const close = trimmed.indexOf('`', 1); |
| 166 | if (close < 0) return null; |
| 167 | rawName = trimmed.slice(0, close + 1); |
| 168 | rest = trimmed.slice(close + 1).trim(); |
| 169 | } else { |
| 170 | const space = trimmed.search(/\s/); |
| 171 | if (space < 0) return null; |
| 172 | rawName = trimmed.slice(0, space); |
| 173 | rest = trimmed.slice(space).trim(); |
| 174 | } |
| 175 | |
| 176 | const name = rawName.replace(/^`|`$/g, ''); |
| 177 | |
| 178 | // Type token: `VARCHAR(255)`, `TINYINT(1)`, `INT`, etc. May contain a |
| 179 | // parenthesised arg list with no spaces in commons.sql. |
| 180 | const typeMatch = /^([A-Za-z]+(?:\([^)]*\))?)/.exec(rest); |
| 181 | if (!typeMatch) return null; |
| 182 | const rawType = typeMatch[1]; |
| 183 | const tail = rest.slice(rawType.length).trim(); |
| 184 | |
| 185 | const tailUpper = tail.toUpperCase(); |
| 186 | const notNull = /\bNOT\s+NULL\b/.test(tailUpper); |
| 187 | const isPrimaryKey = /\bPRIMARY\s+KEY\b/.test(tailUpper); |
| 188 | const defaultMatch = /\bDEFAULT\s+(.+?)(?:\s+(?:NOT\s+NULL|PRIMARY\s+KEY|UNIQUE)\b|$)/i.exec( |
| 189 | tail |
| 190 | ); |
| 191 | const defaultValue = defaultMatch ? defaultMatch[1].trim() : null; |
| 192 | |
| 193 | return { |
| 194 | name, |
| 195 | rawName, |
| 196 | type: parseColumnType(rawType), |
| 197 | rawType, |
| 198 | notNull: notNull || isPrimaryKey, |
| 199 | isPrimaryKey, |
| 200 | default: defaultValue, |
no test coverage detected