Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 83 additions & 2 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,10 @@ type CreateQuery struct {
CreateDatabase bool `json:"create_database,omitempty"`
CreateFunction bool `json:"create_function,omitempty"`
CreateUser bool `json:"create_user,omitempty"`
CreateDictionary bool `json:"create_dictionary,omitempty"`
FunctionName string `json:"function_name,omitempty"`
CreateDictionary bool `json:"create_dictionary,omitempty"`
DictionaryAttrs []*DictionaryAttributeDeclaration `json:"dictionary_attrs,omitempty"`
DictionaryDef *DictionaryDefinition `json:"dictionary_def,omitempty"`
FunctionName string `json:"function_name,omitempty"`
FunctionBody Expression `json:"function_body,omitempty"`
UserName string `json:"user_name,omitempty"`
}
Expand All @@ -295,6 +297,85 @@ type ColumnDeclaration struct {
func (c *ColumnDeclaration) Pos() token.Position { return c.Position }
func (c *ColumnDeclaration) End() token.Position { return c.Position }

// DictionaryAttributeDeclaration represents a dictionary attribute definition.
type DictionaryAttributeDeclaration struct {
Position token.Position `json:"-"`
Name string `json:"name"`
Type *DataType `json:"type"`
Default Expression `json:"default,omitempty"`
Expression Expression `json:"expression,omitempty"` // EXPRESSION clause
Hierarchical bool `json:"hierarchical,omitempty"` // HIERARCHICAL flag
Injective bool `json:"injective,omitempty"` // INJECTIVE flag
IsObjectID bool `json:"is_object_id,omitempty"` // IS_OBJECT_ID flag
}

func (d *DictionaryAttributeDeclaration) Pos() token.Position { return d.Position }
func (d *DictionaryAttributeDeclaration) End() token.Position { return d.Position }

// DictionaryDefinition represents the definition part of a dictionary (PRIMARY KEY, SOURCE, LIFETIME, LAYOUT).
type DictionaryDefinition struct {
Position token.Position `json:"-"`
PrimaryKey []Expression `json:"primary_key,omitempty"`
Source *DictionarySource `json:"source,omitempty"`
Lifetime *DictionaryLifetime `json:"lifetime,omitempty"`
Layout *DictionaryLayout `json:"layout,omitempty"`
Range *DictionaryRange `json:"range,omitempty"`
Settings []*SettingExpr `json:"settings,omitempty"`
}

func (d *DictionaryDefinition) Pos() token.Position { return d.Position }
func (d *DictionaryDefinition) End() token.Position { return d.Position }

// DictionarySource represents the SOURCE clause of a dictionary.
type DictionarySource struct {
Position token.Position `json:"-"`
Type string `json:"type"` // e.g., "CLICKHOUSE", "MYSQL", "FILE"
Args []*KeyValuePair `json:"args,omitempty"`
}

func (d *DictionarySource) Pos() token.Position { return d.Position }
func (d *DictionarySource) End() token.Position { return d.Position }

// KeyValuePair represents a key-value pair in dictionary source or other contexts.
type KeyValuePair struct {
Position token.Position `json:"-"`
Key string `json:"key"`
Value Expression `json:"value"`
}

func (k *KeyValuePair) Pos() token.Position { return k.Position }
func (k *KeyValuePair) End() token.Position { return k.Position }

// DictionaryLifetime represents the LIFETIME clause of a dictionary.
type DictionaryLifetime struct {
Position token.Position `json:"-"`
Min Expression `json:"min,omitempty"`
Max Expression `json:"max,omitempty"`
}

func (d *DictionaryLifetime) Pos() token.Position { return d.Position }
func (d *DictionaryLifetime) End() token.Position { return d.Position }

// DictionaryLayout represents the LAYOUT clause of a dictionary.
type DictionaryLayout struct {
Position token.Position `json:"-"`
Type string `json:"type"` // e.g., "FLAT", "HASHED", "COMPLEX_KEY_HASHED"
Args []*KeyValuePair `json:"args,omitempty"`
}

func (d *DictionaryLayout) Pos() token.Position { return d.Position }
func (d *DictionaryLayout) End() token.Position { return d.Position }

// DictionaryRange represents the RANGE clause of a dictionary.
type DictionaryRange struct {
Position token.Position `json:"-"`
Min Expression `json:"min,omitempty"`
Max Expression `json:"max,omitempty"`
}

func (d *DictionaryRange) Pos() token.Position { return d.Position }
func (d *DictionaryRange) End() token.Position { return d.Position }

// DataType represents a data type.
type DataType struct {
Position token.Position `json:"-"`
Expand Down
175 changes: 175 additions & 0 deletions internal/explain/dictionary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package explain

import (
"fmt"
"strings"

"github.com/sqlc-dev/doubleclick/ast"
)

// explainDictionaryAttributeDeclaration outputs a dictionary attribute declaration.
func explainDictionaryAttributeDeclaration(sb *strings.Builder, n *ast.DictionaryAttributeDeclaration, indent string, depth int) {
children := 0
if n.Type != nil {
children++
}
if n.Default != nil {
children++
}
if n.Expression != nil {
children++
}
if children > 0 {
fmt.Fprintf(sb, "%sDictionaryAttributeDeclaration %s (children %d)\n", indent, n.Name, children)
} else {
fmt.Fprintf(sb, "%sDictionaryAttributeDeclaration %s\n", indent, n.Name)
}
if n.Type != nil {
Node(sb, n.Type, depth+1)
}
if n.Default != nil {
Node(sb, n.Default, depth+1)
}
if n.Expression != nil {
Node(sb, n.Expression, depth+1)
}
}

// explainDictionaryDefinition outputs a dictionary definition section.
func explainDictionaryDefinition(sb *strings.Builder, n *ast.DictionaryDefinition, indent string, depth int) {
children := 0
if len(n.PrimaryKey) > 0 {
children++
}
if n.Source != nil {
children++
}
if n.Lifetime != nil {
children++
}
if n.Layout != nil {
children++
}
if n.Range != nil {
children++
}
if len(n.Settings) > 0 {
children++
}
if children > 0 {
fmt.Fprintf(sb, "%sDictionary definition (children %d)\n", indent, children)
} else {
fmt.Fprintf(sb, "%sDictionary definition\n", indent)
}

// PRIMARY KEY
if len(n.PrimaryKey) > 0 {
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(n.PrimaryKey))
for _, pk := range n.PrimaryKey {
Node(sb, pk, depth+2)
}
}

// SOURCE
if n.Source != nil {
explainDictionarySource(sb, n.Source, indent+" ", depth+1)
}

// LIFETIME
if n.Lifetime != nil {
explainDictionaryLifetime(sb, n.Lifetime, indent+" ", depth+1)
}

// RANGE (if present, comes before LAYOUT)
if n.Range != nil {
explainDictionaryRange(sb, n.Range, indent+" ", depth+1)
}

// LAYOUT
if n.Layout != nil {
explainDictionaryLayout(sb, n.Layout, indent+" ", depth+1)
}

// SETTINGS
if len(n.Settings) > 0 {
fmt.Fprintf(sb, "%s Set\n", indent)
}
}

// explainDictionarySource outputs a dictionary SOURCE clause.
func explainDictionarySource(sb *strings.Builder, n *ast.DictionarySource, indent string, depth int) {
// FunctionWithKeyValueArguments has extra space before name
children := 0
if len(n.Args) > 0 {
children = 1
}
if children > 0 {
fmt.Fprintf(sb, "%sFunctionWithKeyValueArguments %s (children %d)\n", indent, strings.ToLower(n.Type), children)
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(n.Args))
for _, arg := range n.Args {
explainKeyValuePair(sb, arg, indent+" ", depth+2)
}
} else {
fmt.Fprintf(sb, "%sFunctionWithKeyValueArguments %s\n", indent, strings.ToLower(n.Type))
}
}

