2
0

sqlite_catalog.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  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.session.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.session.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.session.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.session.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.session.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.session.GetIndex(name)
  223. if err != nil {
  224. return nil, err
  225. }
  226. schema, err := e.session.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.session.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(primaryKeyOrdinal(schema, col.Name))
  266. // Hidden flag: 0 normal, 2 generated VIRTUAL, 3 generated STORED.
  267. hidden := int64(0)
  268. if col.GeneratedExpr != "" {
  269. if col.GeneratedStored {
  270. hidden = 3
  271. } else {
  272. hidden = 2
  273. }
  274. }
  275. result.AddRow(int64(i), col.Name, col.Type, notnull, col.DefaultLiteral(), pk, hidden)
  276. }
  277. return result, nil
  278. }
  279. // recreateCreateTableSQL rebuilds CREATE TABLE from the durable schema. The
  280. // implicit _rowid_ (and its aliases) are stripped, and identifiers are quoted so
  281. // xorm's IsColumnExist / GORM's HasColumn LIKE patterns match.
  282. func recreateCreateTableSQL(s *storage.Schema) string {
  283. compositeKey := s.IsCompositePrimaryKey()
  284. cols := make([]string, 0, len(s.Columns)+len(s.Checks)+1)
  285. for _, col := range s.Columns {
  286. // Strip only the engine-injected hidden rowid column, never user
  287. // columns that happen to be named oid/rowid/_rowid_.
  288. if s.UsesSyntheticRowID() && col.Name == "_rowid_" {
  289. continue
  290. }
  291. cols = append(cols, recreateColumnDef(s, col, compositeKey))
  292. }
  293. // A composite key is rendered as one table-level constraint, as it was
  294. // declared; a single-column key keeps its inline PRIMARY KEY form.
  295. if compositeKey {
  296. columns := s.PrimaryKeyList()
  297. quoted := make([]string, len(columns))
  298. for i, name := range columns {
  299. quoted[i] = quoteIdent(name)
  300. }
  301. cols = append(cols, "PRIMARY KEY ("+strings.Join(quoted, ", ")+")")
  302. }
  303. // Inline and table-level CHECK declarations are stored uniformly, so they
  304. // are all rendered as table-level constraints: `CREATE TABLE t (a INTEGER,
  305. // CHECK (a > 0))` is equivalent SQL for either declaration.
  306. for _, check := range s.Checks {
  307. if check.Name != "" {
  308. cols = append(cols, fmt.Sprintf("CONSTRAINT %s CHECK (%s)", quoteIdent(check.Name), check.Expression))
  309. continue
  310. }
  311. cols = append(cols, fmt.Sprintf("CHECK (%s)", check.Expression))
  312. }
  313. // Foreign keys are stored as resolved table-level constraints, so an inline
  314. // REFERENCES declaration is reported in its equivalent table-level form.
  315. for _, fk := range s.ForeignKeys {
  316. child := make([]string, len(fk.Columns))
  317. for i, name := range fk.Columns {
  318. child[i] = quoteIdent(name)
  319. }
  320. parent := make([]string, len(fk.RefColumns))
  321. for i, name := range fk.RefColumns {
  322. parent[i] = quoteIdent(name)
  323. }
  324. clause := fmt.Sprintf("FOREIGN KEY (%s) REFERENCES %s (%s)", strings.Join(child, ", "), quoteIdent(fk.RefTable), strings.Join(parent, ", "))
  325. for _, action := range []struct {
  326. clause string
  327. value string
  328. }{{"ON DELETE", fk.OnDelete}, {"ON UPDATE", fk.OnUpdate}} {
  329. if action.value != "" {
  330. clause += " " + action.clause + " " + action.value
  331. }
  332. }
  333. if fk.Name != "" {
  334. clause = fmt.Sprintf("CONSTRAINT %s %s", quoteIdent(fk.Name), clause)
  335. }
  336. cols = append(cols, clause)
  337. }
  338. return fmt.Sprintf("CREATE TABLE %s (%s)", quoteIdent(s.Name), strings.Join(cols, ", "))
  339. }
  340. func recreateColumnDef(s *storage.Schema, col storage.Column, compositeKey bool) string {
  341. var b strings.Builder
  342. b.WriteString(quoteIdent(col.Name))
  343. b.WriteString(" ")
  344. b.WriteString(col.Type)
  345. inlineKey := col.PrimaryKey && !compositeKey
  346. if inlineKey {
  347. b.WriteString(" PRIMARY KEY")
  348. }
  349. if inlineKey && s.AutoIncrement {
  350. b.WriteString(" AUTOINCREMENT")
  351. }
  352. if !col.Nullable {
  353. b.WriteString(" NOT NULL")
  354. }
  355. if col.Default != nil {
  356. b.WriteString(" DEFAULT ")
  357. b.WriteString(sqlLiteral(col.Default))
  358. } else if col.DefaultExpr != "" {
  359. // The catalog stores the expression text, so the rebuilt DDL keeps the
  360. // default non-constant instead of freezing the value it had at DDL time.
  361. b.WriteString(" DEFAULT ")
  362. b.WriteString(col.DefaultExpr)
  363. }
  364. if col.GeneratedExpr != "" {
  365. b.WriteString(" GENERATED ALWAYS AS (")
  366. b.WriteString(col.GeneratedExpr)
  367. b.WriteString(")")
  368. if col.GeneratedStored {
  369. b.WriteString(" STORED")
  370. } else {
  371. b.WriteString(" VIRTUAL")
  372. }
  373. }
  374. return b.String()
  375. }
  376. // primaryKeyOrdinal returns the column's 1-based position in the primary key,
  377. // or 0 when it is not part of it. PRAGMA table_info/table_xinfo report the
  378. // ordinal, so a composite key is distinguishable from several single keys.
  379. func primaryKeyOrdinal(s *storage.Schema, columnName string) int {
  380. for i, name := range s.PrimaryKeyList() {
  381. if strings.EqualFold(name, columnName) {
  382. return i + 1
  383. }
  384. }
  385. return 0
  386. }
  387. func recreateCreateIndexSQL(idx *storage.Index) string {
  388. unique := ""
  389. if idx.Unique {
  390. unique = "UNIQUE "
  391. }
  392. cols := make([]string, 0, len(idx.Columns))
  393. for _, c := range idx.Columns {
  394. col := quoteIdent(c.Name)
  395. if c.Desc {
  396. col += " DESC"
  397. }
  398. cols = append(cols, col)
  399. }
  400. return fmt.Sprintf("CREATE %sINDEX %s ON %s (%s)", unique, quoteIdent(idx.Name), quoteIdent(idx.Table), strings.Join(cols, ", "))
  401. }
  402. // quoteIdent backtick-quotes an identifier, doubling embedded backticks.
  403. func quoteIdent(name string) string {
  404. return "`" + strings.ReplaceAll(name, "`", "``") + "`"
  405. }
  406. func sqlLiteral(v interface{}) string {
  407. switch t := v.(type) {
  408. case nil:
  409. return "NULL"
  410. case string:
  411. return "'" + strings.ReplaceAll(t, "'", "''") + "'"
  412. case bool:
  413. if t {
  414. return "1"
  415. }
  416. return "0"
  417. case int:
  418. return strconv.Itoa(t)
  419. case int64:
  420. return strconv.FormatInt(t, 10)
  421. case float64:
  422. return strconv.FormatFloat(t, 'g', -1, 64)
  423. default:
  424. return "'" + strings.ReplaceAll(fmt.Sprintf("%v", v), "'", "''") + "'"
  425. }
  426. }