sqlite_catalog.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  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. // Hidden flag: 0 normal, 2 generated VIRTUAL, 3 generated STORED.
  270. hidden := int64(0)
  271. if col.GeneratedExpr != "" {
  272. if col.GeneratedStored {
  273. hidden = 3
  274. } else {
  275. hidden = 2
  276. }
  277. }
  278. result.AddRow(int64(i), col.Name, col.Type, notnull, col.Default, pk, hidden)
  279. }
  280. return result, nil
  281. }
  282. // recreateCreateTableSQL rebuilds CREATE TABLE from the durable schema. The
  283. // implicit _rowid_ (and its aliases) are stripped, and identifiers are quoted so
  284. // xorm's IsColumnExist / GORM's HasColumn LIKE patterns match.
  285. func recreateCreateTableSQL(s *storage.Schema) string {
  286. cols := make([]string, 0, len(s.Columns))
  287. for _, col := range s.Columns {
  288. // Strip only the engine-injected hidden rowid column, never user
  289. // columns that happen to be named oid/rowid/_rowid_.
  290. if s.PrimaryKey == "_rowid_" && col.Name == "_rowid_" {
  291. continue
  292. }
  293. cols = append(cols, recreateColumnDef(s, col))
  294. }
  295. return fmt.Sprintf("CREATE TABLE %s (%s)", quoteIdent(s.Name), strings.Join(cols, ", "))
  296. }
  297. func recreateColumnDef(s *storage.Schema, col storage.Column) string {
  298. var b strings.Builder
  299. b.WriteString(quoteIdent(col.Name))
  300. b.WriteString(" ")
  301. b.WriteString(col.Type)
  302. if col.PrimaryKey {
  303. b.WriteString(" PRIMARY KEY")
  304. }
  305. if col.PrimaryKey && s.AutoIncrement {
  306. b.WriteString(" AUTOINCREMENT")
  307. }
  308. if !col.Nullable {
  309. b.WriteString(" NOT NULL")
  310. }
  311. if col.Default != nil {
  312. b.WriteString(" DEFAULT ")
  313. b.WriteString(sqlLiteral(col.Default))
  314. }
  315. if col.GeneratedExpr != "" {
  316. b.WriteString(" GENERATED ALWAYS AS (")
  317. b.WriteString(col.GeneratedExpr)
  318. b.WriteString(")")
  319. if col.GeneratedStored {
  320. b.WriteString(" STORED")
  321. } else {
  322. b.WriteString(" VIRTUAL")
  323. }
  324. }
  325. return b.String()
  326. }
  327. func recreateCreateIndexSQL(idx *storage.Index) string {
  328. unique := ""
  329. if idx.Unique {
  330. unique = "UNIQUE "
  331. }
  332. cols := make([]string, 0, len(idx.Columns))
  333. for _, c := range idx.Columns {
  334. col := quoteIdent(c.Name)
  335. if c.Desc {
  336. col += " DESC"
  337. }
  338. cols = append(cols, col)
  339. }
  340. return fmt.Sprintf("CREATE %sINDEX %s ON %s (%s)", unique, quoteIdent(idx.Name), quoteIdent(idx.Table), strings.Join(cols, ", "))
  341. }
  342. // quoteIdent backtick-quotes an identifier, doubling embedded backticks.
  343. func quoteIdent(name string) string {
  344. return "`" + strings.ReplaceAll(name, "`", "``") + "`"
  345. }
  346. func sqlLiteral(v interface{}) string {
  347. switch t := v.(type) {
  348. case nil:
  349. return "NULL"
  350. case string:
  351. return "'" + strings.ReplaceAll(t, "'", "''") + "'"
  352. case bool:
  353. if t {
  354. return "1"
  355. }
  356. return "0"
  357. case int:
  358. return strconv.Itoa(t)
  359. case int64:
  360. return strconv.FormatInt(t, 10)
  361. case float64:
  362. return strconv.FormatFloat(t, 'g', -1, 64)
  363. default:
  364. return "'" + strings.ReplaceAll(fmt.Sprintf("%v", v), "'", "''") + "'"
  365. }
  366. }