@@ -13,6 +13,8 @@ import {
1313 GithubReporter ,
1414 ReportIndexRecommendation ,
1515} from "./reporters/github.ts" ;
16+ import { preprocessEncodedJson } from "./json.ts" ;
17+ import { ExplainedLog } from "./pg_log.ts" ;
1618
1719function formatQuery ( query : string ) {
1820 return format ( query , {
@@ -63,6 +65,7 @@ async function main() {
6365 const recommendations : ReportIndexRecommendation [ ] = [ ] ;
6466 let allQueries = 0 ;
6567 let matching = 0 ;
68+ let skipped = { } ;
6669 const pg = postgres ( postgresUrl ) ;
6770 const stats = new Statistics ( pg ) ;
6871 const existingIndexes = await stats . getExistingIndexes ( ) ;
@@ -91,82 +94,67 @@ async function main() {
9194 if ( loglevel !== "LOG" || ! queryString . startsWith ( "plan:" ) ) {
9295 continue ;
9396 }
94- const plan : string = queryString . split ( "plan:" ) [ 1 ] . trim ( ) ;
95- let isJSONOutput = false ;
96- let i = 0 ;
97- for ( ; i < plan . length ; i ++ ) {
98- const char = plan [ i ] ;
99- if ( char === "\\" && plan [ i + 1 ] === "n" ) {
100- i ++ ;
101- continue ;
102- } else if ( / \s + / . test ( char ) ) {
103- continue ;
104- } else if ( char === "{" ) {
105- isJSONOutput = true ;
106- break ;
107- }
108- }
109- if ( ! isJSONOutput ) {
110- return ;
97+ const planString : string = queryString . split ( "plan:" ) [ 1 ] . trim ( ) ;
98+ const json = preprocessEncodedJson ( planString ) ;
99+ if ( ! json ) {
100+ continue ;
111101 }
112- const json = plan
113- . slice ( i )
114- . replace ( / \\ n / g, "\n" )
115- // there are random control characters in the json lol
116- // deno-lint-ignore no-control-regex
117- . replace ( / [ \u0000 - \u001F ] + / g, ( c ) =>
118- c === "\n" ? "\\n" : c === "\r" ? "\\r" : c === "\t" ? "\\t" : ""
119- ) ;
120- let parsed : any ;
102+ let parsed : ExplainedLog ;
121103 try {
122- parsed = JSON . parse ( json ) ;
104+ parsed = new ExplainedLog ( json ) ;
123105 } catch ( e ) {
124106 console . log ( e ) ;
125107 break ;
126108 }
127109 allQueries ++ ;
128- const queryFingerprint = await fingerprint ( parsed [ "Query Text" ] ) ;
110+ const { query, parameters, plan } = parsed ;
111+ const queryFingerprint = await fingerprint ( query ) ;
129112 if (
130113 // TODO: we can support inserts/updates too. Just need the right optimization for it.
131- parsed . Plan [ "Node Type" ] === "ModifyTable" ||
132- parsed [ "Query Text" ] . includes ( "@qd_introspection" )
114+ plan . nodeType === "ModifyTable" ||
115+ query . includes ( "@qd_introspection" )
133116 ) {
134117 continue ;
135118 }
136119 const fingerprintNum = parseInt ( queryFingerprint , 16 ) ;
137120 if ( seenQueries . has ( fingerprintNum ) ) {
138- console . log ( "Skipping duplicate query" , fingerprintNum ) ;
121+ if ( process . env . DEBUG ) {
122+ console . log ( "Skipping duplicate query" , fingerprintNum ) ;
123+ }
139124 continue ;
140125 }
141126 seenQueries . add ( fingerprintNum ) ;
142- const query = parsed [ "Query Text" ] ;
143- const rawParams = parsed [ "Query Parameters" ] ;
144- const params = rawParams ? extractParams ( rawParams ) : [ ] ;
145127 const analyzer = new Analyzer ( ) ;
146128
147129 const { indexesToCheck, ansiHighlightedQuery, referencedTables } =
148- await analyzer . analyze ( formatQuery ( query ) , params ) ;
130+ await analyzer . analyze ( formatQuery ( query ) , parameters ) ;
149131
150132 const selectsCatalog = referencedTables . find ( ( table ) =>
151133 table . startsWith ( "pg_" )
152134 ) ;
153135 if ( selectsCatalog ) {
154- console . log (
155- "Skipping query that selects from catalog tables" ,
156- selectsCatalog ,
157- fingerprintNum
158- ) ;
136+ if ( process . env . DEBUG ) {
137+ console . log (
138+ "Skipping query that selects from catalog tables" ,
139+ selectsCatalog ,
140+ fingerprintNum
141+ ) ;
142+ }
159143 continue ;
160144 }
161- console . log ( query ) ;
162145 const indexCandidates = analyzer . deriveIndexes ( tables , indexesToCheck ) ;
163146 if ( indexCandidates . length > 0 ) {
164147 await core . group ( `query:${ fingerprintNum } ` , async ( ) => {
165148 console . time ( `timing` ) ;
166149 matching ++ ;
167150 printLegend ( ) ;
168151 console . log ( ansiHighlightedQuery ) ;
169- const out = await optimizer . run ( query , params , indexCandidates , tables ) ;
152+ const out = await optimizer . run (
153+ query ,
154+ parameters ,
155+ indexCandidates ,
156+ tables
157+ ) ;
170158 if ( out . newIndexes . size > 0 ) {
171159 const newIndexes = Array . from ( out . newIndexes )
172160 . map ( ( n ) => out . triedIndexes . get ( n ) ?. definition )
@@ -185,11 +173,7 @@ async function main() {
185173 }
186174 } )
187175 . filter ( ( i ) => i !== undefined ) ;
188- console . log ( dedent `
189- Optimized cost from ${ out . baseCost } to ${ out . finalCost }
190- Existing indexes: ${ Array . from ( out . existingIndexes ) . join ( ", " ) }
191- New indexes: ${ newIndexes . join ( ", " ) }
192- ` ) ;
176+ console . log ( `New indexes: ${ newIndexes . join ( ", " ) } ` ) ;
193177 recommendations . push ( {
194178 formattedQuery : formatQuery ( query ) ,
195179 baseCost : out . baseCost ,
@@ -219,27 +203,11 @@ async function main() {
219203 timeElapsed : Date . now ( ) - startDate . getTime ( ) ,
220204 } ,
221205 } ) ;
206+ console . log ( seenQueries . size ) ;
222207 console . timeEnd ( "total" ) ;
223208 Deno . exit ( 0 ) ;
224209}
225210
226- const paramPattern = / \$ ( \d + ) \s * = \s * (?: ' ( [ ^ ' ] * ) ' | ( [ ^ , \s ] + ) ) / g;
227- function extractParams ( logLine : string ) {
228- const paramsArray = [ ] ;
229- let match ;
230-
231- while ( ( match = paramPattern . exec ( logLine ) ) !== null ) {
232- const paramValue = match [ 2 ] !== undefined ? match [ 2 ] : match [ 3 ] ;
233- // Push the value directly into the array.
234- // The order is determined by the $1, $2, etc. in the log line.
235- paramsArray [ parseInt ( match [ 1 ] ) - 1 ] = paramValue ;
236- }
237-
238- // Filter out any empty slots if parameters were not consecutive (e.g., $1, $3 present, but $2 missing)
239- // This ensures a dense array without 'empty' items.
240- return paramsArray . filter ( ( value ) => value !== undefined ) ;
241- }
242-
243211if ( import . meta. main ) {
244212 await main ( ) ;
245213}
0 commit comments