sqlite_catalog.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. package executor
  2. import (
  3. "fmt"
  4. "sort"
  5. "strconv"
  6. "strings"
  7. "github.com/danfragoso/pizzasql-next/pkg/parser"
  8. "github.com/danfragoso/pizzasql-next/pkg/storage"
  9. )
  10. // SQLite catalog-introspection support for xorm.io/xorm v0.8.0 and
  11. // github.com/glebarez/sqlite v1.11.0. These drivers read the virtual
  12. // sqlite_master / sqlite_schema table and the index_list / index_info /
  13. // table_xinfo pragmas. Rows are synthesized from the durable PizzaSQL schema;
  14. // nothing is persisted.
  15. var sqliteCatalogTables = map[string]bool{
  16. "sqlite_master": true,
  17. "sqlite_schema": true,
  18. }
  19. func sqliteCatalogSchema(tableName string) *storage.Schema {
  20. return &storage.Schema{
  21. Name: tableName,
  22. Columns: []storage.Column{
  23. {Name: "type", Type: "TEXT", Nullable: true},
  24. {Name: "name", Type: "TEXT", Nullable: true},
  25. {Name: "tbl_name", Type: "TEXT", Nullable: true},
  26. {Name: "rootpage", Type: "INTEGER", Nullable: true},
  27. {Name: "sql", Type: "TEXT", Nullable: true},
  28. },
  29. }
  30. }
  31. func isSQLiteCatalogTable(name string) bool {
  32. return sqliteCatalogTables[strings.ToLower(name)]
  33. }
  34. // sqliteCatalogSelect answers a single-table SELECT against sqlite_master /
  35. // sqlite_schema. handled is false for any other shape so the caller falls
  36. // through to the normal SELECT path.
  37. func (e *Executor) sqliteCatalogSelect(stmt *parser.SelectStmt) (*Result, bool, error) {
  38. if stmt == nil || stmt.Compound != nil || len(stmt.From) != 1 {
  39. return nil, false, nil
  40. }
  41. ref := stmt.From[0]
  42. if ref.Subquery != nil || ref.Join != nil || !isSQLiteCatalogTable(ref.Name) {
  43. return nil, false, nil
  44. }
  45. rows, err := e.sqliteCatalogRows()
  46. if err != nil {
  47. return nil, true, err
  48. }
  49. if ref.Alias != "" {
  50. for i := range rows {
  51. rows[i] = e.addTableAlias(rows[i], ref.Alias)
  52. }
  53. }
  54. result, err := e.executeSelectOnRows(stmt, rows, sqliteCatalogSchema(ref.Name))
  55. if err != nil {
  56. return nil, true, err
  57. }
  58. return result, true, nil
  59. }
  60. // sqliteCatalogRows materializes the sqlite_master row set: one "table" row per
  61. // user table (with recreated CREATE TABLE SQL) and one "index" row per index
  62. // (with recreated CREATE INDEX SQL), deterministically ordered.
  63. func (e *Executor) sqliteCatalogRows() ([]storage.Row, error) {
  64. var rows []storage.Row
  65. tables, err := e.schema.ListTables()
  66. if err != nil {
  67. return nil, err
  68. }
  69. tableNames := append([]string(nil), tables...)
  70. sort.Strings(tableNames)
  71. for _, name := range tableNames {
  72. schema, err := e.schema.GetSchema(name)
  73. if err != nil {
  74. return nil, fmt.Errorf("sqlite_catalog: resolve table %q: %w", name, err)
  75. }
  76. rows = append(rows, storage.Row{
  77. "type": "table",
  78. "name": schema.Name,
  79. "tbl_name": schema.Name,
  80. "rootpage": int64(0),
  81. "sql": recreateCreateTableSQL(schema),
  82. })
  83. }
  84. indexes, err := e.schema.ListIndexes()
  85. if err != nil {
  86. return nil, err
  87. }
  88. indexNames := append([]string(nil), indexes...)
  89. sort.Strings(indexNames)
  90. for _, name := range indexNames {
  91. idx, err := e.schema.GetIndex(name)
  92. if err != nil {
  93. return nil, fmt.Errorf("sqlite_catalog: resolve index %q: %w", name, err)
  94. }
  95. rows = append(rows, storage.Row{
  96. "type": "index",
  97. "name": idx.Name,
  98. "tbl_name": idx.Table,
  99. "rootpage": int64(0),
  100. "sql": recreateCreateIndexSQL(idx),
  101. })
  102. }
  103. return rows, nil
  104. }
  105. // executeSelectOnRows runs the shared SELECT tail (WHERE, GROUP BY, aggregates,
  106. // ORDER BY/LIMIT/OFFSET, projection, DISTINCT) over an in-memory row set.
  107. func (e *Executor) executeSelectOnRows(stmt *parser.SelectStmt, rows []storage.Row, schema *storage.Schema) (*Result, error) {
  108. if stmt.Where != nil {
  109. filtered := make([]storage.Row, 0, len(rows))
  110. for _, row := range rows {
  111. val, err := e.evalExpr(stmt.Where, row)
  112. if err != nil {
  113. return nil, err
  114. }
  115. if toBool(val) {
  116. filtered = append(filtered, row)
  117. }
  118. }
  119. rows = filtered
  120. }
  121. if len(stmt.GroupBy) > 0 {
  122. return e.executeGroupBy(stmt, rows, schema)
  123. }
  124. if e.hasAggregates(stmt.Columns) {
  125. return e.executeAggregateSelect(stmt, rows, schema)
  126. }
  127. rows = e.orderAndLimitRows(rows, stmt.OrderBy, stmt.Limit, stmt.Offset, stmt.Columns)
  128. result := NewResult("SELECT")
  129. for i, col := range stmt.Columns {
  130. switch {
  131. case col.Alias != "":
  132. result.AddColumn(col.Alias)
  133. case col.Star:
  134. for _, c := range schema.Columns {
  135. result.AddColumn(c.Name)
  136. }
  137. default:
  138. if ref, ok := col.Expr.(*parser.ColumnRef); ok {
  139. result.AddColumn(ref.Column)
  140. } else {
  141. result.AddColumn(fmt.Sprintf("column%d", i+1))
  142. }
  143. }
  144. }
  145. for _, row := range rows {
  146. values := make([]interface{}, 0, len(stmt.Columns))
  147. for _, col := range stmt.Columns {
  148. if col.Star {
  149. for _, c := range schema.Columns {
  150. values = append(values, row[c.Name])
  151. }
  152. } else {
  153. val, err := e.evalExpr(col.Expr, row)
  154. if err != nil {
  155. return nil, err
  156. }
  157. values = append(values, val)
  158. }
  159. }
  160. result.AddRow(values...)
  161. }
  162. if stmt.Distinct {
  163. result.Rows = e.applyDistinct(result.Rows)
  164. }
  165. return result, nil
  166. }
  167. // sqliteCatalogPragma answers the introspection pragmas (index_list, index_info,
  168. // table_xinfo). handled is false for everything else so the built-in handler runs.
  169. func (e *Executor) sqliteCatalogPragma(stmt *parser.PragmaStmt) (*Result, bool, error) {
  170. if stmt == nil {
  171. return nil, false, nil
  172. }
  173. switch strings.ToLower(stmt.Name) {
  174. case "index_list", "index_info", "table_xinfo":
  175. res, err := e.executeCatalogPragma(stmt)
  176. return res, true, err
  177. default:
  178. return nil, false, nil
  179. }
  180. }
  181. func (e *Executor) executeCatalogPragma(stmt *parser.PragmaStmt) (*Result, error) {
  182. switch strings.ToLower(stmt.Name) {
  183. case "index_list":
  184. return e.pragmaIndexList(stmt.Arg)
  185. case "index_info":
  186. return e.pragmaIndexInfo(stmt.Arg)
  187. case "table_xinfo":
  188. return e.pragmaTableXInfo(stmt.Arg)
  189. default:
  190. return nil, fmt.Errorf("unknown pragma: %s", stmt.Name)
  191. }
  192. }
  193. // pragmaIndexList returns PRAGMA index_list(table): seq, name, unique, origin,
  194. // partial. PizzaSQL only creates indexes via CREATE INDEX, so origin is "c".
  195. func (e *Executor) pragmaIndexList(table string) (*Result, error) {
  196. if table == "" {
  197. return nil, fmt.Errorf("index_list requires a table name")
  198. }
  199. indexes, err := e.schema.ListTableIndexes(table)
  200. if err != nil {
  201. return nil, err
  202. }
  203. sort.Slice(indexes, func(i, j int) bool { return indexes[i].Name < indexes[j].Name })
  204. result := NewResult("PRAGMA")
  205. for _, c := range []string{"seq", "name", "unique", "origin", "partial"} {
  206. result.AddColumn(c)
  207. }
  208. for i, idx := range indexes {
  209. unique := int64(0)
  210. if idx.Unique {
  211. unique = 1
  212. }
  213. result.AddRow(int64(i), idx.Name, unique, "c", int64(0))
  214. }
  215. return result, nil
  216. }
  217. // pragmaIndexInfo returns PRAGMA index_info(index): seqno, cid, name.
  218. func (e *Executor) pragmaIndexInfo(name string) (*Result, error) {
  219. if name == "" {
  220. return nil, fmt.Errorf("index_info requires an index name")
  221. }
  222. idx, err := e.schema.GetIndex(name)
  223. if err != nil {
  224. return nil, err
  225. }
  226. schema, err := e.schema.GetSchema(idx.Table)
  227. if err != nil {
  228. return nil, err
  229. }
  230. result := NewResult("PRAGMA")
  231. for _, c := range []string{"seqno", "cid", "name"} {
  232. result.AddColumn(c)
  233. }
  234. for seqno, ic := range idx.Columns {
  235. cid := int64(-1)
  236. for i, c := range schema.Columns {
  237. if strings.EqualFold(c.Name, ic.Name) {
  238. cid = int64(i)
  239. break
  240. }
  241. }
  242. result.AddRow(int64(seqno), cid, ic.Name)
  243. }
  244. return result, nil
  245. }
  246. // pragmaTableXInfo returns PRAGMA table_xinfo(table): table_info columns plus a
  247. // trailing hidden flag (always 0).
  248. func (e *Executor) pragmaTableXInfo(table string) (*Result, error) {
  249. if table == "" {
  250. return nil, fmt.Errorf("table_xinfo requires a table name")
  251. }
  252. schema, err := e.schema.GetSchema(table)
  253. if err != nil {
  254. return nil, err
  255. }
  256. result := NewResult("PRAGMA")
  257. for _, c := range []string{"cid", "name", "type", "notnull", "dflt_value", "pk", "hidden"} {
  258. result.AddColumn(c)
  259. }
  260. for i, col := range schema.Columns {
  261. notnull := int64(0)
  262. if !col.Nullable {
  263. notnull = 1
  264. }
  265. pk := int64(0)
  266. if col.PrimaryKey {
  267. pk = 1
  268. }
  269. result.AddRow(int64(i), col.Name, col.Type, notnull, col.Default, pk, int64(0))
  270. }
  271. return result, nil
  272. }
  273. // recreateCreateTableSQL rebuilds CREATE TABLE from the durable schema. The
  274. // implicit _rowid_ (and its aliases) are stripped, and identifiers are quoted so
  275. // xorm's IsColumnExist / GORM's HasColumn LIKE patterns match.
  276. func recreateCreateTableSQL(s *storage.Schema) string {
  277. cols := make([]string, 0, len(s.Columns))
  278. for _, col := range s.Columns {
  279. // Strip only the engine-injected hidden rowid column, never user
  280. // columns that happen to be named oid/rowid/_rowid_.
  281. if s.PrimaryKey == "_rowid_" && col.Name == "_rowid_" {
  282. continue
  283. }
  284. cols = append(cols, recreateColumnDef(s, col))
  285. }
  286. return fmt.Sprintf("CREATE TABLE %s (%s)", quoteIdent(s.Name), strings.Join(cols, ", "))
  287. }
  288. func recreateColumnDef(s *storage.Schema, col storage.Column) string {
  289. var b strings.Builder
  290. b.WriteString(quoteIdent(col.Name))
  291. b.WriteString(" ")
  292. b.WriteString(col.Type)
  293. if col.PrimaryKey {
  294. b.WriteString(" PRIMARY KEY")
  295. }
  296. if col.PrimaryKey && s.AutoIncrement {
  297. b.WriteString(" AUTOINCREMENT")
  298. }
  299. if !col.Nullable {
  300. b.WriteString(" NOT NULL")
  301. }
  302. if col.Default != nil {
  303. b.WriteString(" DEFAULT ")
  304. b.WriteString(sqlLiteral(col.Default))
  305. }
  306. return b.String()
  307. }
  308. func recreateCreateIndexSQL(idx *storage.Index) string {
  309. unique := ""
  310. if idx.Unique {
  311. unique = "UNIQUE "
  312. }
  313. cols := make([]string, 0, len(idx.Columns))
  314. for _, c := range idx.Columns {
  315. col := quoteIdent(c.Name)
  316. if c.Desc {
  317. col += " DESC"
  318. }
  319. cols = append(cols, col)
  320. }
  321. return fmt.Sprintf("CREATE %sINDEX %s ON %s (%s)", unique, quoteIdent(idx.Name), quoteIdent(idx.Table), strings.Join(cols, ", "))
  322. }
  323. // quoteIdent backtick-quotes an identifier, doubling embedded backticks.
  324. func quoteIdent(name string) string {
  325. return "`" + strings.ReplaceAll(name, "`", "``") + "`"
  326. }
  327. func sqlLiteral(v interface{}) string {
  328. switch t := v.(type) {
  329. case nil:
  330. return "NULL"
  331. case string:
  332. return "'" + strings.ReplaceAll(t, "'", "''") + "'"
  333. case bool:
  334. if t {
  335. return "1"
  336. }
  337. return "0"
  338. case int:
  339. return strconv.Itoa(t)
  340. case int64:
  341. return strconv.FormatInt(t, 10)
  342. case float64:
  343. return strconv.FormatFloat(t, 'g', -1, 64)
  344. default:
  345. return "'" + strings.ReplaceAll(fmt.Sprintf("%v", v), "'", "''") + "'"
  346. }
  347. }