sqlite_catalog.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  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. canonical, err := canonicalizeJSONBReadValue(c.Type, row[c.Name])
  151. if err != nil {
  152. return nil, err
  153. }
  154. values = append(values, canonical)
  155. }
  156. } else {
  157. val, err := e.evalExpr(col.Expr, row)
  158. if err != nil {
  159. return nil, err
  160. }
  161. values = append(values, val)
  162. }
  163. }
  164. result.AddRow(values...)
  165. }
  166. if stmt.Distinct {
  167. result.Rows = e.applyDistinct(result.Rows)
  168. }
  169. return result, nil
  170. }
  171. // sqliteCatalogPragma answers the introspection pragmas (index_list, index_info,
  172. // table_xinfo). handled is false for everything else so the built-in handler runs.
  173. func (e *Executor) sqliteCatalogPragma(stmt *parser.PragmaStmt) (*Result, bool, error) {
  174. if stmt == nil {
  175. return nil, false, nil
  176. }
  177. switch strings.ToLower(stmt.Name) {
  178. case "index_list", "index_info", "table_xinfo":
  179. res, err := e.executeCatalogPragma(stmt)
  180. return res, true, err
  181. default:
  182. return nil, false, nil
  183. }
  184. }
  185. func (e *Executor) executeCatalogPragma(stmt *parser.PragmaStmt) (*Result, error) {
  186. switch strings.ToLower(stmt.Name) {
  187. case "index_list":
  188. return e.pragmaIndexList(stmt.Arg)
  189. case "index_info":
  190. return e.pragmaIndexInfo(stmt.Arg)
  191. case "table_xinfo":
  192. return e.pragmaTableXInfo(stmt.Arg)
  193. default:
  194. return nil, fmt.Errorf("unknown pragma: %s", stmt.Name)
  195. }
  196. }
  197. // pragmaIndexList returns PRAGMA index_list(table): seq, name, unique, origin,
  198. // partial. PizzaSQL only creates indexes via CREATE INDEX, so origin is "c".
  199. func (e *Executor) pragmaIndexList(table string) (*Result, error) {
  200. if table == "" {
  201. return nil, fmt.Errorf("index_list requires a table name")
  202. }
  203. indexes, err := e.session.ListTableIndexes(table)
  204. if err != nil {
  205. return nil, err
  206. }
  207. sort.Slice(indexes, func(i, j int) bool { return indexes[i].Name < indexes[j].Name })
  208. result := NewResult("PRAGMA")
  209. for _, c := range []string{"seq", "name", "unique", "origin", "partial"} {
  210. result.AddColumn(c)
  211. }
  212. for i, idx := range indexes {
  213. unique := int64(0)
  214. if idx.Unique {
  215. unique = 1
  216. }
  217. result.AddRow(int64(i), idx.Name, unique, "c", int64(0))
  218. }
  219. return result, nil
  220. }
  221. // pragmaIndexInfo returns PRAGMA index_info(index): seqno, cid, name.
  222. func (e *Executor) pragmaIndexInfo(name string) (*Result, error) {
  223. if name == "" {
  224. return nil, fmt.Errorf("index_info requires an index name")
  225. }
  226. idx, err := e.session.GetIndex(name)
  227. if err != nil {
  228. return nil, err
  229. }
  230. schema, err := e.session.GetSchema(idx.Table)
  231. if err != nil {
  232. return nil, err
  233. }
  234. result := NewResult("PRAGMA")
  235. for _, c := range []string{"seqno", "cid", "name"} {
  236. result.AddColumn(c)
  237. }
  238. for seqno, ic := range idx.Columns {
  239. cid := int64(-1)
  240. for i, c := range schema.Columns {
  241. if strings.EqualFold(c.Name, ic.Name) {
  242. cid = int64(i)
  243. break
  244. }
  245. }
  246. result.AddRow(int64(seqno), cid, ic.Name)
  247. }
  248. return result, nil
  249. }
  250. // pragmaTableXInfo returns PRAGMA table_xinfo(table): table_info columns plus a
  251. // trailing hidden flag (always 0).
  252. func (e *Executor) pragmaTableXInfo(table string) (*Result, error) {
  253. if table == "" {
  254. return nil, fmt.Errorf("table_xinfo requires a table name")
  255. }
  256. schema, err := e.session.GetSchema(table)
  257. if err != nil {
  258. return nil, err
  259. }
  260. result := NewResult("PRAGMA")
  261. for _, c := range []string{"cid", "name", "type", "notnull", "dflt_value", "pk", "hidden"} {
  262. result.AddColumn(c)
  263. }
  264. for i, col := range schema.Columns {
  265. notnull := int64(0)
  266. if !col.Nullable {
  267. notnull = 1
  268. }
  269. pk := int64(primaryKeyOrdinal(schema, col.Name))
  270. // Hidden flag: 0 normal, 2 generated VIRTUAL, 3 generated STORED.
  271. hidden := int64(0)
  272. if col.GeneratedExpr != "" {
  273. if col.GeneratedStored {
  274. hidden = 3
  275. } else {
  276. hidden = 2
  277. }
  278. }
  279. result.AddRow(int64(i), col.Name, col.Type, notnull, col.DefaultLiteral(), pk, hidden)
  280. }
  281. return result, nil
  282. }
  283. // recreateCreateTableSQL rebuilds CREATE TABLE from the durable schema. The
  284. // implicit _rowid_ (and its aliases) are stripped, and identifiers are quoted so
  285. // xorm's IsColumnExist / GORM's HasColumn LIKE patterns match.
  286. func recreateCreateTableSQL(s *storage.Schema) string {
  287. compositeKey := s.IsCompositePrimaryKey()
  288. cols := make([]string, 0, len(s.Columns)+len(s.Checks)+1)
  289. for _, col := range s.Columns {
  290. // Strip only the engine-injected hidden rowid column, never user
  291. // columns that happen to be named oid/rowid/_rowid_.
  292. if s.UsesSyntheticRowID() && col.Name == "_rowid_" {
  293. continue
  294. }
  295. cols = append(cols, recreateColumnDef(s, col, compositeKey))
  296. }
  297. // A composite key is rendered as one table-level constraint, as it was
  298. // declared; a single-column key keeps its inline PRIMARY KEY form.
  299. if compositeKey {
  300. columns := s.PrimaryKeyList()
  301. quoted := make([]string, len(columns))
  302. for i, name := range columns {
  303. quoted[i] = quoteIdent(name)
  304. }
  305. cols = append(cols, "PRIMARY KEY ("+strings.Join(quoted, ", ")+")")
  306. }
  307. // Inline and table-level CHECK declarations are stored uniformly, so they
  308. // are all rendered as table-level constraints: `CREATE TABLE t (a INTEGER,
  309. // CHECK (a > 0))` is equivalent SQL for either declaration.
  310. for _, check := range s.Checks {
  311. if check.Name != "" {
  312. cols = append(cols, fmt.Sprintf("CONSTRAINT %s CHECK (%s)", quoteIdent(check.Name), check.Expression))
  313. continue
  314. }
  315. cols = append(cols, fmt.Sprintf("CHECK (%s)", check.Expression))
  316. }
  317. // Foreign keys are stored as resolved table-level constraints, so an inline
  318. // REFERENCES declaration is reported in its equivalent table-level form.
  319. for _, fk := range s.ForeignKeys {
  320. child := make([]string, len(fk.Columns))
  321. for i, name := range fk.Columns {
  322. child[i] = quoteIdent(name)
  323. }
  324. parent := make([]string, len(fk.RefColumns))
  325. for i, name := range fk.RefColumns {
  326. parent[i] = quoteIdent(name)
  327. }
  328. clause := fmt.Sprintf("FOREIGN KEY (%s) REFERENCES %s (%s)", strings.Join(child, ", "), quoteIdent(fk.RefTable), strings.Join(parent, ", "))
  329. for _, action := range []struct {
  330. clause string
  331. value string
  332. }{{"ON DELETE", fk.OnDelete}, {"ON UPDATE", fk.OnUpdate}} {
  333. if action.value != "" {
  334. clause += " " + action.clause + " " + action.value
  335. }
  336. }
  337. if fk.Name != "" {
  338. clause = fmt.Sprintf("CONSTRAINT %s %s", quoteIdent(fk.Name), clause)
  339. }
  340. cols = append(cols, clause)
  341. }
  342. return fmt.Sprintf("CREATE TABLE %s (%s)", quoteIdent(s.Name), strings.Join(cols, ", "))
  343. }
  344. func recreateColumnDef(s *storage.Schema, col storage.Column, compositeKey bool) string {
  345. var b strings.Builder
  346. b.WriteString(quoteIdent(col.Name))
  347. b.WriteString(" ")
  348. b.WriteString(col.Type)
  349. inlineKey := col.PrimaryKey && !compositeKey
  350. if inlineKey {
  351. b.WriteString(" PRIMARY KEY")
  352. }
  353. if inlineKey && s.AutoIncrement {
  354. b.WriteString(" AUTOINCREMENT")
  355. }
  356. if !col.Nullable {
  357. b.WriteString(" NOT NULL")
  358. }
  359. if col.Default != nil {
  360. b.WriteString(" DEFAULT ")
  361. b.WriteString(sqlLiteral(col.Default))
  362. } else if col.DefaultExpr != "" {
  363. // The catalog stores the expression text, so the rebuilt DDL keeps the
  364. // default non-constant instead of freezing the value it had at DDL time.
  365. b.WriteString(" DEFAULT ")
  366. b.WriteString(col.DefaultExpr)
  367. }
  368. if col.GeneratedExpr != "" {
  369. b.WriteString(" GENERATED ALWAYS AS (")
  370. b.WriteString(col.GeneratedExpr)
  371. b.WriteString(")")
  372. if col.GeneratedStored {
  373. b.WriteString(" STORED")
  374. } else {
  375. b.WriteString(" VIRTUAL")
  376. }
  377. }
  378. return b.String()
  379. }
  380. // primaryKeyOrdinal returns the column's 1-based position in the primary key,
  381. // or 0 when it is not part of it. PRAGMA table_info/table_xinfo report the
  382. // ordinal, so a composite key is distinguishable from several single keys.
  383. func primaryKeyOrdinal(s *storage.Schema, columnName string) int {
  384. for i, name := range s.PrimaryKeyList() {
  385. if strings.EqualFold(name, columnName) {
  386. return i + 1
  387. }
  388. }
  389. return 0
  390. }
  391. func recreateCreateIndexSQL(idx *storage.Index) string {
  392. unique := ""
  393. if idx.Unique {
  394. unique = "UNIQUE "
  395. }
  396. cols := make([]string, 0, len(idx.Columns))
  397. for _, c := range idx.Columns {
  398. col := quoteIdent(c.Name)
  399. if c.Desc {
  400. col += " DESC"
  401. }
  402. cols = append(cols, col)
  403. }
  404. return fmt.Sprintf("CREATE %sINDEX %s ON %s (%s)", unique, quoteIdent(idx.Name), quoteIdent(idx.Table), strings.Join(cols, ", "))
  405. }
  406. // quoteIdent backtick-quotes an identifier, doubling embedded backticks.
  407. func quoteIdent(name string) string {
  408. return "`" + strings.ReplaceAll(name, "`", "``") + "`"
  409. }
  410. func sqlLiteral(v interface{}) string {
  411. switch t := v.(type) {
  412. case nil:
  413. return "NULL"
  414. case string:
  415. return "'" + strings.ReplaceAll(t, "'", "''") + "'"
  416. case bool:
  417. if t {
  418. return "1"
  419. }
  420. return "0"
  421. case int:
  422. return strconv.Itoa(t)
  423. case int64:
  424. return strconv.FormatInt(t, 10)
  425. case float64:
  426. return strconv.FormatFloat(t, 'g', -1, 64)
  427. default:
  428. return "'" + strings.ReplaceAll(fmt.Sprintf("%v", v), "'", "''") + "'"
  429. }
  430. }