| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- package analyzer
- import "strings"
- // Type represents a SQL type with SQLite affinity rules.
- type Type int
- const (
- TypeUnknown Type = iota // Unresolved type
- TypeNull // NULL value
- TypeInteger // INTEGER affinity
- TypeReal // REAL affinity
- TypeText // TEXT affinity
- TypeBlob // BLOB affinity
- TypeNumeric // NUMERIC affinity (flexible)
- TypeBoolean // Boolean (stored as INTEGER in SQLite)
- TypeAny // Any type (for polymorphic functions)
- )
- func (t Type) String() string {
- switch t {
- case TypeUnknown:
- return "UNKNOWN"
- case TypeNull:
- return "NULL"
- case TypeInteger:
- return "INTEGER"
- case TypeReal:
- return "REAL"
- case TypeText:
- return "TEXT"
- case TypeBlob:
- return "BLOB"
- case TypeNumeric:
- return "NUMERIC"
- case TypeBoolean:
- return "BOOLEAN"
- case TypeAny:
- return "ANY"
- default:
- return "UNKNOWN"
- }
- }
- // TypeFromName returns the Type for a SQL type name using SQLite affinity rules.
- // See: https://www.sqlite.org/datatype3.html
- func TypeFromName(name string) Type {
- upper := strings.ToUpper(name)
- // Rule 1: If the type contains "INT" -> INTEGER
- if strings.Contains(upper, "INT") {
- return TypeInteger
- }
- // Rule 2: If the type contains "CHAR", "CLOB", or "TEXT" -> TEXT
- if strings.Contains(upper, "CHAR") ||
- strings.Contains(upper, "CLOB") ||
- strings.Contains(upper, "TEXT") {
- return TypeText
- }
- // Rule 3: If the type contains "BLOB" or is empty -> BLOB
- if strings.Contains(upper, "BLOB") || upper == "" {
- return TypeBlob
- }
- // Rule 4: If the type contains "REAL", "FLOA", or "DOUB" -> REAL
- if strings.Contains(upper, "REAL") ||
- strings.Contains(upper, "FLOA") ||
- strings.Contains(upper, "DOUB") {
- return TypeReal
- }
- // Rule 5: Otherwise -> NUMERIC
- // This includes NUMERIC, DECIMAL, BOOLEAN, DATE, DATETIME
- switch upper {
- case "BOOLEAN", "BOOL":
- return TypeBoolean
- default:
- return TypeNumeric
- }
- }
- // IsNumeric returns true if the type can hold numeric values.
- func (t Type) IsNumeric() bool {
- switch t {
- case TypeInteger, TypeReal, TypeNumeric, TypeBoolean:
- return true
- default:
- return false
- }
- }
- // IsComparable returns true if two types can be compared.
- func (t Type) IsComparable(other Type) bool {
- // NULL is comparable to anything
- if t == TypeNull || other == TypeNull {
- return true
- }
- // ANY matches anything
- if t == TypeAny || other == TypeAny {
- return true
- }
- // Same type
- if t == other {
- return true
- }
- // Numeric types are inter-comparable
- if t.IsNumeric() && other.IsNumeric() {
- return true
- }
- // TEXT and BLOB can be compared
- if (t == TypeText || t == TypeBlob) && (other == TypeText || other == TypeBlob) {
- return true
- }
- // NUMERIC/BOOLEAN accepts TEXT (SQLite-compatible: dates stored as text in numeric columns)
- if (t == TypeNumeric || t == TypeBoolean) && (other == TypeText || other == TypeBlob) {
- return true
- }
- if (other == TypeNumeric || other == TypeBoolean) && (t == TypeText || t == TypeBlob) {
- return true
- }
- return false
- }
- // CommonType returns the common type for binary operations.
- func CommonType(a, b Type) Type {
- if a == TypeUnknown {
- return b
- }
- if b == TypeUnknown {
- return a
- }
- if a == TypeNull {
- return b
- }
- if b == TypeNull {
- return a
- }
- if a == TypeAny {
- return b
- }
- if b == TypeAny {
- return a
- }
- if a == b {
- return a
- }
- // Numeric promotion
- if a.IsNumeric() && b.IsNumeric() {
- if a == TypeReal || b == TypeReal {
- return TypeReal
- }
- if a == TypeNumeric || b == TypeNumeric {
- return TypeNumeric
- }
- return TypeInteger
- }
- // Text/Blob coercion
- if (a == TypeText || a == TypeBlob) && (b == TypeText || b == TypeBlob) {
- return TypeText
- }
- return TypeText // Default to TEXT for mixed types
- }
- // FunctionSignature describes a SQL function.
- type FunctionSignature struct {
- Name string
- MinArgs int
- MaxArgs int // -1 for variadic
- ArgTypes []Type // Expected argument types (TypeAny for flexible)
- ReturnType Type
- IsAggregate bool
- }
- // builtinFunctions contains all built-in SQL functions.
- var builtinFunctions = map[string]FunctionSignature{
- // Aggregate functions
- "COUNT": {Name: "COUNT", MinArgs: 0, MaxArgs: 1, ArgTypes: []Type{TypeAny}, ReturnType: TypeInteger, IsAggregate: true},
- "SUM": {Name: "SUM", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeNumeric}, ReturnType: TypeNumeric, IsAggregate: true},
- "AVG": {Name: "AVG", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeNumeric}, ReturnType: TypeReal, IsAggregate: true},
- "MIN": {Name: "MIN", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeAny}, ReturnType: TypeAny, IsAggregate: true},
- "MAX": {Name: "MAX", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeAny}, ReturnType: TypeAny, IsAggregate: true},
- "TOTAL": {Name: "TOTAL", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeNumeric}, ReturnType: TypeReal, IsAggregate: true},
- "GROUP_CONCAT": {Name: "GROUP_CONCAT", MinArgs: 1, MaxArgs: 2, ArgTypes: []Type{TypeAny, TypeText}, ReturnType: TypeText, IsAggregate: true},
- // String functions
- "LENGTH": {Name: "LENGTH", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeText}, ReturnType: TypeInteger, IsAggregate: false},
- "UPPER": {Name: "UPPER", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeText}, ReturnType: TypeText, IsAggregate: false},
- "LOWER": {Name: "LOWER", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeText}, ReturnType: TypeText, IsAggregate: false},
- "TRIM": {Name: "TRIM", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeText}, ReturnType: TypeText, IsAggregate: false},
- "LTRIM": {Name: "LTRIM", MinArgs: 1, MaxArgs: 2, ArgTypes: []Type{TypeText, TypeText}, ReturnType: TypeText, IsAggregate: false},
- "RTRIM": {Name: "RTRIM", MinArgs: 1, MaxArgs: 2, ArgTypes: []Type{TypeText, TypeText}, ReturnType: TypeText, IsAggregate: false},
- "SUBSTR": {Name: "SUBSTR", MinArgs: 2, MaxArgs: 3, ArgTypes: []Type{TypeText, TypeInteger, TypeInteger}, ReturnType: TypeText, IsAggregate: false},
- "REPLACE": {Name: "REPLACE", MinArgs: 3, MaxArgs: 3, ArgTypes: []Type{TypeText, TypeText, TypeText}, ReturnType: TypeText, IsAggregate: false},
- "INSTR": {Name: "INSTR", MinArgs: 2, MaxArgs: 2, ArgTypes: []Type{TypeText, TypeText}, ReturnType: TypeInteger, IsAggregate: false},
- "PRINTF": {Name: "PRINTF", MinArgs: 1, MaxArgs: -1, ArgTypes: []Type{TypeText}, ReturnType: TypeText, IsAggregate: false},
- "CONCAT": {Name: "CONCAT", MinArgs: 1, MaxArgs: -1, ArgTypes: []Type{TypeAny}, ReturnType: TypeText, IsAggregate: false},
- // Numeric functions
- "ABS": {Name: "ABS", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeNumeric}, ReturnType: TypeNumeric, IsAggregate: false},
- "ROUND": {Name: "ROUND", MinArgs: 1, MaxArgs: 2, ArgTypes: []Type{TypeNumeric, TypeInteger}, ReturnType: TypeNumeric, IsAggregate: false},
- "CEIL": {Name: "CEIL", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeNumeric}, ReturnType: TypeInteger, IsAggregate: false},
- "FLOOR": {Name: "FLOOR", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeNumeric}, ReturnType: TypeInteger, IsAggregate: false},
- "MOD": {Name: "MOD", MinArgs: 2, MaxArgs: 2, ArgTypes: []Type{TypeInteger, TypeInteger}, ReturnType: TypeInteger, IsAggregate: false},
- "RANDOM": {Name: "RANDOM", MinArgs: 0, MaxArgs: 0, ArgTypes: []Type{}, ReturnType: TypeInteger, IsAggregate: false},
- // Null handling
- "COALESCE": {Name: "COALESCE", MinArgs: 1, MaxArgs: -1, ArgTypes: []Type{TypeAny}, ReturnType: TypeAny, IsAggregate: false},
- "NULLIF": {Name: "NULLIF", MinArgs: 2, MaxArgs: 2, ArgTypes: []Type{TypeAny, TypeAny}, ReturnType: TypeAny, IsAggregate: false},
- "IFNULL": {Name: "IFNULL", MinArgs: 2, MaxArgs: 2, ArgTypes: []Type{TypeAny, TypeAny}, ReturnType: TypeAny, IsAggregate: false},
- "IIF": {Name: "IIF", MinArgs: 3, MaxArgs: 3, ArgTypes: []Type{TypeBoolean, TypeAny, TypeAny}, ReturnType: TypeAny, IsAggregate: false},
- // Type functions
- "TYPEOF": {Name: "TYPEOF", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeAny}, ReturnType: TypeText, IsAggregate: false},
- "CAST": {Name: "CAST", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeAny}, ReturnType: TypeAny, IsAggregate: false},
- // Date/Time functions
- "DATE": {Name: "DATE", MinArgs: 0, MaxArgs: -1, ArgTypes: []Type{TypeAny}, ReturnType: TypeText, IsAggregate: false},
- "TIME": {Name: "TIME", MinArgs: 0, MaxArgs: -1, ArgTypes: []Type{TypeAny}, ReturnType: TypeText, IsAggregate: false},
- "DATETIME": {Name: "DATETIME", MinArgs: 0, MaxArgs: -1, ArgTypes: []Type{TypeAny}, ReturnType: TypeText, IsAggregate: false},
- "JULIANDAY": {Name: "JULIANDAY", MinArgs: 0, MaxArgs: -1, ArgTypes: []Type{TypeAny}, ReturnType: TypeReal, IsAggregate: false},
- "UNIXEPOCH": {Name: "UNIXEPOCH", MinArgs: 0, MaxArgs: -1, ArgTypes: []Type{TypeAny}, ReturnType: TypeInteger, IsAggregate: false},
- "STRFTIME": {Name: "STRFTIME", MinArgs: 1, MaxArgs: -1, ArgTypes: []Type{TypeText, TypeAny}, ReturnType: TypeText, IsAggregate: false},
- "TIMEDIFF": {Name: "TIMEDIFF", MinArgs: 2, MaxArgs: 2, ArgTypes: []Type{TypeAny, TypeAny}, ReturnType: TypeText, IsAggregate: false},
- // SQLite specific
- "SQLITE_VERSION": {Name: "SQLITE_VERSION", MinArgs: 0, MaxArgs: 0, ArgTypes: []Type{}, ReturnType: TypeText, IsAggregate: false},
- "LAST_INSERT_ROWID": {Name: "LAST_INSERT_ROWID", MinArgs: 0, MaxArgs: 0, ArgTypes: []Type{}, ReturnType: TypeInteger, IsAggregate: false},
- "CHANGES": {Name: "CHANGES", MinArgs: 0, MaxArgs: 0, ArgTypes: []Type{}, ReturnType: TypeInteger, IsAggregate: false},
- "TOTAL_CHANGES": {Name: "TOTAL_CHANGES", MinArgs: 0, MaxArgs: 0, ArgTypes: []Type{}, ReturnType: TypeInteger, IsAggregate: false},
- // Other
- "HEX": {Name: "HEX", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeBlob}, ReturnType: TypeText, IsAggregate: false},
- "UNHEX": {Name: "UNHEX", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeText}, ReturnType: TypeBlob, IsAggregate: false},
- "ZEROBLOB": {Name: "ZEROBLOB", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeInteger}, ReturnType: TypeBlob, IsAggregate: false},
- "QUOTE": {Name: "QUOTE", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeAny}, ReturnType: TypeText, IsAggregate: false},
- }
- // LookupFunction returns the function signature for a function name.
- func LookupFunction(name string) (FunctionSignature, bool) {
- sig, ok := builtinFunctions[strings.ToUpper(name)]
- return sig, ok
- }
- // IsAggregateFunction returns true if the function is an aggregate.
- func IsAggregateFunction(name string) bool {
- sig, ok := LookupFunction(name)
- return ok && sig.IsAggregate
- }
- // ColumnInfo describes a column in a table.
- type ColumnInfo struct {
- Name string
- Type Type
- Nullable bool
- PrimaryKey bool
- Default interface{}
- TableName string // For qualified references
- }
- // TableInfo describes a table schema.
- type TableInfo struct {
- Name string
- Columns []ColumnInfo
- Alias string // For query-local aliases
- IsView bool // Views accept any column reference
- }
- // GetColumn returns a column by name.
- // For views (IsView=true), returns a wildcard ColumnInfo so column analysis passes.
- func (t *TableInfo) GetColumn(name string) (*ColumnInfo, bool) {
- if t.IsView {
- return &ColumnInfo{Name: name, TableName: t.Name, Type: TypeAny}, true
- }
- upper := strings.ToUpper(name)
- for i := range t.Columns {
- if strings.ToUpper(t.Columns[i].Name) == upper {
- return &t.Columns[i], true
- }
- }
- return nil, false
- }
- // ExprInfo contains analysis results for an expression.
- type ExprInfo struct {
- Type Type
- IsAggregate bool
- IsConstant bool
- Nullable bool
- }
|