PageRenderTime 1611ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/kythe/go/storage/sql/sql.go

https://gitlab.com/kidaa/kythe
Go | 295 lines | 239 code | 26 blank | 30 comment | 55 complexity | 5b0ae840fad93593d073a01217ef63d1 MD5 | raw file
  1. /*
  2. * Copyright 2014 Google Inc. All rights reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. // Package sql implements a graphstore.Service using a SQL database backend. A
  17. // specialized implementation for an xrefs.Service is also available.
  18. package sql
  19. import (
  20. "database/sql"
  21. "errors"
  22. "fmt"
  23. "io"
  24. "log"
  25. "strings"
  26. "kythe.io/kythe/go/services/graphstore"
  27. "kythe.io/kythe/go/storage/gsutil"
  28. "golang.org/x/net/context"
  29. spb "kythe.io/kythe/proto/storage_proto"
  30. _ "github.com/mattn/go-sqlite3" // register the "sqlite3" driver
  31. )
  32. // SQLite3 is the standard database/sql driver name for sqlite3.
  33. const SQLite3 = "sqlite3"
  34. func init() {
  35. gsutil.Register(SQLite3, func(spec string) (graphstore.Service, error) { return Open(SQLite3, spec) })
  36. }
  37. const (
  38. tableName = "kythe"
  39. uniqueColumns = "source_signature, source_corpus, source_root, source_path, source_language, kind, fact, target_signature, target_corpus, target_root, target_path, target_language"
  40. columns = uniqueColumns + ", value"
  41. orderByClause = "ORDER BY " + columns
  42. initStmt = `PRAGMA synchronous = OFF;
  43. PRAGMA journal_mode = WAL;
  44. PRAGMA encoding = "UTF-8";
  45. PRAGMA case_sensitive_like = true;
  46. CREATE TABLE IF NOT EXISTS ` + tableName + " (" + columns + ", UNIQUE(" + uniqueColumns + "))"
  47. )
  48. // Prepared statements
  49. const (
  50. writeStmt = "INSERT OR REPLACE INTO kythe (" + columns + ") VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)"
  51. anchorEdgesStmt = `
  52. SELECT
  53. target_signature,
  54. target_corpus,
  55. target_root,
  56. target_path,
  57. target_language,
  58. kind
  59. FROM kythe
  60. WHERE kind != ""
  61. AND kind != ?
  62. AND kind NOT LIKE "!%" ESCAPE '!'
  63. AND source_signature = ?
  64. AND source_corpus = ?
  65. AND source_root = ?
  66. AND source_path = ?
  67. AND source_language = ?`
  68. edgesStmt = `
  69. SELECT
  70. target_signature,
  71. target_corpus,
  72. target_root,
  73. target_path,
  74. target_language,
  75. kind
  76. FROM kythe
  77. WHERE source_signature = ?
  78. AND source_corpus = ?
  79. AND source_root = ?
  80. AND source_path = ?
  81. AND source_language = ?
  82. AND kind != ""`
  83. revEdgesStmt = `
  84. SELECT
  85. source_signature,
  86. source_corpus,
  87. source_root,
  88. source_path,
  89. source_language,
  90. kind
  91. FROM kythe
  92. WHERE target_signature = ?
  93. AND target_corpus = ?
  94. AND target_root = ?
  95. AND target_path = ?
  96. AND target_language = ?
  97. AND kind != ""`
  98. nodeFactsStmt = `
  99. SELECT fact, value
  100. FROM kythe
  101. WHERE source_signature = ?
  102. AND source_corpus = ?
  103. AND source_root = ?
  104. AND source_language = ?
  105. AND source_path = ?
  106. AND kind = ""`
  107. )
  108. // DB is a wrapper around a sql.DB that implements graphstore.Service
  109. type DB struct {
  110. *sql.DB
  111. writeStmt *sql.Stmt
  112. nodeFactsStmt *sql.Stmt
  113. edgesStmt *sql.Stmt
  114. revEdgesStmt *sql.Stmt
  115. anchorEdgesStmt *sql.Stmt
  116. }
  117. // Open returns an opened SQL database at the given path that can be used as a GraphStore and
  118. // xrefs.Service.
  119. func Open(driverName, dataSourceName string) (*DB, error) {
  120. db, err := sql.Open(driverName, dataSourceName)
  121. if err != nil {
  122. return nil, err
  123. }
  124. db.SetMaxOpenConns(1) // TODO(schroederc): Without this, concurrent writers will get a "database
  125. // is locked" error from sqlite3. Unfortunately, writing
  126. // concurrently with a scan/read will now deadlock...
  127. if _, err := db.Exec(initStmt); err != nil {
  128. return nil, fmt.Errorf("error initializing SQL db: %v", err)
  129. }
  130. d := &DB{DB: db}
  131. d.writeStmt, err = db.Prepare(writeStmt)
  132. if err != nil {
  133. return nil, fmt.Errorf("error preparing write statement: %v", err)
  134. }
  135. d.anchorEdgesStmt, err = db.Prepare(anchorEdgesStmt)
  136. if err != nil {
  137. return nil, fmt.Errorf("error preparing anchor edges statement: %v", err)
  138. }
  139. d.edgesStmt, err = db.Prepare(edgesStmt)
  140. if err != nil {
  141. return nil, fmt.Errorf("error preparing edges statement: %v", err)
  142. }
  143. d.revEdgesStmt, err = db.Prepare(revEdgesStmt)
  144. if err != nil {
  145. return nil, fmt.Errorf("error preparing reverse edges statement: %v", err)
  146. }
  147. d.nodeFactsStmt, err = db.Prepare(nodeFactsStmt)
  148. if err != nil {
  149. return nil, fmt.Errorf("error preparing node facts statement: %v", err)
  150. }
  151. return d, nil
  152. }
  153. // Close implements part of the graphstore.Service interface.
  154. func (db *DB) Close(ctx context.Context) error { return db.DB.Close() }
  155. // Read implements part of the graphstore.Service interface.
  156. func (db *DB) Read(ctx context.Context, req *spb.ReadRequest, f graphstore.EntryFunc) (err error) {
  157. if req.GetSource() == nil {
  158. return errors.New("invalid ReadRequest: missing Source")
  159. }
  160. var rows *sql.Rows
  161. const baseQuery = "SELECT " + columns + " FROM " + tableName + " WHERE source_signature = ? AND source_corpus = ? AND source_root = ? AND source_language = ? AND source_path = ? "
  162. if req.EdgeKind == "*" {
  163. rows, err = db.Query(baseQuery+orderByClause, req.Source.Signature, req.Source.Corpus, req.Source.Root, req.Source.Language, req.Source.Path)
  164. } else {
  165. rows, err = db.Query(baseQuery+"AND kind = ? "+orderByClause, req.Source.Signature, req.Source.Corpus, req.Source.Root, req.Source.Language, req.Source.Path, req.EdgeKind)
  166. }
  167. if err != nil {
  168. return fmt.Errorf("sql select error: %v", err)
  169. }
  170. return scanEntries(rows, f)
  171. }
  172. var likeEscaper = strings.NewReplacer("%", "\t%", "_", "\t_")
  173. // Scan implements part of the graphstore.Service interface.
  174. //
  175. // TODO(fromberger): Maybe use prepared statements here.
  176. func (db *DB) Scan(ctx context.Context, req *spb.ScanRequest, f graphstore.EntryFunc) (err error) {
  177. var rows *sql.Rows
  178. factPrefix := likeEscaper.Replace(req.FactPrefix) + "%"
  179. if req.GetTarget() != nil {
  180. if req.EdgeKind == "" {
  181. rows, err = db.Query("SELECT "+columns+" FROM "+tableName+` WHERE fact LIKE ? ESCAPE '\t' AND source_signature = ? AND source_corpus = ? AND source_root = ? AND source_language = ? AND source_path = ? `+orderByClause, factPrefix, req.Target.Signature, req.Target.Corpus, req.Target.Root, req.Target.Language, req.Target.Path)
  182. } else {
  183. rows, err = db.Query("SELECT "+columns+" FROM "+tableName+` WHERE fact LIKE ? ESCAPE '\t' AND source_signature = ? AND source_corpus = ? AND source_root = ? AND source_language = ? AND source_path = ? AND kind = ? `+orderByClause, factPrefix, req.Target.Signature, req.Target.Corpus, req.Target.Root, req.Target.Language, req.Target.Path, req.EdgeKind)
  184. }
  185. } else {
  186. if req.EdgeKind == "" {
  187. rows, err = db.Query("SELECT "+columns+" FROM "+tableName+" WHERE fact LIKE ? ESCAPE '\t' "+orderByClause, factPrefix)
  188. } else {
  189. rows, err = db.Query("SELECT "+columns+" FROM "+tableName+` WHERE fact LIKE ? ESCAPE '\t' AND kind = ? `+orderByClause, factPrefix, req.EdgeKind)
  190. }
  191. }
  192. if err != nil {
  193. return err
  194. }
  195. return scanEntries(rows, f)
  196. }
  197. var emptyVName = new(spb.VName)
  198. // Write implements part of the graphstore.Service interface.
  199. func (db *DB) Write(ctx context.Context, req *spb.WriteRequest) error {
  200. if req.GetSource() == nil {
  201. return fmt.Errorf("missing Source in WriteRequest: {%v}", req)
  202. }
  203. tx, err := db.Begin()
  204. if err != nil {
  205. return fmt.Errorf("sql transaction begin error: %v", err)
  206. }
  207. writeStmt := tx.Stmt(db.writeStmt)
  208. defer func() {
  209. if err := writeStmt.Close(); err != nil {
  210. log.Printf("error closing SQL Stmt: %v", err)
  211. }
  212. }()
  213. for _, update := range req.Update {
  214. if update.Target == nil {
  215. update.Target = emptyVName
  216. }
  217. _, err := writeStmt.Exec(
  218. req.Source.Signature, req.Source.Corpus, req.Source.Root, req.Source.Path, req.Source.Language,
  219. update.EdgeKind,
  220. update.FactName,
  221. update.Target.Signature, update.Target.Corpus, update.Target.Root, update.Target.Path, update.Target.Language,
  222. update.FactValue)
  223. if err != nil {
  224. tx.Rollback()
  225. return fmt.Errorf("sql insertion error: %v", err)
  226. }
  227. }
  228. if err := tx.Commit(); err != nil {
  229. return fmt.Errorf("sql commit error: %v", err)
  230. }
  231. return nil
  232. }
  233. func scanEntries(rows *sql.Rows, f graphstore.EntryFunc) error {
  234. for rows.Next() {
  235. entry := &spb.Entry{
  236. Source: &spb.VName{},
  237. Target: &spb.VName{},
  238. }
  239. err := rows.Scan(
  240. &entry.Source.Signature,
  241. &entry.Source.Corpus,
  242. &entry.Source.Root,
  243. &entry.Source.Path,
  244. &entry.Source.Language,
  245. &entry.EdgeKind,
  246. &entry.FactName,
  247. &entry.Target.Signature,
  248. &entry.Target.Corpus,
  249. &entry.Target.Root,
  250. &entry.Target.Path,
  251. &entry.Target.Language,
  252. &entry.FactValue)
  253. if err != nil {
  254. rows.Close() // ignore errors
  255. return err
  256. }
  257. if graphstore.IsNodeFact(entry) {
  258. entry.Target = nil
  259. }
  260. if err := f(entry); err == io.EOF {
  261. rows.Close()
  262. return nil
  263. } else if err != nil {
  264. rows.Close()
  265. return err
  266. }
  267. }
  268. return rows.Close()
  269. }