types.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. package analyzer
  2. import (
  3. "sort"
  4. "strings"
  5. )
  6. // Type represents a SQL type with SQLite affinity rules.
  7. type Type int
  8. const (
  9. TypeUnknown Type = iota // Unresolved type
  10. TypeNull // NULL value
  11. TypeInteger // INTEGER affinity
  12. TypeReal // REAL affinity
  13. TypeText // TEXT affinity
  14. TypeBlob // BLOB affinity
  15. TypeNumeric // NUMERIC affinity (flexible)
  16. TypeBoolean // Boolean (stored as INTEGER in SQLite)
  17. TypeAny // Any type (for polymorphic functions)
  18. )
  19. func (t Type) String() string {
  20. switch t {
  21. case TypeUnknown:
  22. return "UNKNOWN"
  23. case TypeNull:
  24. return "NULL"
  25. case TypeInteger:
  26. return "INTEGER"
  27. case TypeReal:
  28. return "REAL"
  29. case TypeText:
  30. return "TEXT"
  31. case TypeBlob:
  32. return "BLOB"
  33. case TypeNumeric:
  34. return "NUMERIC"
  35. case TypeBoolean:
  36. return "BOOLEAN"
  37. case TypeAny:
  38. return "ANY"
  39. default:
  40. return "UNKNOWN"
  41. }
  42. }
  43. // TypeFromName returns the Type for a SQL type name using SQLite affinity rules.
  44. // See: https://www.sqlite.org/datatype3.html
  45. func TypeFromName(name string) Type {
  46. upper := strings.ToUpper(name)
  47. // Rule 1: If the type contains "INT" -> INTEGER
  48. if strings.Contains(upper, "INT") {
  49. return TypeInteger
  50. }
  51. // Rule 2: If the type contains "CHAR", "CLOB", or "TEXT" -> TEXT
  52. if strings.Contains(upper, "CHAR") ||
  53. strings.Contains(upper, "CLOB") ||
  54. strings.Contains(upper, "TEXT") {
  55. return TypeText
  56. }
  57. // UUID is stored as text (SQLite-style), not as a native PostgreSQL UUID.
  58. if upper == "UUID" {
  59. return TypeText
  60. }
  61. // Rule 3: If the type contains "BLOB" or is empty -> BLOB
  62. if strings.Contains(upper, "BLOB") || upper == "" {
  63. return TypeBlob
  64. }
  65. // Rule 4: If the type contains "REAL", "FLOA", or "DOUB" -> REAL
  66. if strings.Contains(upper, "REAL") ||
  67. strings.Contains(upper, "FLOA") ||
  68. strings.Contains(upper, "DOUB") {
  69. return TypeReal
  70. }
  71. // Rule 5: Otherwise -> NUMERIC
  72. // This includes NUMERIC, DECIMAL, BOOLEAN, DATE, DATETIME
  73. switch upper {
  74. case "BOOLEAN", "BOOL":
  75. return TypeBoolean
  76. default:
  77. return TypeNumeric
  78. }
  79. }
  80. // IsNumeric returns true if the type can hold numeric values.
  81. func (t Type) IsNumeric() bool {
  82. switch t {
  83. case TypeInteger, TypeReal, TypeNumeric, TypeBoolean:
  84. return true
  85. default:
  86. return false
  87. }
  88. }
  89. // IsComparable returns true if two types can be compared.
  90. func (t Type) IsComparable(other Type) bool {
  91. // NULL is comparable to anything
  92. if t == TypeNull || other == TypeNull {
  93. return true
  94. }
  95. // ANY matches anything
  96. if t == TypeAny || other == TypeAny {
  97. return true
  98. }
  99. // Same type
  100. if t == other {
  101. return true
  102. }
  103. // Numeric types are inter-comparable
  104. if t.IsNumeric() && other.IsNumeric() {
  105. return true
  106. }
  107. // TEXT and BLOB can be compared
  108. if (t == TypeText || t == TypeBlob) && (other == TypeText || other == TypeBlob) {
  109. return true
  110. }
  111. // NUMERIC/BOOLEAN accepts TEXT (SQLite-compatible: dates stored as text in numeric columns)
  112. if (t == TypeNumeric || t == TypeBoolean) && (other == TypeText || other == TypeBlob) {
  113. return true
  114. }
  115. if (other == TypeNumeric || other == TypeBoolean) && (t == TypeText || t == TypeBlob) {
  116. return true
  117. }
  118. return false
  119. }
  120. // CommonType returns the common type for binary operations.
  121. func CommonType(a, b Type) Type {
  122. if a == TypeUnknown {
  123. return b
  124. }
  125. if b == TypeUnknown {
  126. return a
  127. }
  128. if a == TypeNull {
  129. return b
  130. }
  131. if b == TypeNull {
  132. return a
  133. }
  134. if a == TypeAny {
  135. return b
  136. }
  137. if b == TypeAny {
  138. return a
  139. }
  140. if a == b {
  141. return a
  142. }
  143. // Numeric promotion
  144. if a.IsNumeric() && b.IsNumeric() {
  145. if a == TypeReal || b == TypeReal {
  146. return TypeReal
  147. }
  148. if a == TypeNumeric || b == TypeNumeric {
  149. return TypeNumeric
  150. }
  151. return TypeInteger
  152. }
  153. // Text/Blob coercion
  154. if (a == TypeText || a == TypeBlob) && (b == TypeText || b == TypeBlob) {
  155. return TypeText
  156. }
  157. return TypeText // Default to TEXT for mixed types
  158. }
  159. // FunctionSignature describes a SQL function.
  160. type FunctionSignature struct {
  161. Name string
  162. MinArgs int
  163. MaxArgs int // -1 for variadic
  164. ArgTypes []Type // Expected argument types (TypeAny for flexible)
  165. ReturnType Type
  166. IsAggregate bool
  167. }
  168. // builtinFunctions contains all built-in SQL functions.
  169. var builtinFunctions = map[string]FunctionSignature{
  170. // Aggregate functions
  171. "COUNT": {Name: "COUNT", MinArgs: 0, MaxArgs: 1, ArgTypes: []Type{TypeAny}, ReturnType: TypeInteger, IsAggregate: true},
  172. "SUM": {Name: "SUM", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeNumeric}, ReturnType: TypeNumeric, IsAggregate: true},
  173. "AVG": {Name: "AVG", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeNumeric}, ReturnType: TypeReal, IsAggregate: true},
  174. "MIN": {Name: "MIN", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeAny}, ReturnType: TypeAny, IsAggregate: true},
  175. "MAX": {Name: "MAX", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeAny}, ReturnType: TypeAny, IsAggregate: true},
  176. "TOTAL": {Name: "TOTAL", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeNumeric}, ReturnType: TypeReal, IsAggregate: true},
  177. "GROUP_CONCAT": {Name: "GROUP_CONCAT", MinArgs: 1, MaxArgs: 2, ArgTypes: []Type{TypeAny, TypeText}, ReturnType: TypeText, IsAggregate: true},
  178. // String functions
  179. "LENGTH": {Name: "LENGTH", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeText}, ReturnType: TypeInteger, IsAggregate: false},
  180. "UPPER": {Name: "UPPER", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeText}, ReturnType: TypeText, IsAggregate: false},
  181. "LOWER": {Name: "LOWER", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeText}, ReturnType: TypeText, IsAggregate: false},
  182. "TRIM": {Name: "TRIM", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeText}, ReturnType: TypeText, IsAggregate: false},
  183. "LTRIM": {Name: "LTRIM", MinArgs: 1, MaxArgs: 2, ArgTypes: []Type{TypeText, TypeText}, ReturnType: TypeText, IsAggregate: false},
  184. "RTRIM": {Name: "RTRIM", MinArgs: 1, MaxArgs: 2, ArgTypes: []Type{TypeText, TypeText}, ReturnType: TypeText, IsAggregate: false},
  185. "SUBSTR": {Name: "SUBSTR", MinArgs: 2, MaxArgs: 3, ArgTypes: []Type{TypeText, TypeInteger, TypeInteger}, ReturnType: TypeText, IsAggregate: false},
  186. "REPLACE": {Name: "REPLACE", MinArgs: 3, MaxArgs: 3, ArgTypes: []Type{TypeText, TypeText, TypeText}, ReturnType: TypeText, IsAggregate: false},
  187. "INSTR": {Name: "INSTR", MinArgs: 2, MaxArgs: 2, ArgTypes: []Type{TypeText, TypeText}, ReturnType: TypeInteger, IsAggregate: false},
  188. "PRINTF": {Name: "PRINTF", MinArgs: 1, MaxArgs: -1, ArgTypes: []Type{TypeText}, ReturnType: TypeText, IsAggregate: false},
  189. "CONCAT": {Name: "CONCAT", MinArgs: 1, MaxArgs: -1, ArgTypes: []Type{TypeAny}, ReturnType: TypeText, IsAggregate: false},
  190. // Numeric functions
  191. "ABS": {Name: "ABS", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeNumeric}, ReturnType: TypeNumeric, IsAggregate: false},
  192. "ROUND": {Name: "ROUND", MinArgs: 1, MaxArgs: 2, ArgTypes: []Type{TypeNumeric, TypeInteger}, ReturnType: TypeNumeric, IsAggregate: false},
  193. "CEIL": {Name: "CEIL", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeNumeric}, ReturnType: TypeInteger, IsAggregate: false},
  194. "FLOOR": {Name: "FLOOR", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeNumeric}, ReturnType: TypeInteger, IsAggregate: false},
  195. "MOD": {Name: "MOD", MinArgs: 2, MaxArgs: 2, ArgTypes: []Type{TypeInteger, TypeInteger}, ReturnType: TypeInteger, IsAggregate: false},
  196. "RANDOM": {Name: "RANDOM", MinArgs: 0, MaxArgs: 0, ArgTypes: []Type{}, ReturnType: TypeInteger, IsAggregate: false},
  197. // Null handling
  198. "COALESCE": {Name: "COALESCE", MinArgs: 1, MaxArgs: -1, ArgTypes: []Type{TypeAny}, ReturnType: TypeAny, IsAggregate: false},
  199. "NULLIF": {Name: "NULLIF", MinArgs: 2, MaxArgs: 2, ArgTypes: []Type{TypeAny, TypeAny}, ReturnType: TypeAny, IsAggregate: false},
  200. "IFNULL": {Name: "IFNULL", MinArgs: 2, MaxArgs: 2, ArgTypes: []Type{TypeAny, TypeAny}, ReturnType: TypeAny, IsAggregate: false},
  201. "IIF": {Name: "IIF", MinArgs: 3, MaxArgs: 3, ArgTypes: []Type{TypeBoolean, TypeAny, TypeAny}, ReturnType: TypeAny, IsAggregate: false},
  202. // Type functions
  203. "TYPEOF": {Name: "TYPEOF", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeAny}, ReturnType: TypeText, IsAggregate: false},
  204. "CAST": {Name: "CAST", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeAny}, ReturnType: TypeAny, IsAggregate: false},
  205. // Date/Time functions
  206. "DATE": {Name: "DATE", MinArgs: 0, MaxArgs: -1, ArgTypes: []Type{TypeAny}, ReturnType: TypeText, IsAggregate: false},
  207. "TIME": {Name: "TIME", MinArgs: 0, MaxArgs: -1, ArgTypes: []Type{TypeAny}, ReturnType: TypeText, IsAggregate: false},
  208. "DATETIME": {Name: "DATETIME", MinArgs: 0, MaxArgs: -1, ArgTypes: []Type{TypeAny}, ReturnType: TypeText, IsAggregate: false},
  209. "JULIANDAY": {Name: "JULIANDAY", MinArgs: 0, MaxArgs: -1, ArgTypes: []Type{TypeAny}, ReturnType: TypeReal, IsAggregate: false},
  210. "UNIXEPOCH": {Name: "UNIXEPOCH", MinArgs: 0, MaxArgs: -1, ArgTypes: []Type{TypeAny}, ReturnType: TypeInteger, IsAggregate: false},
  211. "STRFTIME": {Name: "STRFTIME", MinArgs: 1, MaxArgs: -1, ArgTypes: []Type{TypeText, TypeAny}, ReturnType: TypeText, IsAggregate: false},
  212. "TIMEDIFF": {Name: "TIMEDIFF", MinArgs: 2, MaxArgs: 2, ArgTypes: []Type{TypeAny, TypeAny}, ReturnType: TypeText, IsAggregate: false},
  213. // SQLite specific
  214. "SQLITE_VERSION": {Name: "SQLITE_VERSION", MinArgs: 0, MaxArgs: 0, ArgTypes: []Type{}, ReturnType: TypeText, IsAggregate: false},
  215. "PIZZASQL_VERSION": {Name: "PIZZASQL_VERSION", MinArgs: 0, MaxArgs: 0, ArgTypes: []Type{}, ReturnType: TypeText, IsAggregate: false},
  216. "LAST_INSERT_ROWID": {Name: "LAST_INSERT_ROWID", MinArgs: 0, MaxArgs: 0, ArgTypes: []Type{}, ReturnType: TypeInteger, IsAggregate: false},
  217. "CHANGES": {Name: "CHANGES", MinArgs: 0, MaxArgs: 0, ArgTypes: []Type{}, ReturnType: TypeInteger, IsAggregate: false},
  218. "TOTAL_CHANGES": {Name: "TOTAL_CHANGES", MinArgs: 0, MaxArgs: 0, ArgTypes: []Type{}, ReturnType: TypeInteger, IsAggregate: false},
  219. // Other
  220. "HEX": {Name: "HEX", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeBlob}, ReturnType: TypeText, IsAggregate: false},
  221. "UNHEX": {Name: "UNHEX", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeText}, ReturnType: TypeBlob, IsAggregate: false},
  222. "ZEROBLOB": {Name: "ZEROBLOB", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeInteger}, ReturnType: TypeBlob, IsAggregate: false},
  223. "QUOTE": {Name: "QUOTE", MinArgs: 1, MaxArgs: 1, ArgTypes: []Type{TypeAny}, ReturnType: TypeText, IsAggregate: false},
  224. }
  225. // LookupFunction returns the function signature for a function name.
  226. func LookupFunction(name string) (FunctionSignature, bool) {
  227. sig, ok := builtinFunctions[strings.ToUpper(name)]
  228. return sig, ok
  229. }
  230. // IsAggregateFunction returns true if the function is an aggregate.
  231. func IsAggregateFunction(name string) bool {
  232. sig, ok := LookupFunction(name)
  233. return ok && sig.IsAggregate
  234. }
  235. // BuiltinFunctions returns all built-in functions sorted by name.
  236. func BuiltinFunctions() []FunctionSignature {
  237. functions := make([]FunctionSignature, 0, len(builtinFunctions))
  238. for _, sig := range builtinFunctions {
  239. functions = append(functions, sig)
  240. }
  241. sort.Slice(functions, func(i, j int) bool {
  242. return functions[i].Name < functions[j].Name
  243. })
  244. return functions
  245. }
  246. // ColumnInfo describes a column in a table.
  247. type ColumnInfo struct {
  248. Name string
  249. Type Type
  250. Nullable bool
  251. PrimaryKey bool
  252. Default interface{}
  253. TableName string // For qualified references
  254. }
  255. // TableInfo describes a table schema.
  256. type TableInfo struct {
  257. Name string
  258. Columns []ColumnInfo
  259. Alias string // For query-local aliases
  260. IsView bool // Views accept any column reference
  261. }
  262. // GetColumn returns a column by name.
  263. // For views (IsView=true), returns a wildcard ColumnInfo so column analysis passes.
  264. func (t *TableInfo) GetColumn(name string) (*ColumnInfo, bool) {
  265. if t.IsView {
  266. return &ColumnInfo{Name: name, TableName: t.Name, Type: TypeAny}, true
  267. }
  268. upper := strings.ToUpper(name)
  269. for i := range t.Columns {
  270. if strings.ToUpper(t.Columns[i].Name) == upper {
  271. return &t.Columns[i], true
  272. }
  273. }
  274. return nil, false
  275. }
  276. // ExprInfo contains analysis results for an expression.
  277. type ExprInfo struct {
  278. Type Type
  279. IsAggregate bool
  280. IsConstant bool
  281. Nullable bool
  282. }