mirror of https://github.com/matrix-org/go-neb.git
Kegan Dougal
8 years ago
14 changed files with 14675 additions and 3579 deletions
-
2vendor/manifest
-
264vendor/src/github.com/mattn/go-sqlite3/_example/trace/main.go
-
290vendor/src/github.com/mattn/go-sqlite3/backup_test.go
-
4vendor/src/github.com/mattn/go-sqlite3/callback.go
-
2vendor/src/github.com/mattn/go-sqlite3/doc.go
-
15321vendor/src/github.com/mattn/go-sqlite3/sqlite3-binding.c
-
1701vendor/src/github.com/mattn/go-sqlite3/sqlite3-binding.h
-
129vendor/src/github.com/mattn/go-sqlite3/sqlite3.go
-
4vendor/src/github.com/mattn/go-sqlite3/sqlite3_load_extension.go
-
3vendor/src/github.com/mattn/go-sqlite3/sqlite3_test.go
-
32vendor/src/github.com/mattn/go-sqlite3/sqlite3ext.h
-
78vendor/src/github.com/mattn/go-sqlite3/tool/upgrade.go
-
415vendor/src/github.com/mattn/go-sqlite3/tracecallback.go
-
9vendor/src/github.com/mattn/go-sqlite3/tracecallback_noimpl.go
@ -0,0 +1,264 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"database/sql" |
|||
"fmt" |
|||
"log" |
|||
"os" |
|||
|
|||
sqlite3 "github.com/gimpldo/go-sqlite3" |
|||
) |
|||
|
|||
func traceCallback(info sqlite3.TraceInfo) int { |
|||
// Not very readable but may be useful; uncomment next line in case of doubt:
|
|||
//fmt.Printf("Trace: %#v\n", info)
|
|||
|
|||
var dbErrText string |
|||
if info.DBError.Code != 0 || info.DBError.ExtendedCode != 0 { |
|||
dbErrText = fmt.Sprintf("; DB error: %#v", info.DBError) |
|||
} else { |
|||
dbErrText = "." |
|||
} |
|||
|
|||
// Show the Statement-or-Trigger text in curly braces ('{', '}')
|
|||
// since from the *paired* ASCII characters they are
|
|||
// the least used in SQL syntax, therefore better visual delimiters.
|
|||
// Maybe show 'ExpandedSQL' the same way as 'StmtOrTrigger'.
|
|||
//
|
|||
// A known use of curly braces (outside strings) is
|
|||
// for ODBC escape sequences. Not likely to appear here.
|
|||
//
|
|||
// Template languages, etc. don't matter, we should see their *result*
|
|||
// at *this* level.
|
|||
// Strange curly braces in SQL code that reached the database driver
|
|||
// suggest that there is a bug in the application.
|
|||
// The braces are likely to be either template syntax or
|
|||
// a programming language's string interpolation syntax.
|
|||
|
|||
var expandedText string |
|||
if info.ExpandedSQL != "" { |
|||
if info.ExpandedSQL == info.StmtOrTrigger { |
|||
expandedText = " = exp" |
|||
} else { |
|||
expandedText = fmt.Sprintf(" expanded {%q}", info.ExpandedSQL) |
|||
} |
|||
} else { |
|||
expandedText = "" |
|||
} |
|||
|
|||
// SQLite docs as of September 6, 2016: Tracing and Profiling Functions
|
|||
// https://www.sqlite.org/c3ref/profile.html
|
|||
//
|
|||
// The profile callback time is in units of nanoseconds, however
|
|||
// the current implementation is only capable of millisecond resolution
|
|||
// so the six least significant digits in the time are meaningless.
|
|||
// Future versions of SQLite might provide greater resolution on the profiler callback.
|
|||
|
|||
var runTimeText string |
|||
if info.RunTimeNanosec == 0 { |
|||
if info.EventCode == sqlite3.TraceProfile { |
|||
//runTimeText = "; no time" // seems confusing
|
|||
runTimeText = "; time 0" // no measurement unit
|
|||
} else { |
|||
//runTimeText = "; no time" // seems useless and confusing
|
|||
} |
|||
} else { |
|||
const nanosPerMillisec = 1000000 |
|||
if info.RunTimeNanosec%nanosPerMillisec == 0 { |
|||
runTimeText = fmt.Sprintf("; time %d ms", info.RunTimeNanosec/nanosPerMillisec) |
|||
} else { |
|||
// unexpected: better than millisecond resolution
|
|||
runTimeText = fmt.Sprintf("; time %d ns!!!", info.RunTimeNanosec) |
|||
} |
|||
} |
|||
|
|||
var modeText string |
|||
if info.AutoCommit { |
|||
modeText = "-AC-" |
|||
} else { |
|||
modeText = "+Tx+" |
|||
} |
|||
|
|||
fmt.Printf("Trace: ev %d %s conn 0x%x, stmt 0x%x {%q}%s%s%s\n", |
|||
info.EventCode, modeText, info.ConnHandle, info.StmtHandle, |
|||
info.StmtOrTrigger, expandedText, |
|||
runTimeText, |
|||
dbErrText) |
|||
return 0 |
|||
} |
|||
|
|||
func main() { |
|||
eventMask := sqlite3.TraceStmt | sqlite3.TraceProfile | sqlite3.TraceRow | sqlite3.TraceClose |
|||
|
|||
sql.Register("sqlite3_tracing", |
|||
&sqlite3.SQLiteDriver{ |
|||
ConnectHook: func(conn *sqlite3.SQLiteConn) error { |
|||
err := conn.SetTrace(&sqlite3.TraceConfig{ |
|||
Callback: traceCallback, |
|||
EventMask: uint(eventMask), |
|||
WantExpandedSQL: true, |
|||
}) |
|||
return err |
|||
}, |
|||
}) |
|||
|
|||
os.Exit(dbMain()) |
|||
} |
|||
|
|||
// Harder to do DB work in main().
|
|||
// It's better with a separate function because
|
|||
// 'defer' and 'os.Exit' don't go well together.
|
|||
//
|
|||
// DO NOT use 'log.Fatal...' below: remember that it's equivalent to
|
|||
// Print() followed by a call to os.Exit(1) --- and
|
|||
// we want to avoid Exit() so 'defer' can do cleanup.
|
|||
// Use 'log.Panic...' instead.
|
|||
|
|||
func dbMain() int { |
|||
db, err := sql.Open("sqlite3_tracing", ":memory:") |
|||
if err != nil { |
|||
fmt.Printf("Failed to open database: %#+v\n", err) |
|||
return 1 |
|||
} |
|||
defer db.Close() |
|||
|
|||
err = db.Ping() |
|||
if err != nil { |
|||
log.Panic(err) |
|||
} |
|||
|
|||
dbSetup(db) |
|||
|
|||
dbDoInsert(db) |
|||
dbDoInsertPrepared(db) |
|||
dbDoSelect(db) |
|||
dbDoSelectPrepared(db) |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
// 'DDL' stands for "Data Definition Language":
|
|||
|
|||
// Note: "INTEGER PRIMARY KEY NOT NULL AUTOINCREMENT" causes the error
|
|||
// 'near "AUTOINCREMENT": syntax error'; without "NOT NULL" it works.
|
|||
const tableDDL = `CREATE TABLE t1 ( |
|||
id INTEGER PRIMARY KEY AUTOINCREMENT, |
|||
note VARCHAR NOT NULL |
|||
)` |
|||
|
|||
// 'DML' stands for "Data Manipulation Language":
|
|||
|
|||
const insertDML = "INSERT INTO t1 (note) VALUES (?)" |
|||
const selectDML = "SELECT id, note FROM t1 WHERE note LIKE ?" |
|||
|
|||
const textPrefix = "bla-1234567890-" |
|||
const noteTextPattern = "%Prep%" |
|||
|
|||
const nGenRows = 4 // Number of Rows to Generate (for *each* approach tested)
|
|||
|
|||
func dbSetup(db *sql.DB) { |
|||
var err error |
|||
|
|||
_, err = db.Exec("DROP TABLE IF EXISTS t1") |
|||
if err != nil { |
|||
log.Panic(err) |
|||
} |
|||
_, err = db.Exec(tableDDL) |
|||
if err != nil { |
|||
log.Panic(err) |
|||
} |
|||
} |
|||
|
|||
func dbDoInsert(db *sql.DB) { |
|||
const Descr = "DB-Exec" |
|||
for i := 0; i < nGenRows; i++ { |
|||
result, err := db.Exec(insertDML, textPrefix+Descr) |
|||
if err != nil { |
|||
log.Panic(err) |
|||
} |
|||
|
|||
resultDoCheck(result, Descr, i) |
|||
} |
|||
} |
|||
|
|||
func dbDoInsertPrepared(db *sql.DB) { |
|||
const Descr = "DB-Prepare" |
|||
|
|||
stmt, err := db.Prepare(insertDML) |
|||
if err != nil { |
|||
log.Panic(err) |
|||
} |
|||
defer stmt.Close() |
|||
|
|||
for i := 0; i < nGenRows; i++ { |
|||
result, err := stmt.Exec(textPrefix + Descr) |
|||
if err != nil { |
|||
log.Panic(err) |
|||
} |
|||
|
|||
resultDoCheck(result, Descr, i) |
|||
} |
|||
} |
|||
|
|||
func resultDoCheck(result sql.Result, callerDescr string, callIndex int) { |
|||
lastID, err := result.LastInsertId() |
|||
if err != nil { |
|||
log.Panic(err) |
|||
} |
|||
nAffected, err := result.RowsAffected() |
|||
if err != nil { |
|||
log.Panic(err) |
|||
} |
|||
|
|||
log.Printf("Exec result for %s (%d): ID = %d, affected = %d\n", callerDescr, callIndex, lastID, nAffected) |
|||
} |
|||
|
|||
func dbDoSelect(db *sql.DB) { |
|||
const Descr = "DB-Query" |
|||
|
|||
rows, err := db.Query(selectDML, noteTextPattern) |
|||
if err != nil { |
|||
log.Panic(err) |
|||
} |
|||
defer rows.Close() |
|||
|
|||
rowsDoFetch(rows, Descr) |
|||
} |
|||
|
|||
func dbDoSelectPrepared(db *sql.DB) { |
|||
const Descr = "DB-Prepare" |
|||
|
|||
stmt, err := db.Prepare(selectDML) |
|||
if err != nil { |
|||
log.Panic(err) |
|||
} |
|||
defer stmt.Close() |
|||
|
|||
rows, err := stmt.Query(noteTextPattern) |
|||
if err != nil { |
|||
log.Panic(err) |
|||
} |
|||
defer rows.Close() |
|||
|
|||
rowsDoFetch(rows, Descr) |
|||
} |
|||
|
|||
func rowsDoFetch(rows *sql.Rows, callerDescr string) { |
|||
var nRows int |
|||
var id int64 |
|||
var note string |
|||
|
|||
for rows.Next() { |
|||
err := rows.Scan(&id, ¬e) |
|||
if err != nil { |
|||
log.Panic(err) |
|||
} |
|||
log.Printf("Row for %s (%d): id=%d, note=%q\n", |
|||
callerDescr, nRows, id, note) |
|||
nRows++ |
|||
} |
|||
if err := rows.Err(); err != nil { |
|||
log.Panic(err) |
|||
} |
|||
log.Printf("Total %d rows for %s.\n", nRows, callerDescr) |
|||
} |
@ -0,0 +1,290 @@ |
|||
// Use of this source code is governed by an MIT-style
|
|||
// license that can be found in the LICENSE file.
|
|||
|
|||
package sqlite3 |
|||
|
|||
import ( |
|||
"database/sql" |
|||
"fmt" |
|||
"os" |
|||
"testing" |
|||
"time" |
|||
) |
|||
|
|||
// The number of rows of test data to create in the source database.
|
|||
// Can be used to control how many pages are available to be backed up.
|
|||
const testRowCount = 100 |
|||
|
|||
// The maximum number of seconds after which the page-by-page backup is considered to have taken too long.
|
|||
const usePagePerStepsTimeoutSeconds = 30 |
|||
|
|||
// Test the backup functionality.
|
|||
func testBackup(t *testing.T, testRowCount int, usePerPageSteps bool) { |
|||
// This function will be called multiple times.
|
|||
// It uses sql.Register(), which requires the name parameter value to be unique.
|
|||
// There does not currently appear to be a way to unregister a registered driver, however.
|
|||
// So generate a database driver name that will likely be unique.
|
|||
var driverName = fmt.Sprintf("sqlite3_testBackup_%v_%v_%v", testRowCount, usePerPageSteps, time.Now().UnixNano()) |
|||
|
|||
// The driver's connection will be needed in order to perform the backup.
|
|||
driverConns := []*SQLiteConn{} |
|||
sql.Register(driverName, &SQLiteDriver{ |
|||
ConnectHook: func(conn *SQLiteConn) error { |
|||
driverConns = append(driverConns, conn) |
|||
return nil |
|||
}, |
|||
}) |
|||
|
|||
// Connect to the source database.
|
|||
srcTempFilename := TempFilename(t) |
|||
defer os.Remove(srcTempFilename) |
|||
srcDb, err := sql.Open(driverName, srcTempFilename) |
|||
if err != nil { |
|||
t.Fatal("Failed to open the source database:", err) |
|||
} |
|||
defer srcDb.Close() |
|||
err = srcDb.Ping() |
|||
if err != nil { |
|||
t.Fatal("Failed to connect to the source database:", err) |
|||
} |
|||
|
|||
// Connect to the destination database.
|
|||
destTempFilename := TempFilename(t) |
|||
defer os.Remove(destTempFilename) |
|||
destDb, err := sql.Open(driverName, destTempFilename) |
|||
if err != nil { |
|||
t.Fatal("Failed to open the destination database:", err) |
|||
} |
|||
defer destDb.Close() |
|||
err = destDb.Ping() |
|||
if err != nil { |
|||
t.Fatal("Failed to connect to the destination database:", err) |
|||
} |
|||
|
|||
// Check the driver connections.
|
|||
if len(driverConns) != 2 { |
|||
t.Fatalf("Expected 2 driver connections, but found %v.", len(driverConns)) |
|||
} |
|||
srcDbDriverConn := driverConns[0] |
|||
if srcDbDriverConn == nil { |
|||
t.Fatal("The source database driver connection is nil.") |
|||
} |
|||
destDbDriverConn := driverConns[1] |
|||
if destDbDriverConn == nil { |
|||
t.Fatal("The destination database driver connection is nil.") |
|||
} |
|||
|
|||
// Generate some test data for the given ID.
|
|||
var generateTestData = func(id int) string { |
|||
return fmt.Sprintf("test-%v", id) |
|||
} |
|||
|
|||
// Populate the source database with a test table containing some test data.
|
|||
tx, err := srcDb.Begin() |
|||
if err != nil { |
|||
t.Fatal("Failed to begin a transaction when populating the source database:", err) |
|||
} |
|||
_, err = srcDb.Exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)") |
|||
if err != nil { |
|||
tx.Rollback() |
|||
t.Fatal("Failed to create the source database \"test\" table:", err) |
|||
} |
|||
for id := 0; id < testRowCount; id++ { |
|||
_, err = srcDb.Exec("INSERT INTO test (id, value) VALUES (?, ?)", id, generateTestData(id)) |
|||
if err != nil { |
|||
tx.Rollback() |
|||
t.Fatal("Failed to insert a row into the source database \"test\" table:", err) |
|||
} |
|||
} |
|||
err = tx.Commit() |
|||
if err != nil { |
|||
t.Fatal("Failed to populate the source database:", err) |
|||
} |
|||
|
|||
// Confirm that the destination database is initially empty.
|
|||
var destTableCount int |
|||
err = destDb.QueryRow("SELECT COUNT(*) FROM sqlite_master WHERE type = 'table'").Scan(&destTableCount) |
|||
if err != nil { |
|||
t.Fatal("Failed to check the destination table count:", err) |
|||
} |
|||
if destTableCount != 0 { |
|||
t.Fatalf("The destination database is not empty; %v table(s) found.", destTableCount) |
|||
} |
|||
|
|||
// Prepare to perform the backup.
|
|||
backup, err := destDbDriverConn.Backup("main", srcDbDriverConn, "main") |
|||
if err != nil { |
|||
t.Fatal("Failed to initialize the backup:", err) |
|||
} |
|||
|
|||
// Allow the initial page count and remaining values to be retrieved.
|
|||
// According to <https://www.sqlite.org/c3ref/backup_finish.html>, the page count and remaining values are "... only updated by sqlite3_backup_step()."
|
|||
isDone, err := backup.Step(0) |
|||
if err != nil { |
|||
t.Fatal("Unable to perform an initial 0-page backup step:", err) |
|||
} |
|||
if isDone { |
|||
t.Fatal("Backup is unexpectedly done.") |
|||
} |
|||
|
|||
// Check that the page count and remaining values are reasonable.
|
|||
initialPageCount := backup.PageCount() |
|||
if initialPageCount <= 0 { |
|||
t.Fatalf("Unexpected initial page count value: %v", initialPageCount) |
|||
} |
|||
initialRemaining := backup.Remaining() |
|||
if initialRemaining <= 0 { |
|||
t.Fatalf("Unexpected initial remaining value: %v", initialRemaining) |
|||
} |
|||
if initialRemaining != initialPageCount { |
|||
t.Fatalf("Initial remaining value differs from the initial page count value; remaining: %v; page count: %v", initialRemaining, initialPageCount) |
|||
} |
|||
|
|||
// Perform the backup.
|
|||
if usePerPageSteps { |
|||
var startTime = time.Now().Unix() |
|||
|
|||
// Test backing-up using a page-by-page approach.
|
|||
var latestRemaining = initialRemaining |
|||
for { |
|||
// Perform the backup step.
|
|||
isDone, err = backup.Step(1) |
|||
if err != nil { |
|||
t.Fatal("Failed to perform a backup step:", err) |
|||
} |
|||
|
|||
// The page count should remain unchanged from its initial value.
|
|||
currentPageCount := backup.PageCount() |
|||
if currentPageCount != initialPageCount { |
|||
t.Fatalf("Current page count differs from the initial page count; initial page count: %v; current page count: %v", initialPageCount, currentPageCount) |
|||
} |
|||
|
|||
// There should now be one less page remaining.
|
|||
currentRemaining := backup.Remaining() |
|||
expectedRemaining := latestRemaining - 1 |
|||
if currentRemaining != expectedRemaining { |
|||
t.Fatalf("Unexpected remaining value; expected remaining value: %v; actual remaining value: %v", expectedRemaining, currentRemaining) |
|||
} |
|||
latestRemaining = currentRemaining |
|||
|
|||
if isDone { |
|||
break |
|||
} |
|||
|
|||
// Limit the runtime of the backup attempt.
|
|||
if (time.Now().Unix() - startTime) > usePagePerStepsTimeoutSeconds { |
|||
t.Fatal("Backup is taking longer than expected.") |
|||
} |
|||
} |
|||
} else { |
|||
// Test the copying of all remaining pages.
|
|||
isDone, err = backup.Step(-1) |
|||
if err != nil { |
|||
t.Fatal("Failed to perform a backup step:", err) |
|||
} |
|||
if !isDone { |
|||
t.Fatal("Backup is unexpectedly not done.") |
|||
} |
|||
} |
|||
|
|||
// Check that the page count and remaining values are reasonable.
|
|||
finalPageCount := backup.PageCount() |
|||
if finalPageCount != initialPageCount { |
|||
t.Fatalf("Final page count differs from the initial page count; initial page count: %v; final page count: %v", initialPageCount, finalPageCount) |
|||
} |
|||
finalRemaining := backup.Remaining() |
|||
if finalRemaining != 0 { |
|||
t.Fatalf("Unexpected remaining value: %v", finalRemaining) |
|||
} |
|||
|
|||
// Finish the backup.
|
|||
err = backup.Finish() |
|||
if err != nil { |
|||
t.Fatal("Failed to finish backup:", err) |
|||
} |
|||
|
|||
// Confirm that the "test" table now exists in the destination database.
|
|||
var doesTestTableExist bool |
|||
err = destDb.QueryRow("SELECT EXISTS (SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'test' LIMIT 1) AS test_table_exists").Scan(&doesTestTableExist) |
|||
if err != nil { |
|||
t.Fatal("Failed to check if the \"test\" table exists in the destination database:", err) |
|||
} |
|||
if !doesTestTableExist { |
|||
t.Fatal("The \"test\" table could not be found in the destination database.") |
|||
} |
|||
|
|||
// Confirm that the number of rows in the destination database's "test" table matches that of the source table.
|
|||
var actualTestTableRowCount int |
|||
err = destDb.QueryRow("SELECT COUNT(*) FROM test").Scan(&actualTestTableRowCount) |
|||
if err != nil { |
|||
t.Fatal("Failed to determine the rowcount of the \"test\" table in the destination database:", err) |
|||
} |
|||
if testRowCount != actualTestTableRowCount { |
|||
t.Fatalf("Unexpected destination \"test\" table row count; expected: %v; found: %v", testRowCount, actualTestTableRowCount) |
|||
} |
|||
|
|||
// Check each of the rows in the destination database.
|
|||
for id := 0; id < testRowCount; id++ { |
|||
var checkedValue string |
|||
err = destDb.QueryRow("SELECT value FROM test WHERE id = ?", id).Scan(&checkedValue) |
|||
if err != nil { |
|||
t.Fatal("Failed to query the \"test\" table in the destination database:", err) |
|||
} |
|||
|
|||
var expectedValue = generateTestData(id) |
|||
if checkedValue != expectedValue { |
|||
t.Fatalf("Unexpected value in the \"test\" table in the destination database; expected value: %v; actual value: %v", expectedValue, checkedValue) |
|||
} |
|||
} |
|||
} |
|||
|
|||
func TestBackupStepByStep(t *testing.T) { |
|||
testBackup(t, testRowCount, true) |
|||
} |
|||
|
|||
func TestBackupAllRemainingPages(t *testing.T) { |
|||
testBackup(t, testRowCount, false) |
|||
} |
|||
|
|||
// Test the error reporting when preparing to perform a backup.
|
|||
func TestBackupError(t *testing.T) { |
|||
const driverName = "sqlite3_TestBackupError" |
|||
|
|||
// The driver's connection will be needed in order to perform the backup.
|
|||
var dbDriverConn *SQLiteConn |
|||
sql.Register(driverName, &SQLiteDriver{ |
|||
ConnectHook: func(conn *SQLiteConn) error { |
|||
dbDriverConn = conn |
|||
return nil |
|||
}, |
|||
}) |
|||
|
|||
// Connect to the database.
|
|||
dbTempFilename := TempFilename(t) |
|||
defer os.Remove(dbTempFilename) |
|||
db, err := sql.Open(driverName, dbTempFilename) |
|||
if err != nil { |
|||
t.Fatal("Failed to open the database:", err) |
|||
} |
|||
defer db.Close() |
|||
db.Ping() |
|||
|
|||
// Need the driver connection in order to perform the backup.
|
|||
if dbDriverConn == nil { |
|||
t.Fatal("Failed to get the driver connection.") |
|||
} |
|||
|
|||
// Prepare to perform the backup.
|
|||
// Intentionally using the same connection for both the source and destination databases, to trigger an error result.
|
|||
backup, err := dbDriverConn.Backup("main", dbDriverConn, "main") |
|||
if err == nil { |
|||
t.Fatal("Failed to get the expected error result.") |
|||
} |
|||
const expectedError = "source and destination must be distinct" |
|||
if err.Error() != expectedError { |
|||
t.Fatalf("Unexpected error message; expected value: \"%v\"; actual value: \"%v\"", expectedError, err.Error()) |
|||
} |
|||
if backup != nil { |
|||
t.Fatal("Failed to get the expected nil backup result.") |
|||
} |
|||
} |
15321
vendor/src/github.com/mattn/go-sqlite3/sqlite3-binding.c
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
1701
vendor/src/github.com/mattn/go-sqlite3/sqlite3-binding.h
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,78 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"archive/zip" |
|||
"bytes" |
|||
"fmt" |
|||
"io" |
|||
"io/ioutil" |
|||
"log" |
|||
"net/http" |
|||
"os" |
|||
"path" |
|||
"path/filepath" |
|||
"strings" |
|||
|
|||
"github.com/PuerkitoBio/goquery" |
|||
) |
|||
|
|||
func main() { |
|||
site := "https://www.sqlite.org/download.html" |
|||
fmt.Printf("scraping %v\n", site) |
|||
doc, err := goquery.NewDocument(site) |
|||
if err != nil { |
|||
log.Fatal(err) |
|||
} |
|||
var url string |
|||
doc.Find("a").Each(func(_ int, s *goquery.Selection) { |
|||
if url == "" && strings.HasPrefix(s.Text(), "sqlite-amalgamation-") { |
|||
url = "https://www.sqlite.org/2016/" + s.Text() |
|||
} |
|||
}) |
|||
if url == "" { |
|||
return |
|||
} |
|||
fmt.Printf("downloading %v\n", url) |
|||
resp, err := http.Get(url) |
|||
if err != nil { |
|||
log.Fatal(err) |
|||
} |
|||
defer resp.Body.Close() |
|||
|
|||
b, err := ioutil.ReadAll(resp.Body) |
|||
if err != nil { |
|||
log.Fatal(err) |
|||
} |
|||
|
|||
fmt.Printf("extracting %v\n", path.Base(url)) |
|||
r, err := zip.NewReader(bytes.NewReader(b), resp.ContentLength) |
|||
if err != nil { |
|||
log.Fatal(err) |
|||
} |
|||
for _, zf := range r.File { |
|||
var f *os.File |
|||
switch path.Base(zf.Name) { |
|||
case "sqlite3.c": |
|||
f, err = os.Create("sqlite3-binding.c") |
|||
case "sqlite3.h": |
|||
f, err = os.Create("sqlite3-binding.h") |
|||
case "sqlite3ext.h": |
|||
f, err = os.Create("sqlite3ext.h") |
|||
default: |
|||
continue |
|||
} |
|||
if err != nil { |
|||
log.Fatal(err) |
|||
} |
|||
zr, err := zf.Open() |
|||
if err != nil { |
|||
log.Fatal(err) |
|||
} |
|||
_, err = io.Copy(f, zr) |
|||
f.Close() |
|||
if err != nil { |
|||
log.Fatal(err) |
|||
} |
|||
fmt.Printf("extracted %v\n", filepath.Base(f.Name())) |
|||
} |
|||
} |
@ -0,0 +1,415 @@ |
|||
// Copyright (C) 2016 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|||
// TODO: add "Gimpl do foo" team?
|
|||
//
|
|||
// Use of this source code is governed by an MIT-style
|
|||
// license that can be found in the LICENSE file.
|
|||
// +build trace
|
|||
|
|||
package sqlite3 |
|||
|
|||
/* |
|||
#ifndef USE_LIBSQLITE3 |
|||
#include <sqlite3-binding.h> |
|||
#else |
|||
#include <sqlite3.h> |
|||
#endif |
|||
#include <stdlib.h> |
|||
|
|||
void stepTrampoline(sqlite3_context*, int, sqlite3_value**); |
|||
void doneTrampoline(sqlite3_context*); |
|||
void traceCallbackTrampoline(unsigned traceEventCode, void *ctx, void *p, void *x); |
|||
*/ |
|||
import "C" |
|||
|
|||
import ( |
|||
"errors" |
|||
"fmt" |
|||
"reflect" |
|||
"strings" |
|||
"sync" |
|||
"unsafe" |
|||
) |
|||
|
|||
// Trace... constants identify the possible events causing callback invocation.
|
|||
// Values are same as the corresponding SQLite Trace Event Codes.
|
|||
const ( |
|||
TraceStmt = C.SQLITE_TRACE_STMT |
|||
TraceProfile = C.SQLITE_TRACE_PROFILE |
|||
TraceRow = C.SQLITE_TRACE_ROW |
|||
TraceClose = C.SQLITE_TRACE_CLOSE |
|||
) |
|||
|
|||
type TraceInfo struct { |
|||
// Pack together the shorter fields, to keep the struct smaller.
|
|||
// On a 64-bit machine there would be padding
|
|||
// between EventCode and ConnHandle; having AutoCommit here is "free":
|
|||
EventCode uint32 |
|||
AutoCommit bool |
|||
ConnHandle uintptr |
|||
|
|||
// Usually filled, unless EventCode = TraceClose = SQLITE_TRACE_CLOSE:
|
|||
// identifier for a prepared statement:
|
|||
StmtHandle uintptr |
|||
|
|||
// Two strings filled when EventCode = TraceStmt = SQLITE_TRACE_STMT:
|
|||
// (1) either the unexpanded SQL text of the prepared statement, or
|
|||
// an SQL comment that indicates the invocation of a trigger;
|
|||
// (2) expanded SQL, if requested and if (1) is not an SQL comment.
|
|||
StmtOrTrigger string |
|||
ExpandedSQL string // only if requested (TraceConfig.WantExpandedSQL = true)
|
|||
|
|||
// filled when EventCode = TraceProfile = SQLITE_TRACE_PROFILE:
|
|||
// estimated number of nanoseconds that the prepared statement took to run:
|
|||
RunTimeNanosec int64 |
|||
|
|||
DBError Error |
|||
} |
|||
|
|||
// TraceUserCallback gives the signature for a trace function
|
|||
// provided by the user (Go application programmer).
|
|||
// SQLite 3.14 documentation (as of September 2, 2016)
|
|||
// for SQL Trace Hook = sqlite3_trace_v2():
|
|||
// The integer return value from the callback is currently ignored,
|
|||
// though this may change in future releases. Callback implementations
|
|||
// should return zero to ensure future compatibility.
|
|||
type TraceUserCallback func(TraceInfo) int |
|||
|
|||
type TraceConfig struct { |
|||
Callback TraceUserCallback |
|||
EventMask uint |
|||
WantExpandedSQL bool |
|||
} |
|||
|
|||
func fillDBError(dbErr *Error, db *C.sqlite3) { |
|||
// See SQLiteConn.lastError(), in file 'sqlite3.go' at the time of writing (Sept 5, 2016)
|
|||
dbErr.Code = ErrNo(C.sqlite3_errcode(db)) |
|||
dbErr.ExtendedCode = ErrNoExtended(C.sqlite3_extended_errcode(db)) |
|||
dbErr.err = C.GoString(C.sqlite3_errmsg(db)) |
|||
} |
|||
|
|||
func fillExpandedSQL(info *TraceInfo, db *C.sqlite3, pStmt unsafe.Pointer) { |
|||
if pStmt == nil { |
|||
panic("No SQLite statement pointer in P arg of trace_v2 callback") |
|||
} |
|||
|
|||
expSQLiteCStr := C.sqlite3_expanded_sql((*C.sqlite3_stmt)(pStmt)) |
|||
if expSQLiteCStr == nil { |
|||
fillDBError(&info.DBError, db) |
|||
return |
|||
} |
|||
info.ExpandedSQL = C.GoString(expSQLiteCStr) |
|||
} |
|||
|
|||
//export traceCallbackTrampoline
|
|||
func traceCallbackTrampoline( |
|||
traceEventCode uint, |
|||
// Parameter named 'C' in SQLite docs = Context given at registration:
|
|||
ctx unsafe.Pointer, |
|||
// Parameter named 'P' in SQLite docs (Primary event data?):
|
|||
p unsafe.Pointer, |
|||
// Parameter named 'X' in SQLite docs (eXtra event data?):
|
|||
xValue unsafe.Pointer) int { |
|||
|
|||
if ctx == nil { |
|||
panic(fmt.Sprintf("No context (ev 0x%x)", traceEventCode)) |
|||
} |
|||
|
|||
contextDB := (*C.sqlite3)(ctx) |
|||
connHandle := uintptr(ctx) |
|||
|
|||
var traceConf TraceConfig |
|||
var found bool |
|||
if traceEventCode == TraceClose { |
|||
// clean up traceMap: 'pop' means get and delete
|
|||
traceConf, found = popTraceMapping(connHandle) |
|||
} else { |
|||
traceConf, found = lookupTraceMapping(connHandle) |
|||
} |
|||
|
|||
if !found { |
|||
panic(fmt.Sprintf("Mapping not found for handle 0x%x (ev 0x%x)", |
|||
connHandle, traceEventCode)) |
|||
} |
|||
|
|||
var info TraceInfo |
|||
|
|||
info.EventCode = uint32(traceEventCode) |
|||
info.AutoCommit = (int(C.sqlite3_get_autocommit(contextDB)) != 0) |
|||
info.ConnHandle = connHandle |
|||
|
|||
switch traceEventCode { |
|||
case TraceStmt: |
|||
info.StmtHandle = uintptr(p) |
|||
|
|||
var xStr string |
|||
if xValue != nil { |
|||
xStr = C.GoString((*C.char)(xValue)) |
|||
} |
|||
info.StmtOrTrigger = xStr |
|||
if !strings.HasPrefix(xStr, "--") { |
|||
// Not SQL comment, therefore the current event
|
|||
// is not related to a trigger.
|
|||
// The user might want to receive the expanded SQL;
|
|||
// let's check:
|
|||
if traceConf.WantExpandedSQL { |
|||
fillExpandedSQL(&info, contextDB, p) |
|||
} |
|||
} |
|||
|
|||
case TraceProfile: |
|||
info.StmtHandle = uintptr(p) |
|||
|
|||
if xValue == nil { |
|||
panic("NULL pointer in X arg of trace_v2 callback for SQLITE_TRACE_PROFILE event") |
|||
} |
|||
|
|||
info.RunTimeNanosec = *(*int64)(xValue) |
|||
|
|||
// sample the error //TODO: is it safe? is it useful?
|
|||
fillDBError(&info.DBError, contextDB) |
|||
|
|||
case TraceRow: |
|||
info.StmtHandle = uintptr(p) |
|||
|
|||
case TraceClose: |
|||
handle := uintptr(p) |
|||
if handle != info.ConnHandle { |
|||
panic(fmt.Sprintf("Different conn handle 0x%x (expected 0x%x) in SQLITE_TRACE_CLOSE event.", |
|||
handle, info.ConnHandle)) |
|||
} |
|||
|
|||
default: |
|||
// Pass unsupported events to the user callback (if configured);
|
|||
// let the user callback decide whether to panic or ignore them.
|
|||
} |
|||
|
|||
// Do not execute user callback when the event was not requested by user!
|
|||
// Remember that the Close event is always selected when
|
|||
// registering this callback trampoline with SQLite --- for cleanup.
|
|||
// In the future there may be more events forced to "selected" in SQLite
|
|||
// for the driver's needs.
|
|||
if traceConf.EventMask&traceEventCode == 0 { |
|||
return 0 |
|||
} |
|||
|
|||
r := 0 |
|||
if traceConf.Callback != nil { |
|||
r = traceConf.Callback(info) |
|||
} |
|||
return r |
|||
} |
|||
|
|||
type traceMapEntry struct { |
|||
config TraceConfig |
|||
} |
|||
|
|||
var traceMapLock sync.Mutex |
|||
var traceMap = make(map[uintptr]traceMapEntry) |
|||
|
|||
func addTraceMapping(connHandle uintptr, traceConf TraceConfig) { |
|||
traceMapLock.Lock() |
|||
defer traceMapLock.Unlock() |
|||
|
|||
oldEntryCopy, found := traceMap[connHandle] |
|||
if found { |
|||
panic(fmt.Sprintf("Adding trace config %v: handle 0x%x already registered (%v).", |
|||
traceConf, connHandle, oldEntryCopy.config)) |
|||
} |
|||
traceMap[connHandle] = traceMapEntry{config: traceConf} |
|||
fmt.Printf("Added trace config %v: handle 0x%x.\n", traceConf, connHandle) |
|||
} |
|||
|
|||
func lookupTraceMapping(connHandle uintptr) (TraceConfig, bool) { |
|||
traceMapLock.Lock() |
|||
defer traceMapLock.Unlock() |
|||
|
|||
entryCopy, found := traceMap[connHandle] |
|||
return entryCopy.config, found |
|||
} |
|||
|
|||
// 'pop' = get and delete from map before returning the value to the caller
|
|||
func popTraceMapping(connHandle uintptr) (TraceConfig, bool) { |
|||
traceMapLock.Lock() |
|||
defer traceMapLock.Unlock() |
|||
|
|||
entryCopy, found := traceMap[connHandle] |
|||
if found { |
|||
delete(traceMap, connHandle) |
|||
fmt.Printf("Pop handle 0x%x: deleted trace config %v.\n", connHandle, entryCopy.config) |
|||
} |
|||
return entryCopy.config, found |
|||
} |
|||
|
|||
// RegisterAggregator makes a Go type available as a SQLite aggregation function.
|
|||
//
|
|||
// Because aggregation is incremental, it's implemented in Go with a
|
|||
// type that has 2 methods: func Step(values) accumulates one row of
|
|||
// data into the accumulator, and func Done() ret finalizes and
|
|||
// returns the aggregate value. "values" and "ret" may be any type
|
|||
// supported by RegisterFunc.
|
|||
//
|
|||
// RegisterAggregator takes as implementation a constructor function
|
|||
// that constructs an instance of the aggregator type each time an
|
|||
// aggregation begins. The constructor must return a pointer to a
|
|||
// type, or an interface that implements Step() and Done().
|
|||
//
|
|||
// The constructor function and the Step/Done methods may optionally
|
|||
// return an error in addition to their other return values.
|
|||
//
|
|||
// See _example/go_custom_funcs for a detailed example.
|
|||
func (c *SQLiteConn) RegisterAggregator(name string, impl interface{}, pure bool) error { |
|||
var ai aggInfo |
|||
ai.constructor = reflect.ValueOf(impl) |
|||
t := ai.constructor.Type() |
|||
if t.Kind() != reflect.Func { |
|||
return errors.New("non-function passed to RegisterAggregator") |
|||
} |
|||
if t.NumOut() != 1 && t.NumOut() != 2 { |
|||
return errors.New("SQLite aggregator constructors must return 1 or 2 values") |
|||
} |
|||
if t.NumOut() == 2 && !t.Out(1).Implements(reflect.TypeOf((*error)(nil)).Elem()) { |
|||
return errors.New("Second return value of SQLite function must be error") |
|||
} |
|||
if t.NumIn() != 0 { |
|||
return errors.New("SQLite aggregator constructors must not have arguments") |
|||
} |
|||
|
|||
agg := t.Out(0) |
|||
switch agg.Kind() { |
|||
case reflect.Ptr, reflect.Interface: |
|||
default: |
|||
return errors.New("SQlite aggregator constructor must return a pointer object") |
|||
} |
|||
stepFn, found := agg.MethodByName("Step") |
|||
if !found { |
|||
return errors.New("SQlite aggregator doesn't have a Step() function") |
|||
} |
|||
step := stepFn.Type |
|||
if step.NumOut() != 0 && step.NumOut() != 1 { |
|||
return errors.New("SQlite aggregator Step() function must return 0 or 1 values") |
|||
} |
|||
if step.NumOut() == 1 && !step.Out(0).Implements(reflect.TypeOf((*error)(nil)).Elem()) { |
|||
return errors.New("type of SQlite aggregator Step() return value must be error") |
|||
} |
|||
|
|||
stepNArgs := step.NumIn() |
|||
start := 0 |
|||
if agg.Kind() == reflect.Ptr { |
|||
// Skip over the method receiver
|
|||
stepNArgs-- |
|||
start++ |
|||
} |
|||
if step.IsVariadic() { |
|||
stepNArgs-- |
|||
} |
|||
for i := start; i < start+stepNArgs; i++ { |
|||
conv, err := callbackArg(step.In(i)) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
ai.stepArgConverters = append(ai.stepArgConverters, conv) |
|||
} |
|||
if step.IsVariadic() { |
|||
conv, err := callbackArg(t.In(start + stepNArgs).Elem()) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
ai.stepVariadicConverter = conv |
|||
// Pass -1 to sqlite so that it allows any number of
|
|||
// arguments. The call helper verifies that the minimum number
|
|||
// of arguments is present for variadic functions.
|
|||
stepNArgs = -1 |
|||
} |
|||
|
|||
doneFn, found := agg.MethodByName("Done") |
|||
if !found { |
|||
return errors.New("SQlite aggregator doesn't have a Done() function") |
|||
} |
|||
done := doneFn.Type |
|||
doneNArgs := done.NumIn() |
|||
if agg.Kind() == reflect.Ptr { |
|||
// Skip over the method receiver
|
|||
doneNArgs-- |
|||
} |
|||
if doneNArgs != 0 { |
|||
return errors.New("SQlite aggregator Done() function must have no arguments") |
|||
} |
|||
if done.NumOut() != 1 && done.NumOut() != 2 { |
|||
return errors.New("SQLite aggregator Done() function must return 1 or 2 values") |
|||
} |
|||
if done.NumOut() == 2 && !done.Out(1).Implements(reflect.TypeOf((*error)(nil)).Elem()) { |
|||
return errors.New("second return value of SQLite aggregator Done() function must be error") |
|||
} |
|||
|
|||
conv, err := callbackRet(done.Out(0)) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
ai.doneRetConverter = conv |
|||
ai.active = make(map[int64]reflect.Value) |
|||
ai.next = 1 |
|||
|
|||
// ai must outlast the database connection, or we'll have dangling pointers.
|
|||
c.aggregators = append(c.aggregators, &ai) |
|||
|
|||
cname := C.CString(name) |
|||
defer C.free(unsafe.Pointer(cname)) |
|||
opts := C.SQLITE_UTF8 |
|||
if pure { |
|||
opts |= C.SQLITE_DETERMINISTIC |
|||
} |
|||
rv := C._sqlite3_create_function(c.db, cname, C.int(stepNArgs), C.int(opts), C.uintptr_t(newHandle(c, &ai)), nil, (*[0]byte)(unsafe.Pointer(C.stepTrampoline)), (*[0]byte)(unsafe.Pointer(C.doneTrampoline))) |
|||
if rv != C.SQLITE_OK { |
|||
return c.lastError() |
|||
} |
|||
return nil |
|||
} |
|||
|
|||
// SetTrace installs or removes the trace callback for the given database connection.
|
|||
// It's not named 'RegisterTrace' because only one callback can be kept and called.
|
|||
// Calling SetTrace a second time on same database connection
|
|||
// overrides (cancels) any prior callback and all its settings:
|
|||
// event mask, etc.
|
|||
func (c *SQLiteConn) SetTrace(requested *TraceConfig) error { |
|||
connHandle := uintptr(unsafe.Pointer(c.db)) |
|||
|
|||
_, _ = popTraceMapping(connHandle) |
|||
|
|||
if requested == nil { |
|||
// The traceMap entry was deleted already by popTraceMapping():
|
|||
// can disable all events now, no need to watch for TraceClose.
|
|||
err := c.setSQLiteTrace(0) |
|||
return err |
|||
} |
|||
|
|||
reqCopy := *requested |
|||
|
|||
// Disable potentially expensive operations
|
|||
// if their result will not be used. We are doing this
|
|||
// just in case the caller provided nonsensical input.
|
|||
if reqCopy.EventMask&TraceStmt == 0 { |
|||
reqCopy.WantExpandedSQL = false |
|||
} |
|||
|
|||
addTraceMapping(connHandle, reqCopy) |
|||
|
|||
// The callback trampoline function does cleanup on Close event,
|
|||
// regardless of the presence or absence of the user callback.
|
|||
// Therefore it needs the Close event to be selected:
|
|||
actualEventMask := reqCopy.EventMask | TraceClose |
|||
err := c.setSQLiteTrace(actualEventMask) |
|||
return err |
|||
} |
|||
|
|||
func (c *SQLiteConn) setSQLiteTrace(sqliteEventMask uint) error { |
|||
rv := C.sqlite3_trace_v2(c.db, |
|||
C.uint(sqliteEventMask), |
|||
(*[0]byte)(unsafe.Pointer(C.traceCallbackTrampoline)), |
|||
unsafe.Pointer(c.db)) // Fourth arg is same as first: we are
|
|||
// passing the database connection handle as callback context.
|
|||
|
|||
if rv != C.SQLITE_OK { |
|||
return c.lastError() |
|||
} |
|||
return nil |
|||
} |
@ -0,0 +1,9 @@ |
|||
// +build !trace
|
|||
|
|||
package sqlite3 |
|||
|
|||
import "errors" |
|||
|
|||
func (c *SQLiteConn) RegisterAggregator(name string, impl interface{}, pure bool) error { |
|||
return errors.New("This feature is not implemented") |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue