github.com/simpleiot/simpleiot@v0.18.3/store/sqlite.go (about)

     1  package store
     2  
     3  import (
     4  	"crypto/rand"
     5  	"database/sql"
     6  	"errors"
     7  	"fmt"
     8  	"log"
     9  	"math"
    10  	"strings"
    11  	"sync"
    12  	"time"
    13  
    14  	"github.com/google/uuid"
    15  	"github.com/simpleiot/simpleiot/data"
    16  
    17  	// tell sql to use sqlite
    18  	_ "modernc.org/sqlite"
    19  )
    20  
    21  // DbSqlite represents a SQLite data store
    22  type DbSqlite struct {
    23  	db        *sql.DB
    24  	meta      Meta
    25  	writeLock sync.Mutex
    26  }
    27  
    28  // Meta contains metadata about the database
    29  type Meta struct {
    30  	ID      int    `json:"id"`
    31  	Version int    `json:"version"`
    32  	RootID  string `json:"rootID"`
    33  	JWTKey  []byte `json:"jwtKey"`
    34  }
    35  
    36  // NewSqliteDb creates a new Sqlite data store
    37  func NewSqliteDb(dbFile string, rootID string) (*DbSqlite, error) {
    38  	ret := &DbSqlite{}
    39  
    40  	pragmas := "_pragma=foreign_keys(1)&_pragma=journal_mode(WAL)&_pragma=synchronous(NORMAL)&_pragma=busy_timeout(8000)&_pragma=journal_size_limit(100000000)"
    41  
    42  	dbFileOptions := fmt.Sprintf("%s?%s", dbFile, pragmas)
    43  
    44  	log.Println("Open store:", dbFileOptions)
    45  
    46  	db, err := sql.Open("sqlite", dbFileOptions)
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  
    51  	// Note, code should run with the following set, which ensures we don't have any
    52  	// nested db operations. Ideally, all DB operations should exit before the next one
    53  	// starts. Cache rows in memory if necessary to make this happen.
    54  	// db.SetMaxOpenConns(1)
    55  
    56  	ret.db = db
    57  
    58  	_, err = db.Exec(`CREATE TABLE IF NOT EXISTS meta (id INT NOT NULL PRIMARY KEY,
    59  				version INT,
    60  				root_id TEXT,
    61  			  jwt_key BLOB)`)
    62  	if err != nil {
    63  		return nil, fmt.Errorf("Error creating meta table: %v", err)
    64  	}
    65  
    66  	// check if jwt_key column exists
    67  	row := db.QueryRow(`SELECT COUNT(*) AS CNTREC FROM pragma_table_info('meta') WHERE name='jwt_key'`)
    68  	var count int
    69  	err = row.Scan(&count)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	if count <= 0 {
    75  		_, err := db.Exec(`ALTER TABLE meta ADD COLUMN jwt_key BLOB`)
    76  		if err != nil {
    77  			return nil, err
    78  		}
    79  	}
    80  
    81  	_, err = db.Exec(`CREATE TABLE IF NOT EXISTS edges (id TEXT NOT NULL PRIMARY KEY,
    82  				up TEXT,
    83  				down TEXT,
    84  				hash INT,
    85  				type TEXT)`)
    86  
    87  	if err != nil {
    88  		return nil, fmt.Errorf("Error creating edges table: %v", err)
    89  	}
    90  
    91  	_, err = db.Exec(`CREATE TABLE IF NOT EXISTS node_points (id TEXT NOT NULL PRIMARY KEY,
    92  				node_id TEXT,
    93  				type TEXT,
    94  				key TEXT,
    95  				time INT,
    96  				idx REAL,
    97  				value REAL,
    98  				text TEXT,
    99  				data BLOB,
   100  				tombstone INT,
   101  				origin TEXT)`)
   102  
   103  	if err != nil {
   104  		return nil, fmt.Errorf("Error creating node_points table: %v", err)
   105  	}
   106  
   107  	_, err = db.Exec(`CREATE TABLE IF NOT EXISTS edge_points (id TEXT NOT NULL PRIMARY KEY,
   108  				edge_id TEXT,
   109  				type TEXT,
   110  				key TEXT,
   111  				time INT,
   112  				idx REAL,
   113  				value REAL,
   114  				text TEXT,
   115  				data BLOB,
   116  				tombstone INT,
   117  				origin TEXT)`)
   118  
   119  	if err != nil {
   120  		return nil, fmt.Errorf("Error creating edge_points table: %v", err)
   121  	}
   122  
   123  	_, err = db.Exec(`CREATE INDEX IF NOT EXISTS edgeUp ON edges(up)`)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	_, err = db.Exec(`CREATE INDEX IF NOT EXISTS edgeDown ON edges(down)`)
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	_, err = db.Exec(`CREATE INDEX IF NOT EXISTS edgeType ON edges(type)`)
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  
   138  	err = ret.initMeta()
   139  	if err != nil {
   140  		return nil, fmt.Errorf("Error initializing db meta: %v", err)
   141  	}
   142  
   143  	err = ret.runMigrations()
   144  	if err != nil {
   145  		return nil, fmt.Errorf("Error running migrations: %v", err)
   146  	}
   147  
   148  	if ret.meta.RootID == "" {
   149  		// we need to initialize root node and user
   150  		ret.meta.RootID, err = ret.initRoot(rootID)
   151  		if err != nil {
   152  			return nil, fmt.Errorf("Error initializing root node: %v", err)
   153  		}
   154  	}
   155  
   156  	if len(ret.meta.JWTKey) <= 0 {
   157  		err := ret.initJwtKey()
   158  		if err != nil {
   159  			return nil, fmt.Errorf("Error initializing JWT Key: %v", err)
   160  		}
   161  	}
   162  
   163  	// make sure we find root ID
   164  	nodes, err := ret.getNodes(nil, "all", ret.meta.RootID, "", false)
   165  	if err != nil {
   166  		return nil, fmt.Errorf("error fetching root node: %v", err)
   167  	}
   168  
   169  	if len(nodes) < 1 {
   170  		return nil, fmt.Errorf("root node not found")
   171  	}
   172  
   173  	return ret, nil
   174  }
   175  
   176  func (sdb *DbSqlite) initMeta() error {
   177  	// should be one row in the meta database
   178  	rows, err := sdb.db.Query("SELECT id, version, root_id, jwt_key FROM meta")
   179  	if err != nil {
   180  		return err
   181  	}
   182  	defer rows.Close()
   183  
   184  	var count int
   185  
   186  	for rows.Next() {
   187  		count++
   188  		err = rows.Scan(&sdb.meta.ID, &sdb.meta.Version, &sdb.meta.RootID, &sdb.meta.JWTKey)
   189  		if err != nil {
   190  			return fmt.Errorf("Error scanning meta row: %v", err)
   191  		}
   192  	}
   193  
   194  	if count < 1 {
   195  		_, err := sdb.db.Exec("INSERT INTO meta(id, version, root_id) VALUES(?, ?, ?)", 0, 0, "")
   196  		if err != nil {
   197  			return err
   198  		}
   199  	}
   200  
   201  	return nil
   202  }
   203  
   204  func (sdb *DbSqlite) runMigrations() error {
   205  	if sdb.meta.Version < 4 {
   206  		_, err := sdb.db.Exec(`UPDATE node_points SET key = '0' WHERE key = ''`)
   207  		if err != nil {
   208  			return err
   209  		}
   210  
   211  		_, err = sdb.db.Exec(`UPDATE edge_points SET key = '0' WHERE key = ''`)
   212  		if err != nil {
   213  			return err
   214  		}
   215  
   216  		_, err = sdb.db.Exec(`UPDATE meta SET version = 4`)
   217  		if err != nil {
   218  			return err
   219  		}
   220  		sdb.meta.Version = 4
   221  	}
   222  
   223  	return nil
   224  }
   225  
   226  // reset the database by permanently wiping all data
   227  func (sdb *DbSqlite) reset() error {
   228  	var err error
   229  
   230  	// truncate several tables
   231  	tables := []string{"meta", "edges", "node_points", "edge_points"}
   232  	for _, v := range tables {
   233  		_, err = sdb.db.Exec(`DELETE FROM ` + v)
   234  		if err != nil {
   235  			return fmt.Errorf("Error truncating table: %v", err)
   236  		}
   237  	}
   238  
   239  	// we need to initialize root node and user
   240  	// preserve root ID
   241  	sdb.meta.RootID, err = sdb.initRoot(sdb.meta.RootID)
   242  	if err != nil {
   243  		return fmt.Errorf("error initializing root node: %v", err)
   244  	}
   245  
   246  	// make sure we find root ID
   247  	nodes, err := sdb.getNodes(nil, "all", sdb.meta.RootID, "", false)
   248  	if err != nil {
   249  		return fmt.Errorf("error fetching root node: %v", err)
   250  	}
   251  
   252  	if len(nodes) < 1 {
   253  		return fmt.Errorf("root node not found")
   254  	}
   255  
   256  	return nil
   257  }
   258  
   259  // verifyNodeHashes recursively verifies all the hash values for all nodes
   260  // this walks to the bottom of the tree, and then works its way back up
   261  func (sdb *DbSqlite) verifyNodeHashes(fix bool) error {
   262  	// must run this in a transaction so we don't get any modifications
   263  	// while reading child nodes. This may be expensive for a large DB, so
   264  	// we may want to eventually break this down into transactions for each node
   265  	// and its children.
   266  	tx, err := sdb.db.Begin()
   267  	if err != nil {
   268  		return err
   269  	}
   270  
   271  	rollback := func() {
   272  		rbErr := tx.Rollback()
   273  		if rbErr != nil {
   274  			log.Println("Rollback error:", rbErr)
   275  		}
   276  	}
   277  
   278  	// get root node to kick things off
   279  	rootNodes, err := sdb.getNodes(nil, "root", "all", "", true)
   280  
   281  	if err != nil {
   282  		rollback()
   283  		return err
   284  	}
   285  
   286  	if len(rootNodes) < 1 {
   287  		rollback()
   288  		return errors.New("no root nodes")
   289  	}
   290  
   291  	root := rootNodes[0]
   292  
   293  	var verify func(node data.NodeEdge) error
   294  
   295  	verify = func(node data.NodeEdge) error {
   296  		children, err := sdb.getNodes(nil, node.ID, "all", "", true)
   297  		if err != nil {
   298  			return err
   299  		}
   300  
   301  		// it's important to go through children first as this can
   302  		// impact the current hash
   303  		for _, c := range children {
   304  			err := verify(c)
   305  			if err != nil {
   306  				return err
   307  			}
   308  		}
   309  
   310  		hash := node.CalcHash(children)
   311  
   312  		if hash != node.Hash {
   313  			log.Printf("Hash failed for %v, stored: %v, calc: %v",
   314  				node.ID, node.Hash, hash)
   315  			if fix {
   316  				log.Println("fixing ...")
   317  				_, err := tx.Exec(`UPDATE edges SET hash = ? WHERE up = ? AND down = ?`,
   318  					hash, node.Parent, node.ID)
   319  				if err != nil {
   320  					return err
   321  				}
   322  			}
   323  		}
   324  
   325  		return nil
   326  	}
   327  
   328  	err = verify(root)
   329  
   330  	if err != nil {
   331  		rollback()
   332  		return fmt.Errorf("Verify failed: %v", err)
   333  	}
   334  
   335  	err = tx.Commit()
   336  	if err != nil {
   337  		return err
   338  	}
   339  
   340  	return nil
   341  }
   342  
   343  func (sdb *DbSqlite) initRoot(rootID string) (string, error) {
   344  	log.Println("STORE: Initialize root node and admin user")
   345  	rootNode := data.NodeEdge{
   346  		ID:   rootID,
   347  		Type: data.NodeTypeDevice,
   348  	}
   349  
   350  	rootNode.ID = rootID
   351  
   352  	if rootNode.ID == "" {
   353  		rootNode.ID = uuid.New().String()
   354  	}
   355  
   356  	err := sdb.nodePoints(rootNode.ID, rootNode.Points)
   357  	if err != nil {
   358  		return "", fmt.Errorf("Error setting root node points: %v", err)
   359  	}
   360  
   361  	err = sdb.edgePoints(rootNode.ID, "root", data.Points{
   362  		{Type: data.PointTypeTombstone, Value: 0},
   363  		{Type: data.PointTypeNodeType, Text: rootNode.Type},
   364  	})
   365  	if err != nil {
   366  		return "", fmt.Errorf("Error sending root node edges: %w", err)
   367  	}
   368  
   369  	// create admin user off root node
   370  	admin := data.User{
   371  		ID:        uuid.New().String(),
   372  		FirstName: "admin",
   373  		LastName:  "user",
   374  		Email:     "admin",
   375  		Pass:      "admin",
   376  	}
   377  
   378  	points := admin.ToPoints()
   379  
   380  	err = sdb.nodePoints(admin.ID, points)
   381  	if err != nil {
   382  		return "", fmt.Errorf("Error setting default user: %v", err)
   383  	}
   384  
   385  	err = sdb.edgePoints(admin.ID, rootNode.ID, data.Points{
   386  		{Type: data.PointTypeTombstone, Value: 0},
   387  		{Type: data.PointTypeNodeType, Text: data.NodeTypeUser},
   388  	})
   389  
   390  	if err != nil {
   391  		return "", err
   392  	}
   393  
   394  	sdb.writeLock.Lock()
   395  	defer sdb.writeLock.Unlock()
   396  	_, err = sdb.db.Exec("UPDATE meta SET root_id = ?", rootNode.ID)
   397  	if err != nil {
   398  		return "", fmt.Errorf("Error setting meta rootID: %v", err)
   399  	}
   400  
   401  	return rootNode.ID, nil
   402  }
   403  
   404  func (sdb *DbSqlite) initJwtKey() error {
   405  	sdb.meta.JWTKey = make([]byte, 20)
   406  	_, err := rand.Read(sdb.meta.JWTKey)
   407  	if err != nil {
   408  		return fmt.Errorf("Error reading making JWT key: %v", err)
   409  	}
   410  
   411  	sdb.writeLock.Lock()
   412  	defer sdb.writeLock.Unlock()
   413  	_, err = sdb.db.Exec("UPDATE meta SET jwt_key = ?", sdb.meta.JWTKey)
   414  	if err != nil {
   415  		return fmt.Errorf("Error setting meta jwt key: %v", err)
   416  	}
   417  
   418  	return nil
   419  }
   420  
   421  func (sdb *DbSqlite) nodePoints(id string, points data.Points) error {
   422  	points.Collapse()
   423  
   424  	sdb.writeLock.Lock()
   425  	defer sdb.writeLock.Unlock()
   426  	tx, err := sdb.db.Begin()
   427  	if err != nil {
   428  		return err
   429  	}
   430  
   431  	rollback := func() {
   432  		rbErr := tx.Rollback()
   433  		if rbErr != nil {
   434  			log.Println("Rollback error:", rbErr)
   435  		}
   436  	}
   437  
   438  	rowsPoints, err := tx.Query("SELECT * FROM node_points WHERE node_id=?", id)
   439  	if err != nil {
   440  		rollback()
   441  		return err
   442  	}
   443  	defer rowsPoints.Close()
   444  
   445  	var dbPoints data.Points
   446  	var dbPointIDs []string
   447  
   448  	for rowsPoints.Next() {
   449  		var p data.Point
   450  		var timeNS int64
   451  		var pID string
   452  		var nodeID string
   453  		var index float32
   454  		err := rowsPoints.Scan(&pID, &nodeID, &p.Type, &p.Key, &timeNS, &index, &p.Value, &p.Text,
   455  			&p.Data, &p.Tombstone, &p.Origin)
   456  		if err != nil {
   457  			rollback()
   458  			return err
   459  		}
   460  		p.Time = time.Unix(0, timeNS)
   461  		dbPoints = append(dbPoints, p)
   462  		dbPointIDs = append(dbPointIDs, pID)
   463  	}
   464  
   465  	if err := rowsPoints.Close(); err != nil {
   466  		rollback()
   467  		return fmt.Errorf("Error closing rowsPoints: %v", err)
   468  	}
   469  
   470  	var writePoints data.Points
   471  	var writePointIDs []string
   472  
   473  	var hashUpdate uint32
   474  
   475  NextPin:
   476  	for _, pIn := range points {
   477  		if pIn.Time.IsZero() {
   478  			pIn.Time = time.Now()
   479  		}
   480  
   481  		if pIn.Key == "" {
   482  			pIn.Key = "0"
   483  		}
   484  
   485  		for j, pDb := range dbPoints {
   486  			if pIn.Type == pDb.Type && pIn.Key == pDb.Key {
   487  				// found a match
   488  				if pDb.Time.Before(pIn.Time) || pDb.Time.Equal(pIn.Time) {
   489  					writePoints = append(writePoints, pIn)
   490  					writePointIDs = append(writePointIDs, dbPointIDs[j])
   491  					// back out old CRC and add in new one
   492  					hashUpdate ^= pDb.CRC()
   493  					hashUpdate ^= pIn.CRC()
   494  				} else {
   495  					log.Println("Ignoring node point due to timestamps:", id, pIn)
   496  				}
   497  				continue NextPin
   498  			}
   499  		}
   500  
   501  		// point was not found so write it
   502  		writePoints = append(writePoints, pIn)
   503  		hashUpdate ^= pIn.CRC()
   504  		writePointIDs = append(writePointIDs, uuid.New().String())
   505  	}
   506  
   507  	stmt, err := tx.Prepare(`INSERT INTO node_points(id, node_id, type, key, time,
   508                   idx, value, text, data, tombstone, origin)
   509  		 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
   510  		 ON CONFLICT(id) DO UPDATE SET
   511  		 type = ?3,
   512  		 key = ?4,
   513  		 time = ?5,
   514  		 idx = ?6,
   515  		 value = ?7,
   516  		 text = ?8,
   517  		 data = ?9,
   518  		 tombstone = ?10,
   519  		 origin = ?11
   520  		 `)
   521  
   522  	if err != nil {
   523  		rollback()
   524  		return err
   525  	}
   526  
   527  	defer func() {
   528  		err := stmt.Close()
   529  		if err != nil {
   530  			log.Println("Error closing sqlite statement:", err)
   531  		}
   532  	}()
   533  
   534  	for i, p := range writePoints {
   535  		tNs := p.Time.UnixNano()
   536  		pID := writePointIDs[i]
   537  		_, err = stmt.Exec(pID, id, p.Type, p.Key, tNs, 0, p.Value, p.Text, p.Data, p.Tombstone,
   538  			p.Origin)
   539  		if err != nil {
   540  			rollback()
   541  			return err
   542  		}
   543  	}
   544  
   545  	stmt.Close()
   546  
   547  	err = sdb.updateHash(tx, id, hashUpdate)
   548  	if err != nil {
   549  		rollback()
   550  		return fmt.Errorf("Error updating upstream hash: %v", err)
   551  	}
   552  
   553  	err = tx.Commit()
   554  	if err != nil {
   555  		return err
   556  	}
   557  
   558  	return nil
   559  }
   560  
   561  func (sdb *DbSqlite) edgePoints(nodeID, parentID string, points data.Points) error {
   562  	points.Collapse()
   563  
   564  	if nodeID == parentID {
   565  		return fmt.Errorf("Error: edgePoints nodeID=parentID=%v", nodeID)
   566  	}
   567  
   568  	if nodeID == sdb.meta.RootID {
   569  		for _, p := range points {
   570  			if p.Type == data.PointTypeTombstone && p.Value > 0 {
   571  				return fmt.Errorf("Error, can't delete root node")
   572  			}
   573  		}
   574  	}
   575  
   576  	sdb.writeLock.Lock()
   577  	defer sdb.writeLock.Unlock()
   578  
   579  	var err error
   580  	if parentID == "" {
   581  		parentID = "root"
   582  	}
   583  
   584  	tx, err := sdb.db.Begin()
   585  	if err != nil {
   586  		return err
   587  	}
   588  
   589  	rollback := func() {
   590  		rbErr := tx.Rollback()
   591  		if rbErr != nil {
   592  			log.Println("Rollback error:", rbErr)
   593  		}
   594  	}
   595  
   596  	edges, err := sdb.edges(tx, "SELECT * FROM edges WHERE up=? AND down=?", parentID, nodeID)
   597  	if err != nil {
   598  		rollback()
   599  		return err
   600  	}
   601  
   602  	var edge data.Edge
   603  
   604  	newEdge := false
   605  
   606  	if len(edges) <= 0 {
   607  		newEdge = true
   608  		edge.ID = uuid.New().String()
   609  	} else {
   610  		edge = edges[0]
   611  	}
   612  
   613  	rowsPoints, err := tx.Query("SELECT * FROM edge_points WHERE edge_id=?", edge.ID)
   614  	if err != nil {
   615  		rollback()
   616  		return err
   617  	}
   618  	defer rowsPoints.Close()
   619  
   620  	var dbPoints data.Points
   621  	var dbPointIDs []string
   622  
   623  	for rowsPoints.Next() {
   624  		var p data.Point
   625  		var timeNS int64
   626  		var pID string
   627  		var nodeID string
   628  		var index float32
   629  		err := rowsPoints.Scan(&pID, &nodeID, &p.Type, &p.Key, &timeNS, &index, &p.Value, &p.Text,
   630  			&p.Data, &p.Tombstone, &p.Origin)
   631  		if err != nil {
   632  			rollback()
   633  			return err
   634  		}
   635  		p.Time = time.Unix(0, timeNS)
   636  		dbPoints = append(dbPoints, p)
   637  		dbPointIDs = append(dbPointIDs, pID)
   638  	}
   639  
   640  	if err := rowsPoints.Close(); err != nil {
   641  		rollback()
   642  		return fmt.Errorf("Error closing rowsPoints: %v", err)
   643  	}
   644  
   645  	var writePoints data.Points
   646  	var writePointIDs []string
   647  
   648  	var hashUpdate uint32
   649  
   650  	var nodeType string
   651  
   652  NextPin:
   653  	for _, pIn := range points {
   654  		// we don't store node type points
   655  		if pIn.Type == data.PointTypeNodeType {
   656  			nodeType = pIn.Text
   657  			continue NextPin
   658  		}
   659  
   660  		if pIn.Time.IsZero() {
   661  			pIn.Time = time.Now()
   662  		}
   663  
   664  		if pIn.Key == "" {
   665  			pIn.Key = "0"
   666  		}
   667  
   668  		for j, pDb := range dbPoints {
   669  			if pIn.Type == pDb.Type && pIn.Key == pDb.Key {
   670  				// found a match
   671  				if pDb.Time.Before(pIn.Time) || pDb.Time.Equal(pIn.Time) {
   672  					writePoints = append(writePoints, pIn)
   673  					writePointIDs = append(writePointIDs, dbPointIDs[j])
   674  					// back out old CRC and add in new one
   675  					hashUpdate ^= pDb.CRC()
   676  					hashUpdate ^= pIn.CRC()
   677  				} else {
   678  					log.Println("Ignoring edge point due to timestamps:", edge.ID, pIn)
   679  				}
   680  				continue NextPin
   681  			}
   682  		}
   683  
   684  		// point was not found so write it
   685  		writePoints = append(writePoints, pIn)
   686  		hashUpdate ^= pIn.CRC()
   687  		writePointIDs = append(writePointIDs, uuid.New().String())
   688  	}
   689  
   690  	// loop through write points and write them
   691  	stmt, err := tx.Prepare(`INSERT INTO edge_points(id, edge_id, type, key, time,
   692                   idx, value, text, data, tombstone, origin)
   693  		 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
   694  		 ON CONFLICT(id) DO UPDATE SET
   695  		 type = ?3,
   696  		 key = ?4,
   697  		 time = ?5,
   698  		 idx = ?6,
   699  		 value = ?7,
   700  		 text = ?8,
   701  		 data = ?9,
   702  		 tombstone = ?10,
   703  		 origin = ?11
   704  		 `)
   705  
   706  	if err != nil {
   707  		rollback()
   708  		return err
   709  	}
   710  
   711  	for i, p := range writePoints {
   712  		tNs := p.Time.UnixNano()
   713  		pID := writePointIDs[i]
   714  		_, err = stmt.Exec(pID, edge.ID, p.Type, p.Key, tNs, 0, p.Value, p.Text, p.Data, p.Tombstone,
   715  			p.Origin)
   716  		if err != nil {
   717  			stmt.Close()
   718  			rollback()
   719  			return err
   720  		}
   721  	}
   722  
   723  	stmt.Close()
   724  
   725  	// we don't update the hash here as it gets updated later in updateHash()
   726  	// SQLite is amazing as it appears the below INSERT can be read later in the read before
   727  	// the transaction is finished.
   728  
   729  	// write edge
   730  	if newEdge {
   731  		if nodeType == "" {
   732  			rollback()
   733  			return fmt.Errorf("Node type must be sent with new edges")
   734  		}
   735  		// did not find edge, need to add it
   736  		edge.Up = parentID
   737  		edge.Down = nodeID
   738  		edge.Type = nodeType
   739  
   740  		// look for existing node points that must be added to the hash
   741  		rowsPoints, err := tx.Query("SELECT * FROM node_points WHERE node_id=?", nodeID)
   742  		if err != nil {
   743  			rollback()
   744  			return err
   745  		}
   746  		defer rowsPoints.Close()
   747  
   748  		for rowsPoints.Next() {
   749  			var p data.Point
   750  			var timeNS int64
   751  			var pID string
   752  			var nodeID string
   753  			var index float32
   754  			err := rowsPoints.Scan(&pID, &nodeID, &p.Type, &p.Key, &timeNS, &index, &p.Value, &p.Text,
   755  				&p.Data, &p.Tombstone, &p.Origin)
   756  			if err != nil {
   757  				rollback()
   758  				return err
   759  			}
   760  			p.Time = time.Unix(0, timeNS)
   761  			hashUpdate ^= p.CRC()
   762  		}
   763  
   764  		if err := rowsPoints.Close(); err != nil {
   765  			rollback()
   766  			return fmt.Errorf("Error closing rowsPoints: %v", err)
   767  		}
   768  
   769  		_, err = tx.Exec(`INSERT INTO edges(id, up, down, hash, type) VALUES (?, ?, ?, ?, ?)`,
   770  			edge.ID, edge.Up, edge.Down, 0, edge.Type)
   771  
   772  		// (hash will be populated later)
   773  
   774  		if err != nil {
   775  			log.Println("edge insert failed, trying again ...:", err)
   776  			// FIXME, occasionally the above INSERT will fail with "database is locked (5) (SQLITE_BUSY)"
   777  			// FIXME, not sure if retry is required any more since we removed the nested
   778  			// queries
   779  			// not sure why, but the below retry seems to work around this issue for now
   780  			_, err := tx.Exec(`INSERT INTO edges(id, up, down, hash, type) VALUES (?, ?, ?, ?)`,
   781  				edge.ID, edge.Up, edge.Down, edge.Hash, edge.Type)
   782  
   783  			// TODO check for downstream node and add in its hash
   784  			if err != nil {
   785  				rollback()
   786  				return fmt.Errorf("Error when writing edge: %v", err)
   787  			}
   788  		}
   789  
   790  		if parentID == "root" {
   791  			log.Println("inserting new root node, update root in meta")
   792  			_, err = tx.Exec("UPDATE meta SET root_id = ?", nodeID)
   793  			if err != nil {
   794  				rollback()
   795  				return fmt.Errorf("Error update root id in meta: %w", err)
   796  			}
   797  			sdb.meta.RootID = nodeID
   798  		}
   799  	}
   800  
   801  	err = sdb.updateHash(tx, nodeID, hashUpdate)
   802  	if err != nil {
   803  		rollback()
   804  		return fmt.Errorf("Error updating upstream hash: %v", err)
   805  	}
   806  
   807  	err = tx.Commit()
   808  	if err != nil {
   809  		return err
   810  	}
   811  
   812  	return nil
   813  }
   814  
   815  func (sdb *DbSqlite) updateHash(tx *sql.Tx, id string, hashUpdate uint32) error {
   816  	// key in edgeCache is up-down
   817  	cache := make(map[string]uint32)
   818  	err := sdb.updateHashHelper(tx, id, hashUpdate, cache)
   819  	if err != nil {
   820  		return err
   821  	}
   822  
   823  	// write update hash values back to edges
   824  	stmt, err := tx.Prepare(`UPDATE edges SET hash = ? WHERE id = ?`)
   825  
   826  	if err != nil {
   827  		return err
   828  	}
   829  
   830  	for id, hash := range cache {
   831  		_, err = stmt.Exec(hash, id)
   832  		if err != nil {
   833  			stmt.Close()
   834  			return fmt.Errorf("Error updating edge hash: %v", err)
   835  		}
   836  	}
   837  
   838  	stmt.Close()
   839  
   840  	return nil
   841  }
   842  
   843  func (sdb *DbSqlite) updateHashHelper(tx *sql.Tx, id string, hashUpdate uint32, cache map[string]uint32) error {
   844  	edges, err := sdb.edges(tx, "SELECT * FROM edges WHERE down=?", id)
   845  	if err != nil {
   846  		return err
   847  	}
   848  
   849  	for _, e := range edges {
   850  		if _, ok := cache[e.ID]; !ok {
   851  			cache[e.ID] = e.Hash
   852  		}
   853  
   854  		cache[e.ID] ^= hashUpdate
   855  
   856  		if e.Up != "none" {
   857  			err := sdb.updateHashHelper(tx, e.Up, hashUpdate, cache)
   858  			if err != nil {
   859  				return err
   860  			}
   861  		}
   862  	}
   863  	return nil
   864  }
   865  
   866  func (sdb *DbSqlite) edges(tx *sql.Tx, query string, args ...any) ([]data.Edge, error) {
   867  	var rowsEdges *sql.Rows
   868  	var err error
   869  
   870  	if tx != nil {
   871  		rowsEdges, err = tx.Query(query, args...)
   872  	} else {
   873  		rowsEdges, err = sdb.db.Query(query, args...)
   874  	}
   875  
   876  	if err != nil {
   877  		return nil, fmt.Errorf("Error getting edges: %v", err)
   878  	}
   879  	defer rowsEdges.Close()
   880  
   881  	var edges []data.Edge
   882  
   883  	for rowsEdges.Next() {
   884  		var edge data.Edge
   885  		err = rowsEdges.Scan(&edge.ID, &edge.Up, &edge.Down, &edge.Hash, &edge.Type)
   886  		if err != nil {
   887  			return nil, fmt.Errorf("Error scanning edges: %v", err)
   888  		}
   889  
   890  		edges = append(edges, edge)
   891  	}
   892  
   893  	if err := rowsEdges.Close(); err != nil {
   894  		return nil, err
   895  	}
   896  
   897  	if len(edges) < 1 {
   898  		return edges, nil
   899  	}
   900  
   901  	// Load edge points
   902  	edgeIDs := make([]any, len(edges))
   903  	for i, edge := range edges {
   904  		edgeIDs[i] = edge.ID
   905  	}
   906  	edgePoints, err := sdb.queryPoints(
   907  		tx,
   908  		"SELECT * FROM edge_points WHERE edge_id IN(?"+
   909  			strings.Repeat(",?", len(edgeIDs)-1)+")",
   910  		edgeIDs...,
   911  	)
   912  	if err != nil {
   913  		return nil, fmt.Errorf("error getting edge points: %w", err)
   914  	}
   915  
   916  	for i := range edges {
   917  		edges[i].Points = edgePoints[edges[i].ID]
   918  	}
   919  
   920  	return edges, nil
   921  }
   922  
   923  // Close the db
   924  func (sdb *DbSqlite) Close() error {
   925  	return sdb.db.Close()
   926  }
   927  
   928  func (sdb *DbSqlite) rootNodeID() string {
   929  	return sdb.meta.RootID
   930  }
   931  
   932  // If parent is set to "all", then all instances of the node are returned.
   933  // If parent is set and id is "all", then all child nodes are returned.
   934  // Parent can be set to "root" and id to "all" to fetch the root node(s).
   935  func (sdb *DbSqlite) getNodes(tx *sql.Tx, parent, id, typ string, includeDel bool) ([]data.NodeEdge, error) {
   936  	var ret []data.NodeEdge
   937  
   938  	if parent == "" || parent == "none" {
   939  		return nil, errors.New("Parent must be set to valid ID, or all")
   940  	}
   941  
   942  	if id == "" {
   943  		id = "all"
   944  	}
   945  
   946  	var q string
   947  
   948  	switch {
   949  	case parent == "root":
   950  		// return a single root node
   951  		q = fmt.Sprintf("SELECT * FROM edges WHERE down = '%v'", sdb.meta.RootID)
   952  	case parent == "all" && id == "all":
   953  		return nil, errors.New("invalid combination of parent and id")
   954  	case parent == "all":
   955  		q = fmt.Sprintf("SELECT * FROM edges WHERE down = '%v'", id)
   956  	case id == "all":
   957  		q = fmt.Sprintf("SELECT * FROM edges WHERE up = '%v'", parent)
   958  	default:
   959  		// both parent and id are specified
   960  		q = fmt.Sprintf("SELECT * FROM edges WHERE up='%v' AND down = '%v'", parent, id)
   961  	}
   962  
   963  	if typ != "" {
   964  		q += fmt.Sprintf("AND type = '%v'", typ)
   965  	}
   966  
   967  	edges, err := sdb.edges(tx, q)
   968  
   969  	if err != nil {
   970  		return ret, err
   971  	}
   972  
   973  	if len(edges) < 1 {
   974  		return ret, nil
   975  	}
   976  
   977  	// Populate `ret` with NodeEdges with edge points
   978  	for _, edge := range edges {
   979  		var ne data.NodeEdge
   980  		ne.ID = edge.Down
   981  		ne.Parent = edge.Up
   982  		ne.Hash = edge.Hash
   983  		ne.Type = edge.Type
   984  		ne.EdgePoints = edge.Points
   985  
   986  		if !includeDel {
   987  			tombstone, _ := ne.IsTombstone()
   988  			if tombstone {
   989  				// skip deleted nodes
   990  				continue
   991  			}
   992  		}
   993  
   994  		ret = append(ret, ne)
   995  	}
   996  
   997  	if len(ret) < 1 {
   998  		return ret, nil
   999  	}
  1000  
  1001  	// Load node points for each NodeEdge
  1002  	nodeIDs := make([]any, len(ret))
  1003  	for i, ne := range ret {
  1004  		nodeIDs[i] = ne.ID
  1005  	}
  1006  	nodePoints, err := sdb.queryPoints(
  1007  		tx,
  1008  		"SELECT * FROM node_points WHERE node_id IN(?"+
  1009  			strings.Repeat(",?", len(nodeIDs)-1)+")",
  1010  		nodeIDs...,
  1011  	)
  1012  	if err != nil {
  1013  		return nil, fmt.Errorf("children error getting node points: %v", err)
  1014  	}
  1015  
  1016  	for i, ne := range ret {
  1017  		ret[i].Points = nodePoints[ne.ID]
  1018  	}
  1019  
  1020  	return ret, nil
  1021  }
  1022  
  1023  // returns points, and error
  1024  func (sdb *DbSqlite) queryPoints(tx *sql.Tx, query string, args ...any) (map[string]data.Points, error) {
  1025  	retPoints := make(map[string]data.Points)
  1026  
  1027  	var rowsPoints *sql.Rows
  1028  	var err error
  1029  
  1030  	if tx != nil {
  1031  		rowsPoints, err = tx.Query(query, args...)
  1032  	} else {
  1033  		rowsPoints, err = sdb.db.Query(query, args...)
  1034  	}
  1035  
  1036  	if err != nil {
  1037  		return nil, err
  1038  	}
  1039  	defer rowsPoints.Close()
  1040  
  1041  	for rowsPoints.Next() {
  1042  		var p data.Point
  1043  		var timeNS int64
  1044  		var pID string
  1045  		var nodeOrEdgeID string
  1046  		var index float32
  1047  		err := rowsPoints.Scan(
  1048  			&pID, &nodeOrEdgeID, &p.Type, &p.Key, &timeNS, &index, &p.Value,
  1049  			&p.Text, &p.Data, &p.Tombstone, &p.Origin,
  1050  		)
  1051  		if err != nil {
  1052  			return nil, err
  1053  		}
  1054  		p.Time = time.Unix(0, timeNS)
  1055  		retPoints[nodeOrEdgeID] = append(retPoints[nodeOrEdgeID], p)
  1056  	}
  1057  
  1058  	return retPoints, nil
  1059  }
  1060  
  1061  // userCheck checks user authentication
  1062  // returns nil, nil if user is not found
  1063  func (sdb *DbSqlite) userCheck(email, password string) (data.Nodes, error) {
  1064  	var users []data.NodeEdge
  1065  
  1066  	rows, err := sdb.db.Query("SELECT down FROM edges WHERE type=?", data.NodeTypeUser)
  1067  	if err != nil {
  1068  		return nil, fmt.Errorf("userCheck, error query error: %v", err)
  1069  	}
  1070  	defer rows.Close()
  1071  
  1072  	var ids []string
  1073  
  1074  	for rows.Next() {
  1075  		var id string
  1076  		err = rows.Scan(&id)
  1077  		if err != nil {
  1078  			log.Println("Error scanning user id:", id)
  1079  			continue
  1080  		}
  1081  
  1082  		ids = append(ids, id)
  1083  	}
  1084  
  1085  	if err := rows.Close(); err != nil {
  1086  		return nil, err
  1087  	}
  1088  
  1089  	for _, id := range ids {
  1090  		ne, err := sdb.getNodes(nil, "all", id, "", false)
  1091  		if err != nil {
  1092  			log.Println("Error getting user node for id:", id)
  1093  			continue
  1094  		}
  1095  		if len(ne) < 1 {
  1096  			continue
  1097  		}
  1098  
  1099  		n := ne[0].ToNode()
  1100  		u := n.ToUser()
  1101  		if u.Email == email && u.Pass == password {
  1102  			users = append(users, ne...)
  1103  		}
  1104  	}
  1105  
  1106  	// make sure all these user nodes are still alive and have path to root
  1107  	var ret []data.NodeEdge
  1108  
  1109  	var checkUserPathRoot func(string) (bool, error)
  1110  
  1111  	checkUserPathRoot = func(id string) (bool, error) {
  1112  		edges, err := sdb.edges(nil, "SELECT * FROM edges WHERE down=?", id)
  1113  		if err != nil {
  1114  			return false, err
  1115  		}
  1116  
  1117  	NextEdge:
  1118  		for _, e := range edges {
  1119  			// make sure edge is not tombstone
  1120  			for _, p := range e.Points {
  1121  				if p.Type == data.PointTypeTombstone && p.Value != 0 {
  1122  					// this edge does not have a path to root, try next edge
  1123  					continue NextEdge
  1124  				}
  1125  			}
  1126  
  1127  			if e.Up == "root" {
  1128  				return true, nil
  1129  			}
  1130  
  1131  			// continue walking upstream
  1132  			ok, err := checkUserPathRoot(e.Up)
  1133  			if err != nil {
  1134  				return false, err
  1135  			}
  1136  
  1137  			if ok {
  1138  				// found a path, return
  1139  				return ok, nil
  1140  			}
  1141  
  1142  			// look at the next edge
  1143  		}
  1144  
  1145  		return false, nil
  1146  	}
  1147  
  1148  	for _, u := range users {
  1149  		ok, err := checkUserPathRoot(u.ID)
  1150  		if err != nil {
  1151  			return nil, err
  1152  		}
  1153  
  1154  		if ok {
  1155  			ret = append(ret, u)
  1156  		}
  1157  	}
  1158  
  1159  	return ret, nil
  1160  }
  1161  
  1162  // up returns upstream ids for a node
  1163  func (sdb *DbSqlite) up(id string, includeDeleted bool) ([]string, error) {
  1164  	var ups []string
  1165  
  1166  	edges, err := sdb.edges(nil, "SELECT * FROM edges WHERE down=?", id)
  1167  	if err != nil {
  1168  		return nil, err
  1169  	}
  1170  
  1171  	for _, e := range edges {
  1172  		if includeDeleted {
  1173  			ups = append(ups, e.Up)
  1174  		} else {
  1175  			p, _ := e.Points.Find(data.PointTypeTombstone, "")
  1176  			if math.Mod(p.Value, 2) == 0 {
  1177  				ups = append(ups, e.Up)
  1178  			}
  1179  		}
  1180  	}
  1181  
  1182  	return ups, nil
  1183  }