// explainKeyValuePair outputs a key-value pair (lowercase "pair").
func explainKeyValuePair(sb *strings.Builder, n *ast.KeyValuePair, indent string, depth int) {
children := 0
if n.Value != nil {
children = 1
}
if children > 0 {
fmt.Fprintf(sb, "%spair (children %d)\n", indent, children)
Node(sb, n.Value, depth+1)
} else {
fmt.Fprintf(sb, "%spair\n", indent)
}
}

// explainDictionaryLifetime outputs a dictionary LIFETIME clause.
func explainDictionaryLifetime(sb *strings.Builder, n *ast.DictionaryLifetime, indent string, depth int) {
// LIFETIME is output as "Dictionary lifetime" without children count typically
fmt.Fprintf(sb, "%sDictionary lifetime\n", indent)
}

// explainDictionaryLayout outputs a dictionary LAYOUT clause.
func explainDictionaryLayout(sb *strings.Builder, n *ast.DictionaryLayout, indent string, depth int) {
children := 0
if len(n.Args) > 0 {
children = 1
}
if children > 0 {
fmt.Fprintf(sb, "%sDictionary layout (children %d)\n", indent, children)
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(n.Args))
for _, arg := range n.Args {
explainKeyValuePair(sb, arg, indent+" ", depth+2)
}
} else {
fmt.Fprintf(sb, "%sDictionary layout (children 1)\n", indent)
fmt.Fprintf(sb, "%s ExpressionList\n", indent)
}
}

