sqlite.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. package migrate
  2. import (
  3. "git.ycl.pub/yl32940/TsDB2/DB/core/page"
  4. "database/sql"
  5. "fmt"
  6. "strings"
  7. _ "modernc.org/sqlite" // 纯 Go 的 SQLite 实现, 不依赖 CGO
  8. )
  9. // OpenSQLite 打开 (不存在则新建) 一个 SQLite 库文件。
  10. //
  11. // path 可以是普通路径, 也可以是 "file:xxx.db?_pragma=busy_timeout(5000)" 这种 URI。
  12. // 只留 1 个连接: SQLite 同一时刻只有一个写者, 多连接更容易撞 SQLITE_BUSY。
  13. func OpenSQLite(path string, opts *Options) (*SQL, error) {
  14. db, err := sql.Open("sqlite", path)
  15. if err != nil {
  16. return nil, fmt.Errorf("打开 SQLite %s: %w", path, err)
  17. }
  18. db.SetMaxOpenConns(1)
  19. if err := db.Ping(); err != nil {
  20. _ = db.Close()
  21. return nil, fmt.Errorf("打开 SQLite %s: %w", path, err)
  22. }
  23. return newSQL(db, KindSQLite, path, sqliteDial{}, opts), nil
  24. }
  25. type sqliteDial struct{}
  26. func (sqliteDial) kind() Kind { return KindSQLite }
  27. func (sqliteDial) quote(name string) string {
  28. return `"` + strings.ReplaceAll(name, `"`, `""`) + `"`
  29. }
  30. func (d sqliteDial) columnDDL(c Column) string {
  31. return d.quote(c.Name) + " " + columnTypeName(c, false) + nullClause(c)
  32. }
  33. func (d sqliteDial) createTable(s *TableSchema) string {
  34. parts := make([]string, 0, len(s.Columns)+1)
  35. for _, c := range s.Columns {
  36. parts = append(parts, " "+d.columnDDL(c))
  37. }
  38. if s.PKColumn != "" {
  39. parts = append(parts, " PRIMARY KEY ("+d.quote(s.PKColumn)+")")
  40. }
  41. return fmt.Sprintf("CREATE TABLE %s (\n%s\n)", d.quote(s.Name), strings.Join(parts, ",\n"))
  42. }
  43. func (d sqliteDial) dropTable(table string) string {
  44. return "DROP TABLE IF EXISTS " + d.quote(table)
  45. }
  46. func (d sqliteDial) addColumn(table string, c Column) string {
  47. return fmt.Sprintf("ALTER TABLE %s ADD COLUMN %s", d.quote(table), d.columnDDL(c))
  48. }
  49. func (d sqliteDial) createIndex(table string, idx IndexSchema) string {
  50. cols := make([]string, len(idx.Columns))
  51. for i, c := range idx.Columns {
  52. cols[i] = d.quote(c)
  53. }
  54. // SQLite 的索引名是库级唯一的 (不像 MySQL 按表命名空间), 撞名时退让:
  55. // 迁移过来的 VRTX 索引名通常自带表名, 这里只是兜底。
  56. return fmt.Sprintf("%s IF NOT EXISTS %s ON %s (%s)",
  57. createIndexKind(idx), d.quote(idx.Name), d.quote(table), strings.Join(cols, ", "))
  58. }
  59. func (d sqliteDial) listTables(db *sql.DB) ([]string, error) {
  60. rows, err := db.Query(`SELECT name FROM sqlite_master
  61. WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name`)
  62. if err != nil {
  63. return nil, fmt.Errorf("列 SQLite 表: %w", err)
  64. }
  65. defer rows.Close()
  66. var out []string
  67. for rows.Next() {
  68. var name string
  69. if err := rows.Scan(&name); err != nil {
  70. return nil, err
  71. }
  72. out = append(out, name)
  73. }
  74. return out, rows.Err()
  75. }
  76. // columns 读列与主键列名 (PRAGMA table_info; pk 字段是该列在主键里的序号)。
  77. // 第三个返回值是"声明容量超过单值上限"的列 (见 capacityNote), 由 SQL.Schema 收进报告。
  78. func (d sqliteDial) columns(db *sql.DB, table string) ([]Column, string, sourceNotes, error) {
  79. rows, err := db.Query("PRAGMA table_info(" + d.quote(table) + ")")
  80. if err != nil {
  81. return nil, "", sourceNotes{}, err
  82. }
  83. defer rows.Close()
  84. type pkCol struct {
  85. seq int
  86. name string
  87. }
  88. var (
  89. cols []Column
  90. pks []pkCol
  91. notes sourceNotes
  92. )
  93. for rows.Next() {
  94. var (
  95. cid, notNull, pk int
  96. name, decl string
  97. dflt any
  98. )
  99. if err := rows.Scan(&cid, &name, &decl, &notNull, &dflt, &pk); err != nil {
  100. return nil, "", sourceNotes{}, err
  101. }
  102. ft, param, note := inferSQLTypeWithNote(decl, false)
  103. if note != "" {
  104. notes.Capped = append(notes.Capped, name+": "+note)
  105. }
  106. if d := degradeNote(decl); d != "" {
  107. notes.Degraded = append(notes.Degraded, name+": "+d)
  108. }
  109. cols = append(cols, Column{
  110. Name: name,
  111. Type: ft,
  112. Param: param,
  113. Decl: decl,
  114. TimeKind: sqlTimeKind(decl),
  115. Nullable: notNull == 0, // PRAGMA 的 notnull: 0 = 该列可为空
  116. })
  117. if pk > 0 {
  118. pks = append(pks, pkCol{seq: pk, name: name})
  119. }
  120. }
  121. if err := rows.Err(); err != nil {
  122. return nil, "", sourceNotes{}, err
  123. }
  124. if len(pks) == 1 {
  125. // SQLite 的 `id INTEGER PRIMARY KEY` 是 rowid 别名, PRAGMA 里 notnull = 0
  126. // (非空由"自动生成 rowid"保证, 不由 NOT NULL 约束表达)。而 VRTX 的行存储列
  127. // 承载 pkID, 必须有值 —— 不在这里钉住, 外部库导进来就会得到一张"主键列可为空"
  128. // 的 VRTX 表。注意这是 SQLite 特有的: 我们导出的表会显式写 NOT NULL, 读回
  129. // notnull = 1, 所以这一步只对"外部建的库"生效, 不影响往返。
  130. for i := range cols {
  131. if cols[i].Name == pks[0].name {
  132. cols[i].Nullable = false
  133. }
  134. }
  135. return cols, pks[0].name, notes, nil
  136. }
  137. if len(pks) > 1 {
  138. // `PRIMARY KEY (a, b)` 是 SQLite 的表级约束, PRAGMA 里表现为多列的 pk 序号。
  139. // 与 MySQL 同名场景一样: VRTX 的行存储索引只能单列, 但"源表主键是哪几列"这个
  140. // 事实只有这里知道, 记下来供下游报准话。
  141. ordered := make([]string, len(pks))
  142. for _, p := range pks {
  143. if p.seq >= 1 && p.seq <= len(pks) {
  144. ordered[p.seq-1] = p.name
  145. }
  146. }
  147. notes.PK = compositePKNote(table, ordered)
  148. }
  149. // 复合主键 / 无主键: 都当作"没有单列主键", 由调用方决定自增还是报错
  150. return cols, "", notes, nil
  151. }
  152. // indexes 读二级索引, 唯一性照搬 (PRAGMA index_list 的 unique 列)。
  153. //
  154. // origin='c' 是显式 CREATE INDEX (普通或唯一); origin='u' 是 UNIQUE 列/表约束
  155. // 自动生成的 sqlite_autoindex_* —— 名字不是用户起的, 但它就是一条唯一索引, 忽略
  156. // 它等于把约束丢掉, 所以照收并改个能重建的名字 (sqlite_ 前缀是保留的, 不能拿来
  157. // 建索引)。origin='pk' 是主键索引, 由 PKColumn 表达, 跳过。
  158. func (d sqliteDial) indexes(db *sql.DB, table string) ([]IndexSchema, error) {
  159. rows, err := db.Query("PRAGMA index_list(" + d.quote(table) + ")")
  160. if err != nil {
  161. return nil, err
  162. }
  163. type entry struct {
  164. name string
  165. origin string
  166. unique bool
  167. }
  168. var list []entry
  169. for rows.Next() {
  170. var (
  171. seq, unique, partial int
  172. name, origin string
  173. )
  174. if err := rows.Scan(&seq, &name, &unique, &origin, &partial); err != nil {
  175. rows.Close()
  176. return nil, err
  177. }
  178. list = append(list, entry{name: name, origin: origin, unique: unique != 0})
  179. }
  180. if err := rows.Err(); err != nil {
  181. rows.Close()
  182. return nil, err
  183. }
  184. rows.Close()
  185. var out []IndexSchema
  186. uqSeq := 0
  187. for _, e := range list {
  188. if e.origin != "c" && e.origin != "u" {
  189. continue
  190. }
  191. cols, err := d.indexColumns(db, e.name)
  192. if err != nil {
  193. return nil, err
  194. }
  195. if len(cols) == 0 {
  196. continue
  197. }
  198. name := e.name
  199. if e.origin == "u" {
  200. uqSeq++
  201. name = fmt.Sprintf("uq_%s_%d", table, uqSeq)
  202. }
  203. out = append(out, IndexSchema{Name: name, Type: page.IndexTypeBTree, Columns: cols, Unique: e.unique})
  204. }
  205. return out, nil
  206. }
  207. func (d sqliteDial) indexColumns(db *sql.DB, index string) ([]string, error) {
  208. rows, err := db.Query("PRAGMA index_info(" + d.quote(index) + ")")
  209. if err != nil {
  210. return nil, err
  211. }
  212. defer rows.Close()
  213. var cols []string
  214. for rows.Next() {
  215. var (
  216. seqno, cid int
  217. name string
  218. )
  219. if err := rows.Scan(&seqno, &cid, &name); err != nil {
  220. return nil, err
  221. }
  222. cols = append(cols, name)
  223. }
  224. return cols, rows.Err()
  225. }
  226. func (d sqliteDial) metaDDL() string {
  227. return fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (tbl TEXT PRIMARY KEY, payload TEXT NOT NULL)",
  228. d.quote(MetaTable))
  229. }
  230. func (d sqliteDial) metaUpsert() string {
  231. return fmt.Sprintf("INSERT INTO %s (tbl, payload) VALUES (?, ?) "+
  232. "ON CONFLICT(tbl) DO UPDATE SET payload = excluded.payload", d.quote(MetaTable))
  233. }
  234. func (d sqliteDial) metaSelect() string {
  235. return fmt.Sprintf("SELECT payload FROM %s WHERE tbl = ? LIMIT 1", d.quote(MetaTable))
  236. }
  237. // maxPlaceholders SQLite 的 SQLITE_MAX_VARIABLE_NUMBER 现代版本默认 32766。
  238. func (d sqliteDial) maxPlaceholders() int { return 30000 }
  239. func (d sqliteDial) textLenExpr(col string) string {
  240. // SQLite 的 length() 对文本数的是【字符】数 (到首个 NUL 为止), 与按字节计容量的
  241. // 宽度码对不上 —— "25 个汉字" 会采成 25 而实际 75 字节, 建窄的列后面全写不进去。
  242. // CAST 成 BLOB 后 length() 返回原始字节数, 与宽度码同口径。
  243. return "LENGTH(CAST(" + d.quote(col) + " AS BLOB))"
  244. }