package gql
import (
"errors"
"fmt"
"strings"
"time"
chickpeas "github.com/freeeve/gochickpeas"
"github.com/freeeve/gochickpeas/gql/internal/ast"
"github.com/freeeve/gochickpeas/gql/internal/eval"
"github.com/freeeve/gochickpeas/gql/internal/exec"
"github.com/freeeve/gochickpeas/gql/internal/explain"
"github.com/freeeve/gochickpeas/gql/internal/graph"
"github.com/freeeve/gochickpeas/gql/internal/parser"
"github.com/freeeve/gochickpeas/gql/internal/plan"
"github.com/freeeve/gochickpeas/gql/internal/semantics"
"github.com/freeeve/gochickpeas/gql/value"
)
func Run (g *chickpeas .Snapshot , query string ) (*Rows , error ) {
return RunWithParams (g , query , nil )
}
func RunWithParams (g *chickpeas .Snapshot , query string , params map [string ]value .Value ) (*Rows , error ) {
return DefaultCacheFor (g ).RunWithParams (g , query , params )
}
func RunUncached (g *chickpeas .Snapshot , query string ) (*Rows , error ) {
return RunUncachedWithParams (g , query , nil )
}
func RunUncachedWithParams (g *chickpeas .Snapshot , query string , params map [string ]value .Value ) (*Rows , error ) {
q , gr , p , planTime , err := prepare (g , query )
if err != nil {
return nil , err
}
ctx := &eval .Ctx {G : gr , Named : params , ForceInterp : forceInterp }
return execPlan (gr , p , q .Mode , planTime , ctx )
}
func execPlan(gr *graph .SnapshotGraph , p *plan .Plan , mode ast .QueryMode , planTime time .Duration , ctx *eval .Ctx ) (*Rows , error ) {
switch mode {
case ast .Explain :
return renderPlanRows (gr , p , nil , planTime ), nil
case ast .Profile :
return renderPlanRows (gr , p , exec .ExecuteProfiled (ctx , p ), planTime ), nil
}
rows , err := exec .Execute (ctx , p )
if err != nil {
return nil , wrapStage (err )
}
return newRows (p .Columns , rows ), nil
}
func Explain (g *chickpeas .Snapshot , query string ) (string , error ) {
_ , gr , p , planTime , err := prepare (g , query )
if err != nil {
return "" , err
}
return strings .Join (explain .Render (p , nil , planTime , plan .Estimate (p , gr )), "\n" ), nil
}
func ExplainCanonical (g *chickpeas .Snapshot , query string ) (string , error ) {
_ , gr , p , _ , err := prepare (g , query )
if err != nil {
return "" , err
}
return strings .Join (explain .Canonical (p , plan .Estimate (p , gr )), "\n" ), nil
}
func prepare(g *chickpeas .Snapshot , query string ) (*ast .Query , *graph .SnapshotGraph , *plan .Plan , time .Duration , error ) {
start := time .Now ()
q , err := parseDesugar (query )
if err != nil {
return nil , nil , nil , 0 , err
}
gr := graph .New (g )
p , err := plan .Build (q , gr )
if err != nil {
return nil , nil , nil , 0 , wrapStage (err )
}
return q , gr , p , time .Since (start ), nil
}
func parseDesugar(query string ) (*ast .Query , error ) {
q , err := parser .Parse (query )
if err != nil {
return nil , fmt .Errorf ("%s: %w" , err .Error(), ErrParse )
}
if err := semantics .Desugar (q ); err != nil {
return nil , wrapStage (err )
}
return q , nil
}
func renderPlanRows(gr graph .Graph , p *plan .Plan , prof *explain .Profile , planTime time .Duration ) *Rows {
lines := explain .Render (p , prof , planTime , plan .Estimate (p , gr ))
rows := make ([][]value .Value , len (lines ))
for i , l := range lines {
rows [i ] = []value .Value {value .Str (l )}
}
return newRows ([]string {"plan" }, rows )
}
var forceInterp bool
func wrapStage(err error ) error {
var se *semantics .Error
if errors .As (err , &se ) {
switch se .Kind {
case semantics .KindBind :
return fmt .Errorf ("%s: %w" , se .Msg , ErrBind )
default :
return fmt .Errorf ("%s: %w" , se .Msg , ErrPlan )
}
}
return err
}
The pages are generated with Golds v0.8.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .