github.com/muhammedhassanm/blockchain@v0.0.0-20200120143007-697261defd4d/sawtooth-supply-chain-master/server/db/index.js (about)

     1  /**
     2   * Copyright 2017 Intel Corporation
     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   */
    17  'use strict'
    18  
    19  const r = require('rethinkdb')
    20  const _ = require('lodash')
    21  const jsSchema = require('js-schema')
    22  const config = require('../system/config')
    23  
    24  const HOST = config.DB_HOST
    25  const PORT = config.DB_PORT
    26  const NAME = config.DB_NAME
    27  const RETRY_WAIT = config.RETRY_WAIT
    28  const AWAIT_TABLE = 'blocks'
    29  
    30  // Connection to db for query methods, run connect before querying
    31  let connection = null
    32  
    33  const promisedTimeout = (fn, wait) => {
    34    return new Promise(resolve => setTimeout(resolve, wait)).then(fn);
    35  }
    36  
    37  const awaitDatabase = () => {
    38    return r.tableList().run(connection)
    39      .then(tableNames => {
    40        if (!tableNames.includes(AWAIT_TABLE)) {
    41          throw new Error()
    42        }
    43        console.log('Successfully connected to database:', NAME)
    44      })
    45      .catch(() => {
    46        console.warn('Database not initialized:', NAME)
    47        console.warn(`Retrying database in ${RETRY_WAIT / 1000} seconds...`)
    48        return promisedTimeout(awaitDatabase, RETRY_WAIT)
    49      })
    50  }
    51  
    52  const connect = () => {
    53    return r.connect({host: HOST, port: PORT, db: NAME})
    54      .then(conn => {
    55        connection = conn
    56        return awaitDatabase()
    57      })
    58      .catch(err => {
    59        if (err instanceof r.Error.ReqlDriverError) {
    60          console.warn('Unable to connect to RethinkDB')
    61          console.warn(`Retrying in ${RETRY_WAIT / 1000} seconds...`)
    62          return promisedTimeout(connect, RETRY_WAIT)
    63        }
    64        throw err
    65      })
    66  }
    67  
    68  const runQuery = query => {
    69    return query
    70      .run(connection)
    71      .catch(err => {
    72        console.error(err.message)
    73        throw new Error(err.message)
    74      })
    75  }
    76  
    77  const queryWithCurrentBlock = query => {
    78    return runQuery(
    79      r.table('blocks')
    80        .orderBy(r.desc('blockNum'))
    81        .nth(0)('blockNum')
    82        .do(query)
    83    )
    84  }
    85  
    86  // Runs a specified query against a database table
    87  const queryTable = (table, query, removeCursor = true) => {
    88    return query(r.table(table))
    89      .run(connection)
    90      .then(cursor => removeCursor ? cursor.toArray() : cursor)
    91      .catch(err => {
    92        console.error(`Unable to query "${table}" table!`)
    93        console.error(err.message)
    94        throw new Error(err.message)
    95      })
    96  }
    97  
    98  // Use for queries that modify a table, turns error messages into errors
    99  const modifyTable = (table, query) => {
   100    return queryTable(table, query, false)
   101      .then(results => {
   102        if (!results) {
   103          throw new Error(`Unknown error while attempting to modify "${table}"`)
   104        }
   105        if (results.errors > 0) {
   106          throw new Error(results.first_error)
   107        }
   108        return results
   109      })
   110  }
   111  
   112  // Inserts a document into a table, throwing an error on failure
   113  // Accepts an optional validator function, which should have an errors method
   114  const insertTable = (table, doc) => {
   115    return modifyTable(table, t => t.insert(doc))
   116      .then(results => {
   117        if (results.inserted === 0) {
   118          throw new Error(`Unknown Error: Unable to insert to ${table}`)
   119        }
   120        return results
   121      })
   122  }
   123  
   124  const updateTable = (table, primary, changes) => {
   125    return modifyTable(table, t => {
   126      return t.get(primary).update(changes, {returnChanges: true})
   127    })
   128      .then(results => {
   129        if (results.replaced === 0 && results.unchanged === 0) {
   130          throw new Error(`Unknown Error: Unable to update ${primary}`)
   131        }
   132        return results
   133      })
   134  }
   135  
   136  // Validates a db input based on a schema as promised
   137  const validate = (input, schema) => {
   138    return Promise.resolve()
   139      .then(() => {
   140        const validator = jsSchema(schema)
   141        if (validator(input)) return input
   142  
   143        const errors = validator.errors(input)
   144        if (!errors) throw new Error('Invalid Input: one or more keys forbidden')
   145  
   146        const [ key, message ] = _.entries(errors)[0]
   147        throw new Error(`Invalid Input: "${key}" - ${message}`)
   148      })
   149  }
   150  
   151  module.exports = {
   152    connect,
   153    runQuery,
   154    queryWithCurrentBlock,
   155    queryTable,
   156    modifyTable,
   157    insertTable,
   158    updateTable,
   159    validate
   160  }