// explainDictionaryRange outputs a dictionary RANGE clause.
func explainDictionaryRange(sb *strings.Builder, n *ast.DictionaryRange, indent string, depth int) {
children := 0
if n.Min != nil {
children++
}
if n.Max != nil {
children++
}
if children > 0 {
fmt.Fprintf(sb, "%sDictionary range (children %d)\n", indent, children)
if n.Min != nil {
Node(sb, n.Min, depth+1)
}
if n.Max != nil {
Node(sb, n.Max, depth+1)
}
} else {
fmt.Fprintf(sb, "%sDictionary range\n", indent)
}
}
16 changes: 16 additions & 0 deletions internal/explain/explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,22 @@ func Node(sb *strings.Builder, node interface{}, depth int) {
case *ast.Parameter:
explainParameter(sb, n, indent)

// Dictionary types
case *ast.DictionaryAttributeDeclaration:
explainDictionaryAttributeDeclaration(sb, n, indent, depth)
case *ast.DictionaryDefinition:
explainDictionaryDefinition(sb, n, indent, depth)
case *ast.DictionarySource:
explainDictionarySource(sb, n, indent, depth)
case *ast.KeyValuePair:
explainKeyValuePair(sb, n, indent, depth)
case *ast.DictionaryLifetime:
explainDictionaryLifetime(sb, n, indent, depth)
case *ast.DictionaryLayout:
explainDictionaryLayout(sb, n, indent, depth)
case *ast.DictionaryRange:
explainDictionaryRange(sb, n, indent, depth)

default:
// For unhandled types, just print the type name
fmt.Fprintf(sb, "%s%T\n", indent, node)
Expand Down
21 changes: 20 additions & 1 deletion internal/explain/statements.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,27 @@ func explainCreateQuery(sb *strings.Builder, n *ast.CreateQuery, indent string,
return
}
if n.CreateDictionary {
fmt.Fprintf(sb, "%sCreateDictionaryQuery %s (children 1)\n", indent, n.Table)
// Dictionary: count children = identifier + attributes (if any) + definition (if any)
children := 1 // identifier
if len(n.DictionaryAttrs) > 0 {
children++
}
if n.DictionaryDef != nil {
children++
}
fmt.Fprintf(sb, "%sCreateQuery %s (children %d)\n", indent, n.Table, children)
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Table)
// Dictionary attributes
if len(n.DictionaryAttrs) > 0 {
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(n.DictionaryAttrs))
for _, attr := range n.DictionaryAttrs {
explainDictionaryAttributeDeclaration(sb, attr, indent+" ", depth+2)
}
}
// Dictionary definition
if n.DictionaryDef != nil {
explainDictionaryDefinition(sb, n.DictionaryDef, indent+" ", depth+1)
}
return
}

Expand Down
Loading