@@ -23,6 +23,8 @@ using SHA
2323include (" quandle_semantic.jl" )
2424using . QuandleSemantic
2525
26+ include (" query_explain.jl" )
27+
2628include (" Diagnostics.jl" )
2729include (" krl/KRL.jl" )
2830using . KRL: parse_any, parse_krl, parse_sql, KRLParseError, KRLLexError,
@@ -63,23 +65,6 @@ CREATE TABLE IF NOT EXISTS semantic_schema_info (
6365);
6466"""
6567
66- const SEMANTIC_INDEX_STATEMENTS = [
67- " CREATE INDEX IF NOT EXISTS idx_semantic_hash ON quandle_semantic_index(descriptor_hash)" ,
68- " CREATE INDEX IF NOT EXISTS idx_semantic_key ON quandle_semantic_index(quandle_key)" ,
69- " CREATE INDEX IF NOT EXISTS idx_semantic_crossing ON quandle_semantic_index(crossing_number)" ,
70- " CREATE INDEX IF NOT EXISTS idx_semantic_determinant ON quandle_semantic_index(determinant)" ,
71- " CREATE INDEX IF NOT EXISTS idx_semantic_signature ON quandle_semantic_index(signature)" ,
72- " CREATE INDEX IF NOT EXISTS idx_semantic_col3 ON quandle_semantic_index(colouring_count_3)" ,
73- " CREATE INDEX IF NOT EXISTS idx_semantic_col5 ON quandle_semantic_index(colouring_count_5)" ,
74- # DB-3 Phase A additions (see docs/db-3-index-strategy.md):
75- # writhe / genus / quandle_generator_count are accepted as filter
76- # parameters in GET /api/knots and GET /api/semantic but were not
77- # indexed; queries forced O(n) scans. B-tree per the audit.
78- " CREATE INDEX IF NOT EXISTS idx_semantic_writhe ON quandle_semantic_index(writhe)" ,
79- " CREATE INDEX IF NOT EXISTS idx_semantic_genus ON quandle_semantic_index(genus)" ,
80- " CREATE INDEX IF NOT EXISTS idx_semantic_gencount ON quandle_semantic_index(quandle_generator_count)" ,
81- ]
82-
8368const REQUIRED_SEMANTIC_COLUMNS = [
8469 (" descriptor_version" , " TEXT" ),
8570 (" descriptor_hash" , " TEXT" ),
@@ -116,8 +101,12 @@ mutable struct SemanticIndexDB
116101 end
117102
118103 _ensure_semantic_columns! (conn)
119- for stmt in SEMANTIC_INDEX_STATEMENTS
120- DBInterface. execute (conn, stmt)
104+
105+ # Create standard indices via Skein.jl (DB-3 Phase B)
106+ for col in [" descriptor_hash" , " quandle_key" , " crossing_number" , " determinant" ,
107+ " signature" , " colouring_count_3" , " colouring_count_5" ,
108+ " writhe" , " genus" , " quandle_generator_count" ]
109+ Skein. create_index! (conn, " quandle_semantic_index" , col)
121110 end
122111
123112 DBInterface. execute (conn,
@@ -756,7 +745,7 @@ function handle_semantic_equivalents(db::SkeinDB, sdb::SemanticIndexDB, name::St
756745 ))
757746end
758747
759- function handle_semantic_index (sdb :: SemanticIndexDB , params:: Dict{String, String} )
748+ function _build_semantic_query_sql ( params:: Dict{String, String} )
760749 crossing_number = parse_int_param (params, " crossing_number" )
761750 determinant_val = parse_int_param (params, " determinant" )
762751 signature_val = parse_int_param (params, " signature" )
@@ -809,10 +798,37 @@ function handle_semantic_index(sdb::SemanticIndexDB, params::Dict{String, String
809798 push! (args, limit)
810799 push! (args, offset)
811800
801+ return sql, args, limit, offset
802+ end
803+
804+ function handle_semantic_index (sdb:: SemanticIndexDB , params:: Dict{String, String} )
805+ sql, args, limit, offset = _build_semantic_query_sql (params)
812806 rows = [semantic_to_dict (row) for row in DBInterface. execute (sdb. conn, sql, args)]
813807 json_response (Dict (" semantic_index" => rows, " count" => length (rows), " limit" => limit, " offset" => offset))
814808end
815809
810+ function handle_explain (db:: SkeinDB , sdb:: SemanticIndexDB , params:: Dict{String, String} )
811+ endpoint = get (params, " endpoint" , nothing )
812+ if endpoint == " semantic"
813+ sql, args, limit, offset = _build_semantic_query_sql (params)
814+ plan = explain_query_plan (sdb. conn, sql, args)
815+ return json_response (Dict (" query_plan" => plan, " plan_summary" => plan_summary (plan)))
816+ end
817+
818+ raw_sql = get (params, " sql" , nothing )
819+ if ! isnothing (raw_sql)
820+ if ! is_select_only (raw_sql)
821+ return error_response (" Mutation prohibited. EXPLAIN is restricted to SELECT/WITH queries." ; status= 403 )
822+ end
823+ # Phase B: explain raw SQL using the DB connection (could be SkeinDB or SemanticIndexDB)
824+ # Assuming the raw SQL queries the main Skein database
825+ plan = explain_query_plan (db. conn, raw_sql, Any[])
826+ return json_response (Dict (" query_plan" => plan, " plan_summary" => plan_summary (plan)))
827+ end
828+
829+ error_response (" Missing 'endpoint=semantic' or 'sql=...' parameter" ; status= 400 )
830+ end
831+
816832function handle_krl_query (data:: DataProvider , sem:: SemProvider ,
817833 metrics:: QueryMetrics , req:: HTTP.Request )
818834 t0 = time ()
@@ -998,6 +1014,8 @@ function router(db::SkeinDB, sdb::SemanticIndexDB, static_dir::String,
9981014 ! isnothing (m_sem) &&
9991015 return handle_semantic_detail (db, sdb, m_sem. captures[1 ])
10001016
1017+ path == " /api/explain" && return handle_explain (db, sdb, params)
1018+
10011019 m_knot = match (r" ^/api/knots/(.+)$" , path)
10021020 ! isnothing (m_knot) &&
10031021 return handle_knot_detail (db, sdb, m_knot. captures[1 ])
0 commit comments