<commit_before>'use strict';

const _ = require('lodash');
const qs = require('qs');
const log = require('../../util/log');
const crud = require('../../../lib/sql/crud');
const serverErrors = require('../../util/server-errors');
const sendJson = require('../../util/send-json');
const handleQueryError = require('../../util/handle-query-error');
const formatTransaction = require('../../util/format-transaction');

module.exports = function(req, res) {
  log.info({req}, 'A read request is being processed.');
  const selfLink = req.originalUrl;
  const id = req.params.id;
  const isSingular = Boolean(id);
  const pagination = this.definition.pagination;
  const pageNumber = Number(_.get(req.query, 'page.number', pagination.default_page_number));
  const pageSize = Number(_.get(req.query, 'page.size', pagination.default_page_size));

  let paginationErrors = [];
  if (pageNumber < 1) {
    paginationErrors.push(serverErrors.outOfBoundsPagination.body('page.number'));
  }
  if (pageSize < 1) {
    paginationErrors.push(serverErrors.outOfBoundsPagination.body('page.size'));
  }

  if (paginationErrors.length) {
    res.status(serverErrors.outOfBoundsPagination.code);
    sendJson(res, {
      errors: paginationErrors,
      links: {
        self: selfLink
      }
    });
    return;
  }

  // Find the fields to return
  let fieldsToReturn = _.get(req.query, `fields.${this.definition.plural_form}`);

  // This captures if the user specifies the parameter, but doesn't actually
  // enter a value.
  if (!fieldsToReturn) {
    fieldsToReturn = '*';
  }

  // The user can pass in comma-separated fields. i.e.
  // ?fields[people]=first_name,last_name,address
  // to just get those fields.
  if (fieldsToReturn !== '*') {
    fieldsToReturn = fieldsToReturn
      .split(',')
      // Ensure only valid fields are specified
      .filter(field => _.includes(_.map(this.definition.attributes, 'name'), field));
  }

  let fieldsIsArray = Array.isArray(fieldsToReturn);

  // If they tried to specify fields, but none of them exist on this resource,
  // then we return an error response.
  if (fieldsIsArray && fieldsToReturn.length === 0) {
    log.info({reqId: req.id}, 'A read request specified sparse fields, but provided no valid fields.');
    res.status(serverErrors.noValidFields.code);
    sendJson(res, {
      errors: [serverErrors.noValidFields.body(this.definition.plural_form)],
      links: {
        self: selfLink
      }
    });
    res.end();
    return;
  }

  if (fieldsIsArray) {
    // We always need the ID, as well as the meta attributes. `fields`
    // only refers to relationships and attributes.
    fieldsToReturn = fieldsToReturn.concat('id', _.map(this.definition.meta, 'name'));
  }

  // Only paginate if this is a readMany, and if the resource has specified
  // pagination.
  const enablePagination = !isSingular && pagination.enabled;

  // `isSingular` is whether or not we're looking for 1
  // or all. This coercion is fine because SERIALs start at 1
  const query = crud.read({
    definition: this.definition,
    tableName: this.definition.tableName.raw,
    db: this.db,
    fields: fieldsToReturn,
    pageSize,
    pageNumber,
    enablePagination,
    id
  });
  const method = isSingular ? 'one' : 'any';

  log.info({query, resourceName: this.definition.name, reqId: req.id}, 'Reading a resource');

  this.db[method](query)
    // This first section guarantees that we get an accurate total count when
    // performing a read many. Our system for getting the total count with
    // pagination only works when at least one result is returned. If no
    // results are returned (which can happen if the user accesses a page
    // beyond the last page), then we will not get a value. In those situations,
    // we do one more read to get the total count before sending our response
    // back.
    .then(result => {
      // Singular results have no total count, so we don't do anything special.
      if (isSingular) {
        return {result};
      }
      // If we do get results, or pagination is disabled, then it's not possible
      // that our total count is wrong.
      else if (result.length || !enablePagination) {
        return {
          result,
          totalCount: _.get(result[0], 'total_count', 0)
        };
      }
      log.info({reqId: req.id}, 'No results returned on a paginated read many.');

      const readOneAttempt = crud.read({
        definition: this.definition,
        db: this.db,
        pageSize: 1,
        pageNumber: 1,
        enablePagination
      });

      log.info({reqId: req.id, query}, 'Follow-up paginated read many query.');
      return this.db.oneOrNone(readOneAttempt)<commit_msg>Begin converting read middleware to async/await<commit_after>'use strict';

const _ = require('lodash');
const qs = require('qs');
const log = require('../../util/log');
const crud = require('../../../lib/sql/crud');
const serverErrors = require('../../util/server-errors');
const sendJson = require('../../util/send-json');
const handleQueryError = require('../../util/handle-query-error');
const formatTransaction = require('../../util/format-transaction');

module.exports = async function(req, res) {
  log.info({req}, 'A read request is being processed.');
  const selfLink = req.originalUrl;
  const id = req.params.id;
  const isSingular = Boolean(id);
  const pagination = this.definition.pagination;
  const pageNumber = Number(_.get(req.query, 'page.number', pagination.default_page_number));
  const pageSize = Number(_.get(req.query, 'page.size', pagination.default_page_size));

  let paginationErrors = [];
  if (pageNumber < 1) {
    paginationErrors.push(serverErrors.outOfBoundsPagination.body('page.number'));
  }
  if (pageSize < 1) {
    paginationErrors.push(serverErrors.outOfBoundsPagination.body('page.size'));
  }

  if (paginationErrors.length) {
    res.status(serverErrors.outOfBoundsPagination.code);
    sendJson(res, {
      errors: paginationErrors,
      links: {
        self: selfLink
      }
    });
    return;
  }

  // Find the fields to return
  let fieldsToReturn = _.get(req.query, `fields.${this.definition.plural_form}`);

  // This captures if the user specifies the parameter, but doesn't actually
  // enter a value.
  if (!fieldsToReturn) {
    fieldsToReturn = '*';
  }

  // The user can pass in comma-separated fields. i.e.
  // ?fields[people]=first_name,last_name,address
  // to just get those fields.
  if (fieldsToReturn !== '*') {
    fieldsToReturn = fieldsToReturn
      .split(',')
      // Ensure only valid fields are specified
      .filter(field => _.includes(_.map(this.definition.attributes, 'name'), field));
  }

  let fieldsIsArray = Array.isArray(fieldsToReturn);

  // If they tried to specify fields, but none of them exist on this resource,
  // then we return an error response.
  if (fieldsIsArray && fieldsToReturn.length === 0) {
    log.info({reqId: req.id}, 'A read request specified sparse fields, but provided no valid fields.');
    res.status(serverErrors.noValidFields.code);
    sendJson(res, {
      errors: [serverErrors.noValidFields.body(this.definition.plural_form)],
      links: {
        self: selfLink
      }
    });
    res.end();
    return;
  }

  if (fieldsIsArray) {
    // We always need the ID, as well as the meta attributes. `fields`
    // only refers to relationships and attributes.
    fieldsToReturn = fieldsToReturn.concat('id', _.map(this.definition.meta, 'name'));
  }

  // Only paginate if this is a readMany, and if the resource has specified
  // pagination.
  const enablePagination = !isSingular && pagination.enabled;

  // `isSingular` is whether or not we're looking for 1
  // or all. This coercion is fine because SERIALs start at 1
  const query = crud.read({
    definition: this.definition,
    tableName: this.definition.tableName.raw,
    db: this.db,
    fields: fieldsToReturn,
    pageSize,
    pageNumber,
    enablePagination,
    id
  });
  const method = isSingular ? 'one' : 'any';

  log.info({query, resourceName: this.definition.name, reqId: req.id}, 'Reading a resource');

  const result = await this.db[method](query).catch(r => r);

  if (_.isError(result)) {
    const crudAction = isSingular ? 'readOne' : 'readMany';
    return handleQueryError({
      err: result,
      definition: this.definition,
      req, res, crudAction, query, selfLink,
    });
  }

  Promise.resolve(result)
    .then(result => {
      // Singular results have no total count, so we don't do anything special.
      if (isSingular) {
        return {result};
      }
      // If we do get results, or pagination is disabled, then it's not possible
      // that our total count is wrong.
      else if (result.length || !enablePagination) {
        return {
          result,
          totalCount: _.get(result[0], 'total_count', 0)
        };
      }
      log.info({reqId: req.id}, 'No results returned on a paginated read many.');

      const readOneAttempt = crud.read({
        definition: this.definition,
        db: this.db,
        pageSize: 1,
        pageNumber: 1,
        enablePagination
      });

      log.info({reqId: req.id, query}, 'Follow-up paginated read many query.');
      return this.db.oneOrNone(readOneAttempt)
{'fraction_non_alphanumeric': 0.13047701273472911, 'fraction_numerical': 0.0019425857975393912, 'mean_word_length': 3.003023758099352, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 8, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '108840', 'n_tokens_mistral': 2794, 'n_tokens_neox': 2737, 'n_words': 805}