main.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. package main
  2. import (
  3. "database/sql"
  4. "fmt"
  5. "log"
  6. "os"
  7. sqlite3 "github.com/mattn/go-sqlite3"
  8. )
  9. func traceCallback(info sqlite3.TraceInfo) int {
  10. // Not very readable but may be useful; uncomment next line in case of doubt:
  11. //fmt.Printf("Trace: %#v\n", info)
  12. var dbErrText string
  13. if info.DBError.Code != 0 || info.DBError.ExtendedCode != 0 {
  14. dbErrText = fmt.Sprintf("; DB error: %#v", info.DBError)
  15. } else {
  16. dbErrText = "."
  17. }
  18. // Show the Statement-or-Trigger text in curly braces ('{', '}')
  19. // since from the *paired* ASCII characters they are
  20. // the least used in SQL syntax, therefore better visual delimiters.
  21. // Maybe show 'ExpandedSQL' the same way as 'StmtOrTrigger'.
  22. //
  23. // A known use of curly braces (outside strings) is
  24. // for ODBC escape sequences. Not likely to appear here.
  25. //
  26. // Template languages, etc. don't matter, we should see their *result*
  27. // at *this* level.
  28. // Strange curly braces in SQL code that reached the database driver
  29. // suggest that there is a bug in the application.
  30. // The braces are likely to be either template syntax or
  31. // a programming language's string interpolation syntax.
  32. var expandedText string
  33. if info.ExpandedSQL != "" {
  34. if info.ExpandedSQL == info.StmtOrTrigger {
  35. expandedText = " = exp"
  36. } else {
  37. expandedText = fmt.Sprintf(" expanded {%q}", info.ExpandedSQL)
  38. }
  39. } else {
  40. expandedText = ""
  41. }
  42. // SQLite docs as of September 6, 2016: Tracing and Profiling Functions
  43. // https://www.sqlite.org/c3ref/profile.html
  44. //
  45. // The profile callback time is in units of nanoseconds, however
  46. // the current implementation is only capable of millisecond resolution
  47. // so the six least significant digits in the time are meaningless.
  48. // Future versions of SQLite might provide greater resolution on the profiler callback.
  49. var runTimeText string
  50. if info.RunTimeNanosec == 0 {
  51. if info.EventCode == sqlite3.TraceProfile {
  52. //runTimeText = "; no time" // seems confusing
  53. runTimeText = "; time 0" // no measurement unit
  54. } else {
  55. //runTimeText = "; no time" // seems useless and confusing
  56. }
  57. } else {
  58. const nanosPerMillisec = 1000000
  59. if info.RunTimeNanosec%nanosPerMillisec == 0 {
  60. runTimeText = fmt.Sprintf("; time %d ms", info.RunTimeNanosec/nanosPerMillisec)
  61. } else {
  62. // unexpected: better than millisecond resolution
  63. runTimeText = fmt.Sprintf("; time %d ns!!!", info.RunTimeNanosec)
  64. }
  65. }
  66. var modeText string
  67. if info.AutoCommit {
  68. modeText = "-AC-"
  69. } else {
  70. modeText = "+Tx+"
  71. }
  72. fmt.Printf("Trace: ev %d %s conn 0x%x, stmt 0x%x {%q}%s%s%s\n",
  73. info.EventCode, modeText, info.ConnHandle, info.StmtHandle,
  74. info.StmtOrTrigger, expandedText,
  75. runTimeText,
  76. dbErrText)
  77. return 0
  78. }
  79. func main() {
  80. eventMask := sqlite3.TraceStmt | sqlite3.TraceProfile | sqlite3.TraceRow | sqlite3.TraceClose
  81. sql.Register("sqlite3_tracing",
  82. &sqlite3.SQLiteDriver{
  83. ConnectHook: func(conn *sqlite3.SQLiteConn) error {
  84. err := conn.SetTrace(&sqlite3.TraceConfig{
  85. Callback: traceCallback,
  86. EventMask: eventMask,
  87. WantExpandedSQL: true,
  88. })
  89. return err
  90. },
  91. })
  92. os.Exit(dbMain())
  93. }
  94. // Harder to do DB work in main().
  95. // It's better with a separate function because
  96. // 'defer' and 'os.Exit' don't go well together.
  97. //
  98. // DO NOT use 'log.Fatal...' below: remember that it's equivalent to
  99. // Print() followed by a call to os.Exit(1) --- and
  100. // we want to avoid Exit() so 'defer' can do cleanup.
  101. // Use 'log.Panic...' instead.
  102. func dbMain() int {
  103. db, err := sql.Open("sqlite3_tracing", ":memory:")
  104. if err != nil {
  105. fmt.Printf("Failed to open database: %#+v\n", err)
  106. return 1
  107. }
  108. defer db.Close()
  109. err = db.Ping()
  110. if err != nil {
  111. log.Panic(err)
  112. }
  113. dbSetup(db)
  114. dbDoInsert(db)
  115. dbDoInsertPrepared(db)
  116. dbDoSelect(db)
  117. dbDoSelectPrepared(db)
  118. return 0
  119. }
  120. // 'DDL' stands for "Data Definition Language":
  121. // Note: "INTEGER PRIMARY KEY NOT NULL AUTOINCREMENT" causes the error
  122. // 'near "AUTOINCREMENT": syntax error'; without "NOT NULL" it works.
  123. const tableDDL = `CREATE TABLE t1 (
  124. id INTEGER PRIMARY KEY AUTOINCREMENT,
  125. note VARCHAR NOT NULL
  126. )`
  127. // 'DML' stands for "Data Manipulation Language":
  128. const insertDML = "INSERT INTO t1 (note) VALUES (?)"
  129. const selectDML = "SELECT id, note FROM t1 WHERE note LIKE ?"
  130. const textPrefix = "bla-1234567890-"
  131. const noteTextPattern = "%Prep%"
  132. const nGenRows = 4 // Number of Rows to Generate (for *each* approach tested)
  133. func dbSetup(db *sql.DB) {
  134. var err error
  135. _, err = db.Exec("DROP TABLE IF EXISTS t1")
  136. if err != nil {
  137. log.Panic(err)
  138. }
  139. _, err = db.Exec(tableDDL)
  140. if err != nil {
  141. log.Panic(err)
  142. }
  143. }
  144. func dbDoInsert(db *sql.DB) {
  145. const Descr = "DB-Exec"
  146. for i := 0; i < nGenRows; i++ {
  147. result, err := db.Exec(insertDML, textPrefix+Descr)
  148. if err != nil {
  149. log.Panic(err)
  150. }
  151. resultDoCheck(result, Descr, i)
  152. }
  153. }
  154. func dbDoInsertPrepared(db *sql.DB) {
  155. const Descr = "DB-Prepare"
  156. stmt, err := db.Prepare(insertDML)
  157. if err != nil {
  158. log.Panic(err)
  159. }
  160. defer stmt.Close()
  161. for i := 0; i < nGenRows; i++ {
  162. result, err := stmt.Exec(textPrefix + Descr)
  163. if err != nil {
  164. log.Panic(err)
  165. }
  166. resultDoCheck(result, Descr, i)
  167. }
  168. }
  169. func resultDoCheck(result sql.Result, callerDescr string, callIndex int) {
  170. lastID, err := result.LastInsertId()
  171. if err != nil {
  172. log.Panic(err)
  173. }
  174. nAffected, err := result.RowsAffected()
  175. if err != nil {
  176. log.Panic(err)
  177. }
  178. log.Printf("Exec result for %s (%d): ID = %d, affected = %d\n", callerDescr, callIndex, lastID, nAffected)
  179. }
  180. func dbDoSelect(db *sql.DB) {
  181. const Descr = "DB-Query"
  182. rows, err := db.Query(selectDML, noteTextPattern)
  183. if err != nil {
  184. log.Panic(err)
  185. }
  186. defer rows.Close()
  187. rowsDoFetch(rows, Descr)
  188. }
  189. func dbDoSelectPrepared(db *sql.DB) {
  190. const Descr = "DB-Prepare"
  191. stmt, err := db.Prepare(selectDML)
  192. if err != nil {
  193. log.Panic(err)
  194. }
  195. defer stmt.Close()
  196. rows, err := stmt.Query(noteTextPattern)
  197. if err != nil {
  198. log.Panic(err)
  199. }
  200. defer rows.Close()
  201. rowsDoFetch(rows, Descr)
  202. }
  203. func rowsDoFetch(rows *sql.Rows, callerDescr string) {
  204. var nRows int
  205. var id int64
  206. var note string
  207. for rows.Next() {
  208. err := rows.Scan(&id, &note)
  209. if err != nil {
  210. log.Panic(err)
  211. }
  212. log.Printf("Row for %s (%d): id=%d, note=%q\n",
  213. callerDescr, nRows, id, note)
  214. nRows++
  215. }
  216. if err := rows.Err(); err != nil {
  217. log.Panic(err)
  218. }
  219. log.Printf("Total %d rows for %s.\n", nRows, callerDescr)
  220. }