@@ -44,9 +44,10 @@ public void Execute(string sql, IWAL? wal = null)
4444 /// <inheritdoc />
4545 public void Execute ( string sql , Dictionary < string , object ? > parameters , IWAL ? wal = null )
4646 {
47- // If parameters are provided, bind them to ? placeholders
47+ string ? originalSql = null ;
4848 if ( parameters != null && parameters . Count > 0 )
4949 {
50+ originalSql = sql ;
5051 sql = this . BindParameters ( sql , parameters ) ;
5152 }
5253 else
@@ -59,7 +60,7 @@ public void Execute(string sql, Dictionary<string, object?> parameters, IWAL? wa
5960
6061 // Proceed with existing logic
6162 var parts = sql . Trim ( ) . Split ( ' ' , StringSplitOptions . RemoveEmptyEntries ) ;
62- this . ExecuteInternal ( sql , parts , wal ) ;
63+ this . ExecuteInternal ( originalSql ?? sql , parts , wal , originalSql ?? sql ) ;
6364 }
6465
6566 /// <summary>
@@ -80,7 +81,7 @@ public void Execute(CachedQueryPlan plan, Dictionary<string, object?> parameters
8081 sql = this . SanitizeSql ( sql ) ;
8182 }
8283
83- this . ExecuteInternal ( sql , plan . Parts , wal ) ;
84+ this . ExecuteInternal ( sql , plan . Parts , wal , plan . Sql ) ;
8485 }
8586
8687 /// <summary>
@@ -104,7 +105,7 @@ public List<Dictionary<string, object>> ExecuteQuery(string sql, Dictionary<stri
104105 return this . ExecuteQueryInternal ( sql , parts ) ;
105106 }
106107
107- private void ExecuteInternal ( string sql , string [ ] parts , IWAL ? wal = null )
108+ private void ExecuteInternal ( string sql , string [ ] parts , IWAL ? wal = null , string ? cacheKey = null )
108109 {
109110 // Parts are already provided, no need to parse again
110111
@@ -165,6 +166,19 @@ private void ExecuteInternal(string sql, string[] parts, IWAL? wal = null)
165166 } ;
166167 this . tables [ tableName ] = table ;
167168 wal ? . Log ( sql ) ;
169+ // Auto-create hash index on primary key for faster lookups
170+ if ( primaryKeyIndex >= 0 )
171+ {
172+ table . CreateHashIndex ( table . Columns [ primaryKeyIndex ] ) ;
173+ }
174+ // Auto-create hash indexes on all columns for faster WHERE lookups
175+ for ( int i = 0 ; i < columns . Count ; i ++ )
176+ {
177+ if ( i != primaryKeyIndex )
178+ {
179+ table . CreateHashIndex ( columns [ i ] ) ;
180+ }
181+ }
168182 }
169183 else if ( parts [ 0 ] . ToUpper ( ) == SqlConstants . CREATE && parts [ 1 ] . ToUpper ( ) == "INDEX" )
170184 {
@@ -268,6 +282,52 @@ private void ExecuteInternal(string sql, string[] parts, IWAL? wal = null)
268282 this . tables [ tableName ] . Insert ( row ) ;
269283 wal ? . Log ( sql ) ;
270284 }
285+ else if ( parts [ 0 ] . ToUpper ( ) == "EXPLAIN" )
286+ {
287+ if ( parts . Length < 2 || parts [ 1 ] . ToUpper ( ) != "SELECT" )
288+ {
289+ throw new InvalidOperationException ( "EXPLAIN only supports SELECT queries" ) ;
290+ }
291+ var selectParts = parts . Skip ( 1 ) . ToArray ( ) ;
292+ var fromIdx = Array . IndexOf ( selectParts , SqlConstants . FROM ) ;
293+ if ( fromIdx < 0 )
294+ {
295+ throw new InvalidOperationException ( "Invalid SELECT query for EXPLAIN" ) ;
296+ }
297+ var tableName = selectParts [ fromIdx + 1 ] ;
298+ if ( ! this . tables . ContainsKey ( tableName ) )
299+ {
300+ throw new InvalidOperationException ( $ "Table { tableName } does not exist") ;
301+ }
302+ var whereIdx = Array . IndexOf ( selectParts , SqlConstants . WHERE ) ;
303+ string plan = "Full table scan" ;
304+ if ( whereIdx > 0 )
305+ {
306+ var whereStr = string . Join ( " " , selectParts . Skip ( whereIdx + 1 ) ) ;
307+ var whereTokens = whereStr . Split ( ' ' , StringSplitOptions . RemoveEmptyEntries ) ;
308+ if ( whereTokens . Length >= 3 && whereTokens [ 1 ] == "=" )
309+ {
310+ var col = whereTokens [ 0 ] ;
311+ if ( this . tables [ tableName ] . HasHashIndex ( col ) )
312+ {
313+ plan = $ "Hash index lookup on { col } ";
314+ }
315+ else if ( this . tables [ tableName ] . PrimaryKeyIndex >= 0 && this . tables [ tableName ] . Columns [ this . tables [ tableName ] . PrimaryKeyIndex ] == col )
316+ {
317+ plan = $ "Primary key lookup on { col } ";
318+ }
319+ else
320+ {
321+ plan = $ "Full table scan with WHERE on { col } ";
322+ }
323+ }
324+ else
325+ {
326+ plan = "Full table scan with complex WHERE" ;
327+ }
328+ }
329+ Console . WriteLine ( $ "Query Plan: { plan } ") ;
330+ }
271331 else if ( parts [ 0 ] . ToUpper ( ) == SqlConstants . SELECT )
272332 {
273333 var fromIdx = Array . IndexOf ( parts , SqlConstants . FROM ) ;
@@ -344,10 +404,10 @@ private void ExecuteInternal(string sql, string[] parts, IWAL? wal = null)
344404 var results = new List < Dictionary < string , object > > ( ) ;
345405 foreach ( var r1 in rows1 )
346406 {
347- var key = r1 [ left ] ;
348- if ( dict2 . ContainsKey ( key ?? new object ( ) ) )
407+ var joinKey = r1 [ left ] ;
408+ if ( dict2 . ContainsKey ( joinKey ?? new object ( ) ) )
349409 {
350- foreach ( var r2 in dict2 [ key ?? new object ( ) ] )
410+ foreach ( var r2 in dict2 [ joinKey ?? new object ( ) ] )
351411 {
352412 var combined = new Dictionary < string , object > ( ) ;
353413 foreach ( var kv in r1 )
@@ -416,6 +476,7 @@ private void ExecuteInternal(string sql, string[] parts, IWAL? wal = null)
416476 else
417477 {
418478 var tableName = fromParts [ 0 ] ;
479+ Console . WriteLine ( $ "Query Plan: { GetQueryPlan ( tableName , whereStr ) } ") ;
419480 var results = this . tables [ tableName ] . Select ( whereStr , orderBy , asc ) ;
420481
421482 // Apply limit and offset
@@ -541,14 +602,14 @@ private bool EvaluateJoinWhere(Dictionary<string, object> row, string where)
541602
542603 return op switch
543604 {
544- "=" => ( rowValue ? . ToString ( ) == value ) ,
545- "!=" => ( rowValue ? . ToString ( ) != value ) ,
605+ "=" => rowValue ? . ToString ( ) == value ,
606+ "!=" => rowValue ? . ToString ( ) != value ,
546607 "<" => Comparer < object > . Default . Compare ( rowValue , value ) < 0 ,
547608 "<=" => Comparer < object > . Default . Compare ( rowValue , value ) <= 0 ,
548609 ">" => Comparer < object > . Default . Compare ( rowValue , value ) > 0 ,
549610 ">=" => Comparer < object > . Default . Compare ( rowValue , value ) >= 0 ,
550- "LIKE" => rowValue ? . ToString ( ) . Contains ( value . Replace ( "%" , "" ) . Replace ( "_" , "" ) ) == true ,
551- "NOT LIKE" => rowValue ? . ToString ( ) . Contains ( value . Replace ( "%" , "" ) . Replace ( "_" , "" ) ) != true ,
611+ "LIKE" => rowValue ? . ToString ( ) . Contains ( value . Replace ( "%" , string . Empty ) . Replace ( "_" , string . Empty ) ) == true ,
612+ "NOT LIKE" => rowValue ? . ToString ( ) . Contains ( value . Replace ( "%" , string . Empty ) . Replace ( "_" , string . Empty ) ) != true ,
552613 "IN" => value . Split ( ',' ) . Select ( v => v . Trim ( ) . Trim ( '\' ' ) ) . Contains ( rowValue ? . ToString ( ) ) ,
553614 "NOT IN" => ! value . Split ( ',' ) . Select ( v => v . Trim ( ) . Trim ( '\' ' ) ) . Contains ( rowValue ? . ToString ( ) ) ,
554615 _ => throw new InvalidOperationException ( $ "Unsupported operator { op } ") ,
@@ -579,14 +640,14 @@ private bool EvaluateJoinWhere(Dictionary<string, object> row, string where)
579640
580641 subConditions . Add ( op switch
581642 {
582- "=" => ( rowValue ? . ToString ( ) == value ) ,
583- "!=" => ( rowValue ? . ToString ( ) != value ) ,
643+ "=" => rowValue ? . ToString ( ) == value ,
644+ "!=" => rowValue ? . ToString ( ) != value ,
584645 "<" => Comparer < object > . Default . Compare ( rowValue , value ) < 0 ,
585646 "<=" => Comparer < object > . Default . Compare ( rowValue , value ) <= 0 ,
586647 ">" => Comparer < object > . Default . Compare ( rowValue , value ) > 0 ,
587648 ">=" => Comparer < object > . Default . Compare ( rowValue , value ) >= 0 ,
588- "LIKE" => rowValue ? . ToString ( ) . Contains ( value . Replace ( "%" , "" ) . Replace ( "_" , "" ) ) == true ,
589- "NOT LIKE" => rowValue ? . ToString ( ) . Contains ( value . Replace ( "%" , "" ) . Replace ( "_" , "" ) ) != true ,
649+ "LIKE" => rowValue ? . ToString ( ) . Contains ( value . Replace ( "%" , string . Empty ) . Replace ( "_" , string . Empty ) ) == true ,
650+ "NOT LIKE" => rowValue ? . ToString ( ) . Contains ( value . Replace ( "%" , string . Empty ) . Replace ( "_" , string . Empty ) ) != true ,
590651 "IN" => value . Split ( ',' ) . Select ( v => v . Trim ( ) . Trim ( '\' ' ) ) . Contains ( rowValue ? . ToString ( ) ) ,
591652 "NOT IN" => ! value . Split ( ',' ) . Select ( v => v . Trim ( ) . Trim ( '\' ' ) ) . Contains ( rowValue ? . ToString ( ) ) ,
592653 _ => throw new InvalidOperationException ( $ "Unsupported operator { op } ") ,
@@ -607,7 +668,7 @@ private string BindParameters(string sql, Dictionary<string, object?> parameters
607668 foreach ( var param in parameters )
608669 {
609670 var paramName = param . Key ;
610- var valueStr = FormatValue ( param . Value ) ;
671+ var valueStr = this . FormatValue ( param . Value ) ;
611672
612673 // Try matching with @ prefix
613674 if ( paramName . StartsWith ( "@" ) )
@@ -755,10 +816,10 @@ private List<Dictionary<string, object>> ExecuteQueryInternal(string sql, string
755816 var results = new List < Dictionary < string , object > > ( ) ;
756817 foreach ( var r1 in rows1 )
757818 {
758- var key = r1 [ left ] ;
759- if ( dict2 . ContainsKey ( key ?? new object ( ) ) )
819+ var joinKey = r1 [ left ] ;
820+ if ( dict2 . ContainsKey ( joinKey ?? new object ( ) ) )
760821 {
761- foreach ( var r2 in dict2 [ key ?? new object ( ) ] )
822+ foreach ( var r2 in dict2 [ joinKey ?? new object ( ) ] )
762823 {
763824 var combined = new Dictionary < string , object > ( ) ;
764825 foreach ( var kv in r1 )
@@ -824,6 +885,7 @@ private List<Dictionary<string, object>> ExecuteQueryInternal(string sql, string
824885 else
825886 {
826887 var tableName = fromParts [ 0 ] ;
888+ Console . WriteLine ( $ "Query Plan: { GetQueryPlan ( tableName , whereStr ) } ") ;
827889 var results = this . tables [ tableName ] . Select ( whereStr , orderBy , asc ) ;
828890
829891 // Apply limit and offset
@@ -846,4 +908,34 @@ private List<Dictionary<string, object>> ExecuteQueryInternal(string sql, string
846908 return new List < Dictionary < string , object > > ( ) ;
847909 }
848910 }
911+
912+ private string GetQueryPlan ( string tableName , string ? whereStr )
913+ {
914+ string plan = "Full table scan" ;
915+ if ( ! string . IsNullOrEmpty ( whereStr ) )
916+ {
917+ var whereTokens = whereStr . Split ( ' ' , StringSplitOptions . RemoveEmptyEntries ) ;
918+ if ( whereTokens . Length >= 3 && whereTokens [ 1 ] == "=" )
919+ {
920+ var col = whereTokens [ 0 ] ;
921+ if ( this . tables [ tableName ] . HasHashIndex ( col ) )
922+ {
923+ plan = $ "Hash index lookup on { col } ";
924+ }
925+ else if ( this . tables [ tableName ] . PrimaryKeyIndex >= 0 && this . tables [ tableName ] . Columns [ this . tables [ tableName ] . PrimaryKeyIndex ] == col )
926+ {
927+ plan = $ "Primary key lookup on { col } ";
928+ }
929+ else
930+ {
931+ plan = $ "Full table scan with WHERE on { col } ";
932+ }
933+ }
934+ else
935+ {
936+ plan = "Full table scan with complex WHERE" ;
937+ }
938+ }
939+ return plan ;
940+ }
849941}
0 commit comments