11import { readdirSync , readFileSync , statSync } from "node:fs" ;
22import { join } from "node:path" ;
33
4+ const COLUMN_DEFAULT_PATTERN = / \b (?: D E F A U L T | E P H E M E R A L ) \b / i;
5+ const COLUMN_NAME_PATTERN = / ^ ` ? ( \w + ) ` ? \s + / ;
6+ const COMPUTED_COLUMN_PATTERN = / \b (?: M A T E R I A L I Z E D | A L I A S ) \b / i;
7+ const INDEX_NAME_PATTERN = / ^ I N D E X \s + ` ? ( \w + ) ` ? \s + / i;
8+ const LOW_CARDINALITY_PATTERN = / ^ L o w C a r d i n a l i t y \( ( .* ) \) $ / i;
9+ const MATERIALIZED_VIEW_PATTERN = / M A T E R I A L I Z E D \s + V I E W / i;
10+ const NULLABLE_PATTERN = / ^ N u l l a b l e \( / i;
11+ const QUALIFIED_NAME_PATTERN =
12+ / C R E A T E \s + (?: T A B L E | M A T E R I A L I Z E D \s + V I E W ) \s + (?: I F \s + N O T \s + E X I S T S \s + ) ? (?: ` ( [ ^ ` ] + ) ` | ( \w + ) ) \. (?: ` ( [ ^ ` ] + ) ` | ( \w + ) ) / i;
13+ const TABLE_NAME_PATTERN =
14+ / C R E A T E \s + (?: T A B L E | M A T E R I A L I Z E D \s + V I E W ) \s + (?: I F \s + N O T \s + E X I S T S \s + ) ? [ \w . ] * \. ( \w + ) / i;
15+
416export interface ParsedColumn {
17+ computed : boolean ;
18+ hasDefault : boolean ;
519 name : string ;
6- type : string ;
720 nullable : boolean ;
8- hasDefault : boolean ;
9- computed : boolean ;
21+ type : string ;
22+ }
23+
24+ export interface ParsedIndex {
25+ definition : string ;
26+ name : string ;
1027}
1128
1229export function isNullable ( type : string ) : boolean {
13- const inner = type . replace ( / ^ L o w C a r d i n a l i t y \( ( . * ) \) $ / i , "$1" ) . trim ( ) ;
14- return / ^ N u l l a b l e \( / i . test ( inner ) ;
30+ const inner = type . replace ( LOW_CARDINALITY_PATTERN , "$1" ) . trim ( ) ;
31+ return NULLABLE_PATTERN . test ( inner ) ;
1532}
1633
1734export interface ParsedTable {
18- name : string ;
19- isView : boolean ;
2035 columns : ParsedColumn [ ] ;
2136 engine : string ;
22- partitionBy : string ;
37+ indexes : ParsedIndex [ ] ;
38+ isView : boolean ;
39+ name : string ;
2340 orderBy : string ;
41+ partitionBy : string ;
2442 settings : string ;
2543}
2644
@@ -85,19 +103,15 @@ const COLUMN_MODIFIERS =
85103const NON_COLUMN = / ^ (?: I N D E X | C O N S T R A I N T | P R O J E C T I O N | P R I M A R Y \s + K E Y ) \b / i;
86104
87105export function tableNameOf ( sql : string ) : string {
88- const m = sql . match (
89- / C R E A T E \s + (?: T A B L E | M A T E R I A L I Z E D \s + V I E W ) \s + (?: I F \s + N O T \s + E X I S T S \s + ) ? [ \w . ] * \. ( \w + ) / i
90- ) ;
106+ const m = sql . match ( TABLE_NAME_PATTERN ) ;
91107 if ( ! m ) {
92108 throw new Error ( "Could not parse table name" ) ;
93109 }
94110 return m [ 1 ] ;
95111}
96112
97113export function qualifiedNameOf ( sql : string ) : string {
98- const m = sql . match (
99- / C R E A T E \s + (?: T A B L E | M A T E R I A L I Z E D \s + V I E W ) \s + (?: I F \s + N O T \s + E X I S T S \s + ) ? (?: ` ( [ ^ ` ] + ) ` | ( \w + ) ) \. (?: ` ( [ ^ ` ] + ) ` | ( \w + ) ) / i
100- ) ;
114+ const m = sql . match ( QUALIFIED_NAME_PATTERN ) ;
101115 if ( ! m ) {
102116 throw new Error ( "Could not parse qualified table name" ) ;
103117 }
@@ -112,7 +126,7 @@ export function parseColumns(sql: string): ParsedColumn[] {
112126 if ( NON_COLUMN . test ( item ) ) {
113127 continue ;
114128 }
115- const nameMatch = item . match ( / ^ ` ? ( \w + ) ` ? \s + / ) ;
129+ const nameMatch = item . match ( COLUMN_NAME_PATTERN ) ;
116130 if ( ! nameMatch ) {
117131 continue ;
118132 }
@@ -123,31 +137,53 @@ export function parseColumns(sql: string): ParsedColumn[] {
123137 name : nameMatch [ 1 ] ,
124138 type,
125139 nullable : isNullable ( type ) ,
126- hasDefault : / \b (?: D E F A U L T | E P H E M E R A L ) \b / i . test ( afterName ) ,
127- computed : / \b (?: M A T E R I A L I Z E D | A L I A S ) \b / i . test ( afterName ) ,
140+ hasDefault : COLUMN_DEFAULT_PATTERN . test ( afterName ) ,
141+ computed : COMPUTED_COLUMN_PATTERN . test ( afterName ) ,
128142 } ) ;
129143 }
130144 return cols ;
131145}
132146
147+ function normalizeDefinition ( definition : string ) : string {
148+ return definition . replaceAll ( "`" , "" ) . replace ( / \s + / g, " " ) . trim ( ) ;
149+ }
150+
151+ export function parseIndexes ( sql : string ) : ParsedIndex [ ] {
152+ const indexes : ParsedIndex [ ] = [ ] ;
153+ for ( const item of splitTopLevel ( firstParenGroup ( sql ) . body ) ) {
154+ const match = item . match ( INDEX_NAME_PATTERN ) ;
155+ if ( ! match ) {
156+ continue ;
157+ }
158+ indexes . push ( {
159+ name : match [ 1 ] ,
160+ definition : normalizeDefinition ( item . slice ( match [ 0 ] . length ) ) ,
161+ } ) ;
162+ }
163+ return indexes ;
164+ }
165+
133166function clause ( tail : string , keyword : string , stops : string [ ] ) : string {
134- const lookahead = stops . length
135- ? `(?=(?:${ stops . join ( "|" ) } )\\b|$)`
136- : "(?=$)" ;
167+ const lookahead = stops . length ? `(?=(?:${ stops . join ( "|" ) } )\\b|$)` : "(?=$)" ;
137168 const re = new RegExp ( `${ keyword } \\s+([\\s\\S]*?)\\s*${ lookahead } ` , "i" ) ;
138169 const m = tail . match ( re ) ;
139170 return m ? m [ 1 ] . trim ( ) : "" ;
140171}
141172
142173export function parseTable ( sql : string ) : ParsedTable {
143174 const name = tableNameOf ( sql ) ;
144- const isView = / M A T E R I A L I Z E D \s + V I E W / i . test ( sql ) ;
175+ const isView = MATERIALIZED_VIEW_PATTERN . test ( sql ) ;
145176 const columns = parseColumns ( sql ) ;
146- const tail = sql . slice ( firstParenGroup ( sql ) . end + 1 ) . replace ( / \s + / g, " " ) . trim ( ) ;
177+ const indexes = parseIndexes ( sql ) ;
178+ const tail = sql
179+ . slice ( firstParenGroup ( sql ) . end + 1 )
180+ . replace ( / \s + / g, " " )
181+ . trim ( ) ;
147182 return {
148183 name,
149184 isView,
150185 columns,
186+ indexes,
151187 engine : clause ( tail , "ENGINE\\s*=" , [
152188 "PARTITION BY" ,
153189 "PRIMARY KEY" ,
0 commit comments