This guide provides comprehensive documentation for developers who want to integrate with, extend, or use the glazed help system's query capabilities programmatically.
The query system consists of several composed components:
User Query DSL β Parser β AST β Compiler β Predicates β Execution
β β β β β β
"examples" Lexer Expression SQL Gen Filters Results
pkg/help/
βββ dsl/ # DSL parser and compiler
β βββ lexer.go # Tokenization
β βββ parser.go # AST building
β βββ compiler.go # Predicate generation
β βββ dsl.go # Main API
βββ store/ # Storage backend (optional)
β βββ query.go # Store-specific predicates
β βββ store.go # SQLite implementation
βββ dsl_bridge.go # Integration section
βββ help.go # Core help system
βββ cobra.go # CLI integration
package main
import (
"context"
"fmt"
"github.com/go-go-golems/glazed/pkg/help"
)
func main() {
// Create help system
hs := help.NewHelpSystem()
// Load documentation
err := doc.AddDocToHelpSystem(hs)
if err != nil {
panic(err)
}
// Execute simple query
results, err := hs.QuerySections("examples")
if err != nil {
panic(err)
}
fmt.Printf("Found %d examples\n", len(results))
for _, section := range results {
fmt.Printf("- %s: %s\n", section.Slug, section.Title)
}
}
// Complex boolean queries
queries := []string{
"type:example AND topic:database",
"examples OR tutorials",
"NOT type:application",
"(examples OR tutorials) AND topic:templates",
}
for _, query := range queries {
results, err := hs.QuerySections(query)
if err != nil {
fmt.Printf("Query error: %s\n", err)
continue
}
fmt.Printf("Query: %s β %d results\n", query, len(results))
}
results, err := hs.QuerySections("invalid:syntax AND")
if err != nil {
// Handle different error types
switch {
case strings.Contains(err.Error(), "unknown field"):
fmt.Println("Invalid field name")
case strings.Contains(err.Error(), "syntax error"):
fmt.Println("Query syntax error")
default:
fmt.Printf("Query error: %s\n", err)
}
}
import "github.com/go-go-golems/glazed/pkg/help/dsl"
// Parse query to AST
expr, err := dsl.Parse("type:example AND topic:database")
if err != nil {
return err
}
// Compile to predicate (if using store backend)
predicate, err := dsl.ParseQuery("type:example AND topic:database")
if err != nil {
return err
}
// Use predicate with store
ctx := context.Background()
results, err := store.Find(ctx, predicate)
// Get DSL information
info := dsl.GetQueryInfo()
fmt.Printf("Valid fields: %v\n", info.ValidFields)
fmt.Printf("Valid types: %v\n", info.ValidTypes)
fmt.Printf("Valid shortcuts: %v\n", info.ValidShortcuts)
// Validate queries
validQueries := []string{
"examples",
"type:tutorial",
"topic:database AND examples",
}
for _, query := range validQueries {
if err := dsl.ValidateQuery(query); err != nil {
fmt.Printf("Invalid query '%s': %s\n", query, err)
} else {
fmt.Printf("Valid query: %s\n", query)
}
}
// Get debug information about a query
debugInfo, err := dsl.GetDebugInfo("(examples OR tutorials) AND NOT default:true")
if err != nil {
return err
}
fmt.Printf("Query: %s\n", debugInfo.Query)
fmt.Printf("AST: %s\n", debugInfo.AST)
fmt.Printf("SQL: %s\n", debugInfo.SQL)
fmt.Printf("Fields: %v\n", debugInfo.Fields)
Predicates are functions that filter help sections based on criteria:
type Predicate func(*Section) bool
// Example predicate implementations
func isExample(section *Section) bool {
return section.SectionType == SectionExample
}
func hasTopic(topic string) func(*Section) bool {
return func(section *Section) bool {
for _, t := range section.Topics {
if strings.EqualFold(t, topic) {
return true
}
}
return false
}
}
// Custom predicate for recent sections
func CreatedAfter(date time.Time) func(*Section) bool {
return func(section *Section) bool {
return section.CreatedAt.After(date)
}
}
// Custom predicate for content length
func MinContentLength(minLength int) func(*Section) bool {
return func(section *Section) bool {
return len(section.Content) >= minLength
}
}
// Usage
recentSections := filterSections(allSections, CreatedAfter(time.Now().AddDate(0, -1, 0)))
longSections := filterSections(allSections, MinContentLength(1000))
// Predicate combinators
func And(predicates ...func(*Section) bool) func(*Section) bool {
return func(section *Section) bool {
for _, pred := range predicates {
if !pred(section) {
return false
}
}
return true
}
}
func Or(predicates ...func(*Section) bool) func(*Section) bool {
return func(section *Section) bool {
for _, pred := range predicates {
if pred(section) {
return true
}
}
return false
}
}
func Not(predicate func(*Section) bool) func(*Section) bool {
return func(section *Section) bool {
return !predicate(section)
}
}
// Usage
complexPredicate := And(
isExample,
hasTopic("database"),
Not(func(s *Section) bool { return s.IsTopLevel }),
)
If using the SQLite store backend, queries compile to SQL:
import "github.com/go-go-golems/glazed/pkg/help/store"
// Create store
store, err := store.NewInMemory()
if err != nil {
return err
}
defer store.Close()
// Load sections
ctx := context.Background()
for _, section := range sections {
err := store.AddSection(ctx, section)
if err != nil {
return err
}
}
// Query with predicates
predicate := store.And(
store.IsType("example"),
store.HasTopic("database"),
store.Not(store.IsTopLevel()),
)
results, err := store.Find(ctx, predicate)
if err != nil {
return err
}
// Implement custom query compiler
type CustomCompiler struct {
filters []func(*Section) bool
}
func (c *CustomCompiler) AddFilter(filter func(*Section) bool) {
c.filters = append(c.filters, filter)
}
func (c *CustomCompiler) Execute(sections []*Section) []*Section {
var results []*Section
for _, section := range sections {
matches := true
for _, filter := range c.filters {
if !filter(section) {
matches = false
break
}
}
if matches {
results = append(results, section)
}
}
return results
}
// Usage
compiler := &CustomCompiler{}
compiler.AddFilter(isExample)
compiler.AddFilter(hasTopic("database"))
results := compiler.Execute(allSections)
// Extend Section with custom fields
type ExtendedSection struct {
*help.Section
Priority int `json:"priority"`
Tags []string `json:"tags"`
Metadata map[string]string `json:"metadata"`
}
// Custom field predicates
func HasPriority(priority int) func(*ExtendedSection) bool {
return func(section *ExtendedSection) bool {
return section.Priority >= priority
}
}
func HasTag(tag string) func(*ExtendedSection) bool {
return func(section *ExtendedSection) bool {
for _, t := range section.Tags {
if strings.EqualFold(t, tag) {
return true
}
}
return false
}
}
// Extend the DSL parser for custom syntax
type ExtendedParser struct {
*dsl.Parser
}
func (p *ExtendedParser) parseCustomExpression() (dsl.Expression, error) {
// Parse custom syntax like "priority:>=5" or "tag:important"
switch p.currentToken().Type {
case dsl.PRIORITY:
return p.parsePriorityExpression()
case dsl.TAG:
return p.parseTagExpression()
default:
return p.Parser.ParseExpression()
}
}
// Register custom field handlers
func init() {
dsl.RegisterField("priority", handlePriorityField)
dsl.RegisterField("tag", handleTagField)
}
// Implement custom storage backend
type CustomStore struct {
sections []*help.Section
index map[string][]*help.Section // field -> sections index
}
func (s *CustomStore) Find(ctx context.Context, pred store.Predicate) ([]*help.Section, error) {
// Custom query execution logic
var results []*help.Section
for _, section := range s.sections {
if s.evaluatePredicate(pred, section) {
results = append(results, section)
}
}
return results, nil
}
func (s *CustomStore) AddSection(ctx context.Context, section *help.Section) error {
s.sections = append(s.sections, section)
s.updateIndex(section)
return nil
}
// 1. Use indexed fields first
// Good: Fast field lookup first
"type:example AND \"complex search\""
// Less optimal: Text search first
"\"complex search\" AND type:example"
// 2. Limit result sets early
// Good: Narrow scope quickly
"type:example AND topic:specific"
// Less optimal: Broad scope
"\"common word\" OR type:example"
// 3. Use shortcuts when possible
// Good: Optimized shortcut
"examples"
// Verbose: Field query
"type:example"
type CachedHelpSystem struct {
*help.HelpSystem
queryCache map[string][]*help.Section
cacheMutex sync.RWMutex
}
func (chs *CachedHelpSystem) QuerySections(query string) ([]*help.Section, error) {
chs.cacheMutex.RLock()
if results, found := chs.queryCache[query]; found {
chs.cacheMutex.RUnlock()
return results, nil
}
chs.cacheMutex.RUnlock()
// Execute query
results, err := chs.HelpSystem.QuerySections(query)
if err != nil {
return nil, err
}
// Cache results
chs.cacheMutex.Lock()
chs.queryCache[query] = results
chs.cacheMutex.Unlock()
return results, nil
}
type IndexedHelpSystem struct {
sections []*help.Section
// Pre-built indexes
typeIndex map[help.SectionType][]*help.Section
topicIndex map[string][]*help.Section
commandIndex map[string][]*help.Section
flagIndex map[string][]*help.Section
}
func (ihs *IndexedHelpSystem) buildIndexes() {
ihs.typeIndex = make(map[help.SectionType][]*help.Section)
ihs.topicIndex = make(map[string][]*help.Section)
ihs.commandIndex = make(map[string][]*help.Section)
ihs.flagIndex = make(map[string][]*help.Section)
for _, section := range ihs.sections {
// Index by type
ihs.typeIndex[section.SectionType] = append(
ihs.typeIndex[section.SectionType], section)
// Index by topics
for _, topic := range section.Topics {
ihs.topicIndex[topic] = append(ihs.topicIndex[topic], section)
}
// Index by commands and flags
for _, command := range section.Commands {
ihs.commandIndex[command] = append(ihs.commandIndex[command], section)
}
for _, flag := range section.Flags {
ihs.flagIndex[flag] = append(ihs.flagIndex[flag], section)
}
}
}
func setupQueryAPI(router *mux.Router, hs *help.HelpSystem) {
router.HandleFunc("/api/help/search", func(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("q")
if query == "" {
http.Error(w, "Missing query field", http.StatusBadRequest)
return
}
results, err := hs.QuerySections(query)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
response := struct {
Query string `json:"query"`
Results []*help.Section `json:"results"`
Count int `json:"count"`
}{
Query: query,
Results: results,
Count: len(results),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
})
}
type Resolver struct {
helpSystem *help.HelpSystem
}
func (r *Resolver) SearchHelp(ctx context.Context, args struct {
Query string
Limit *int32
}) ([]*help.Section, error) {
results, err := r.helpSystem.QuerySections(args.Query)
if err != nil {
return nil, err
}
if args.Limit != nil && len(results) > int(*args.Limit) {
results = results[:*args.Limit]
}
return results, nil
}
// GraphQL schema
const schema = `
type Section {
slug: String!
title: String!
content: String!
sectionType: String!
topics: [String!]!
commands: [String!]!
flags: [String!]!
}
type Query {
searchHelp(query: String!, limit: Int): [Section!]!
}
`
func CreateQueryCommand(hs *help.HelpSystem) *cobra.Command {
cmd := &cobra.Command{
Use: "query [query]",
Short: "Search help sections using query DSL",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
query := args[0]
// Get flags
format, _ := cmd.Flags().GetString("format")
printQuery, _ := cmd.Flags().GetBool("print-query")
printSQL, _ := cmd.Flags().GetBool("print-sql")
// Debug output
if printQuery {
ast, err := dsl.ParseToAST(query)
if err != nil {
return err
}
fmt.Printf("AST: %s\n", ast.String())
}
// Execute query
results, err := hs.QuerySections(query)
if err != nil {
return err
}
// Format output
switch format {
case "json":
return json.NewEncoder(os.Stdout).Encode(results)
case "table":
return printTable(results)
default:
return printList(results)
}
},
}
cmd.Flags().String("format", "list", "Output format: list, table, json")
cmd.Flags().Bool("print-query", false, "Print parsed query AST")
cmd.Flags().Bool("print-sql", false, "Print generated SQL")
return cmd
}
func analyzeQuery(query string) {
fmt.Printf("Analyzing query: %s\n", query)
// Parse to AST
ast, err := dsl.ParseToAST(query)
if err != nil {
fmt.Printf("Parse error: %s\n", err)
return
}
fmt.Printf("AST structure:\n%s\n", ast.String())
// Analyze complexity
complexity := ast.Complexity()
fmt.Printf("Query complexity: %d\n", complexity)
// Estimate performance
if complexity > 10 {
fmt.Println("Warning: High complexity query, consider optimization")
}
// Show field usage
fields := ast.GetUsedFields()
fmt.Printf("Fields used: %v\n", fields)
}
func profileQuery(hs *help.HelpSystem, query string) {
start := time.Now()
results, err := hs.QuerySections(query)
duration := time.Since(start)
if err != nil {
fmt.Printf("Query failed after %v: %s\n", duration, err)
return
}
fmt.Printf("Query completed in %v\n", duration)
fmt.Printf("Results: %d sections\n", len(results))
fmt.Printf("Performance: %.2f sections/ms\n",
float64(len(results))/float64(duration.Nanoseconds())*1e6)
// Performance warnings
if duration > 100*time.Millisecond {
fmt.Println("Warning: Slow query detected")
}
}
func robustQuery(hs *help.HelpSystem, query string) ([]*help.Section, error) {
results, err := hs.QuerySections(query)
if err != nil {
// Try to recover from common errors
if strings.Contains(err.Error(), "unknown field") {
// Suggest similar fields
suggestions := suggestFields(query)
return nil, fmt.Errorf("%s. Did you mean: %v", err, suggestions)
}
if strings.Contains(err.Error(), "syntax error") {
// Try simplified query
simplified := simplifyQuery(query)
if simplified != query {
fmt.Printf("Trying simplified query: %s\n", simplified)
return hs.QuerySections(simplified)
}
}
return nil, err
}
return results, nil
}
// Main help system interface
type HelpSystem interface {
QuerySections(query string) ([]*Section, error)
AddSection(section *Section)
GetSectionWithSlug(slug string) (*Section, error)
}
// DSL parser interface
type Parser interface {
Parse(query string) (Expression, error)
ParseToAST(query string) (*AST, error)
Validate(query string) error
}
// Query compiler interface
type Compiler interface {
Compile(expr Expression) (Predicate, error)
CompileToSQL(expr Expression) (string, []interface{}, error)
}
// DSL package
func Parse(query string) (Expression, error)
func ParseQuery(query string) (store.Predicate, error)
func GetQueryInfo() *QueryInfo
func ValidateQuery(query string) error
// Help system
func NewHelpSystem() *HelpSystem
func (hs *HelpSystem) QuerySections(query string) ([]*Section, error)
// Store backend (optional)
func NewInMemoryStore() (*Store, error)
func (s *Store) Find(ctx context.Context, pred Predicate) ([]*Section, error)
type QueryConfig struct {
MaxResults int // Maximum results to return
Timeout time.Duration // Query timeout
CacheEnabled bool // Enable result caching
CacheTTL time.Duration // Cache time-to-live
DebugMode bool // Enable debug output
IndexingEnabled bool // Enable field indexing
}
func ConfigureQuery(hs *HelpSystem, config QueryConfig) {
// Apply configuration to help system
}
type QueryError struct {
Type ErrorType
Message string
Query string
Position int
}
type ErrorType int
const (
SyntaxError ErrorType = iota
UnknownFieldError
InvalidValueError
TimeoutError
InternalError
)
func TestQuerySystem(t *testing.T) {
hs := createTestHelpSystem()
testCases := []struct {
query string
expected int
wantErr bool
}{
{"examples", 5, false},
{"type:tutorial", 3, false},
{"invalid:field", 0, true},
{"(examples OR tutorials) AND topic:database", 2, false},
}
for _, tc := range testCases {
results, err := hs.QuerySections(tc.query)
if tc.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Len(t, results, tc.expected)
}
}
}
This guide provides comprehensive coverage of the query system's capabilities for developers. Use it as a reference for building applications that leverage the powerful help system query functionality.