/
file.go
316 lines (270 loc) · 7.86 KB
/
file.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
// Copyright 2017 The go-sqlite Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package sqlite3
import (
"fmt"
"io"
"os"
"reflect"
"strings"
"github.com/gonuts/binary"
)
const (
sqlite3Magic = "SQLite format 3\x00"
printfDebug = false
)
var (
tblconstraints = []string{"CHECK", "FOREIGN KEY", "UNIQUE", "PRIMARY KEY"}
)
type DbFile struct {
pager pager
header dbHeader
tables []Table
close func() error
}
type dbHeader struct {
Magic [16]byte
PageSize uint16 // database page size in bytes
WVersion byte // file format write version
RVersion byte // file format read version
NReserved byte // bytes of unused reserved space at the end of each page
MaxFraction byte // maximum embedded payload fraction (must be 64)
MinFraction byte // minimum embedded payload fraction (must be 32)
LeafFraction byte // leaf payload fraction (must be 32)
NFileChanges int32 // file change counter
DbSize int32 // size of the database file in pages. The "in-header database size".
FreePage int32 // page number of the first freelist trunk page.
NFreePages int32 // total number of freelist pages.
SchemaCookie [4]byte // schema cookie
SchemaFormat int32 // schema format number. supported formats are 1,2,3 and 4.
PageCacheSize int32 // default page cache size
AutoVacuum int32 // page number of the largest root b-tree page when in auto-vacuum or incremental-vacuum modes, or zero otherwise.
// the database text encoding.
// 1: UTF-8.
// 2: UTF-16le
// 3: UTF-16be
DbEncoding int32
UserVersion int32 // the "user version" as read and set by the user_version PRAGMA
IncrVacuum int32 // tree (non-zero) for incremental-vacuum mode. False (zero) otherwise
ApplicationID int32 // the "Application ID" set by the PRAGMA application_id
XXX_reserved [20]byte // reserved for expansion. must be zero
VersionValid int32 // the version-valid-for number
SqliteVersion int32 // SQLITE_VERSION_NUMBER
}
func OpenFrom(f io.ReadSeeker) (*DbFile, error) {
var db DbFile
dec := binary.NewDecoder(f)
dec.Order = binary.BigEndian
err := dec.Decode(&db.header)
if err != nil {
return nil, err
}
if db.header.DbSize == 0 {
// determine it based on the size of the database file.
// if the size of the database file is not an integer multiple of
// the page-size, round down to the nearest page.
// except, any file larger than 0-bytes in size, is considered to
// contain at least one page.
size, err := f.Seek(0, io.SeekEnd)
if err != nil {
return nil, err
}
if _, err := f.Seek(0, io.SeekStart); err != nil {
return nil, err
}
pagesz := int64(db.header.PageSize)
npages := (size + pagesz - 1) / pagesz
db.header.DbSize = int32(npages)
}
if printfDebug {
fmt.Printf("db: %#v\n", db.header)
}
_, err = f.Seek(0, 0)
if err != nil {
return nil, err
}
if string(db.header.Magic[:]) != sqlite3Magic {
return nil, fmt.Errorf(
"sqlite: invalid file header.\ngot: %q\nwant: %q\n",
string(db.header.Magic[:]),
sqlite3Magic,
)
}
db.pager = newPager(f, db.PageSize(), db.NumPage())
err = db.init()
if err != nil {
return nil, err
}
return &db, err
}
func Open(fname string) (*DbFile, error) {
f, err := os.Open(fname)
if err != nil {
return nil, err
}
db, err := OpenFrom(f)
if err != nil {
f.Close()
return nil, err
}
db.close = f.Close
return db, nil
}
func (db *DbFile) Close() error {
db.pager.Delete()
if db.close != nil {
return db.close()
}
return nil
}
// PageSize returns the database page size in bytes
func (db *DbFile) PageSize() int {
return int(db.header.PageSize)
}
// NumPage returns the number of pages for this database
func (db *DbFile) NumPage() int {
return int(db.header.DbSize)
}
// Encoding returns the text encoding for this database
func (db *DbFile) Encoding() int {
return int(db.header.DbEncoding)
}
// Version returns the sqlite version number used to create this database
func (db *DbFile) Version() int {
return int(db.header.SqliteVersion)
}
func (db *DbFile) Tables() []Table {
return db.tables
}
func (db *DbFile) init() error {
// load sqlite_master
page, err := db.pager.Page(1)
if err != nil {
return err
}
if page.Kind() != BTreeLeafTableKind && page.Kind() != BTreeInteriorTableKind {
return fmt.Errorf("sqlite3: invalid page kind (%v)", page.Kind())
}
btree, err := newBtreeTable(page, db)
if err != nil {
return err
}
if printfDebug {
fmt.Printf(">>> bt-hdr: %#v\n", btree.btheader)
fmt.Printf(">>> init... (ncells=%d)\n", btree.NumCell())
}
return btree.visitRecordsInorder(func(_ *int64, rec Record) error {
// {"table", "tbl1", "tbl1", 2, "CREATE TABLE tbl1(one varchar(10), two smallint)"} (body=62)
// {"table", "tbl2", "tbl2", 3, "CREATE TABLE tbl2(\n f1 varchar(30) primary key,\n f2 text,\n f3 real\n)"}
if len(rec.Values) != 5 {
return fmt.Errorf("sqlite3: invalid table format")
}
rectype := rec.Values[0].(string)
if rectype != "table" {
return nil
}
pageid := reflect.ValueOf(rec.Values[3])
table := Table{
name: rec.Values[1].(string),
pageid: int(pageid.Int()),
}
// skip internal tables, aka don't expose them
if strings.HasPrefix(table.name, "sqlite_") {
return nil
}
def := rec.Values[4].(string)
def = strings.Replace(def, "CREATE TABLE "+table.name, "", 1)
def = strings.Replace(def, "\n", "", -1)
def = strings.TrimSpace(def)
if def[0] == '(' {
def = def[1:]
}
if def[len(def)-1] == ')' {
def = def[:len(def)-1]
}
def = strings.TrimSpace(def)
parts := strings.Split(def, ",")
// strip away statements like 'UNIQUE ...' or 'PRIMARY KEY ...' from a table definition
for i := range parts {
if i >= len(parts) {
break // we removed at least one elem, so avoid out of bounds read
}
parts[i] = strings.TrimSpace(parts[i])
for j := range tblconstraints {
if strings.HasPrefix(parts[i], tblconstraints[j]) {
// drop all other elements
parts = parts[:i]
break
}
}
}
table.cols = make([]Column, len(parts))
for i := range parts {
parts[i] = strings.TrimSpace(parts[i])
idx := strings.Index(parts[i], " ") // find where col name ends and type starts
if idx > 0 {
table.cols[i].name = parts[i][:idx]
} else {
table.cols[i].name = parts[i]
}
}
if printfDebug {
fmt.Printf(">>> def: %q => ncols=%d\n", def, len(table.cols))
}
db.tables = append(db.tables, table)
return nil
})
}
func (db *DbFile) Dumpdb() error {
var err error
for i := 1; i < db.NumPage(); i++ {
page, err := db.pager.Page(i)
if err != nil {
fmt.Printf("error: sqlite3: error retrieving page-%d: %v\n", i, err)
continue
}
fmt.Printf("page-%d: %v\n", i, page.Kind())
btree, err := newBtreeTable(page, db)
if err != nil {
fmt.Printf("** error: %v\n", err)
continue
}
for i := 0; i < btree.NumCell(); i++ {
cell, err := btree.loadCell(i)
if err != nil {
fmt.Printf("** error: %v\n", err)
continue
}
fmt.Printf("--- cell[%03d/%03d]= leftchildpage=%d row=%d payload=%d overflow=%d\n",
i+1, btree.NumCell(),
cell.LeftChildPage,
cell.RowID,
len(cell.Payload),
cell.OverflowPage,
)
}
}
return err
}
// VisitTableRecords performs an inorder traversal of all cells in the
// btree for the table with the given name, passing the (optional,
// hence nullable) RowID, and record-decoded payload of each cell to
// the visitor function `f`.
func (db *DbFile) VisitTableRecords(tableName string, f func(*int64, Record) error) error {
for _, table := range db.tables {
if table.name != tableName {
continue
}
page, err := db.pager.Page(table.pageid)
if err != nil {
return err
}
btree, err := newBtreeTable(page, db)
if err != nil {
return err
}
return btree.visitRecordsInorder(f)
}
return fmt.Errorf("unknown table %q", tableName)
}