| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318 | /* eslint-disable no-use-before-define *///  ████████╗ ██████╗ ██╗  ██╗███████╗███╗   ██╗██╗███████╗███████╗██████╗//  ╚══██╔══╝██╔═══██╗██║ ██╔╝██╔════╝████╗  ██║██║╚══███╔╝██╔════╝██╔══██╗//     ██║   ██║   ██║█████╔╝ █████╗  ██╔██╗ ██║██║  ███╔╝ █████╗  ██████╔╝//     ██║   ██║   ██║██╔═██╗ ██╔══╝  ██║╚██╗██║██║ ███╔╝  ██╔══╝  ██╔══██╗//     ██║   ╚██████╔╝██║  ██╗███████╗██║ ╚████║██║███████╗███████╗██║  ██║//     ╚═╝    ╚═════╝ ╚═╝  ╚═╝╚══════╝╚═╝  ╚═══╝╚═╝╚══════╝╚══════╝╚═╝  ╚═╝//// The tokenizer is responsible for taking a nested Waterline statement and// turning it into a flat set of keys. This allows the query to more easily be// parsed and prevents further recusion as the query progresses to eventually// end up as a native query.//// In most cases this will not be implemented by adapter authors but will be used// inside a database driver's `compileStatement` machine.var _ = require('@sailshq/lodash');//  ╔═╗╦═╗╔═╗╔═╗╔═╗╔═╗╔═╗╔═╗╦═╗  ╔═╗╦ ╦╔╗╔╔═╗╔╦╗╦╔═╗╔╗╔╔═╗//  ╠═╝╠╦╝║ ║║  ║╣ ╚═╗╚═╗║ ║╠╦╝  ╠╣ ║ ║║║║║   ║ ║║ ║║║║╚═╗//  ╩  ╩╚═╚═╝╚═╝╚═╝╚═╝╚═╝╚═╝╩╚═  ╚  ╚═╝╝╚╝╚═╝ ╩ ╩╚═╝╝╚╝╚═╝// These are the identifiers used in RQL for various keysvar identifiers = {  'select': 'SELECT',  'from': 'FROM',  'or': 'OR',  'and': 'AND',  'not': 'NOT',  'nin': 'NOTIN',  'in': 'IN',  'distinct': 'DISTINCT',  'count': 'COUNT',  'min': 'MIN',  'max': 'MAX',  'sum': 'SUM',  'avg': 'AVG',  'limit': 'LIMIT',  'skip': 'SKIP',  'groupBy': 'GROUPBY',  'orderBy': 'ORDERBY',  'where': 'WHERE',  'insert': 'INSERT',  'into': 'INTO',  'update': 'UPDATE',  'using': 'USING',  'del': 'DELETE',  'join': 'JOIN',  'innerJoin': 'JOIN',  'outerJoin': 'JOIN',  'crossJoin': 'JOIN',  'leftJoin': 'JOIN',  'leftOuterJoin': 'JOIN',  'rightJoin': 'JOIN',  'rightOuterJoin': 'JOIN',  'fullOuterJoin': 'JOIN',  'union': 'UNION',  'unionAll': 'UNIONALL',  'as': 'AS',  '>': 'OPERATOR',  '<': 'OPERATOR',  '<>': 'OPERATOR',  '<=': 'OPERATOR',  '>=': 'OPERATOR',  '!=': 'OPERATOR',  'like': 'OPERATOR',  'opts': 'OPTS',  'returning': 'RETURNING'};// If these identifiers are found within a WHERE clause, treat them as regular keys.var WHERE_EXEMPT = [  'from',  'distinct',  'count',  'min',  'max',  'sum',  'avg',  'insert',  'union',  'as',  'returning',  'join'];// These are the Data Manipulation Identifiers that denote a subqueryvar DML_IDENTIFIERS = [  'select',  'insert',  'update',  'del'];//  ╔╦╗╔═╗╦╔═╔═╗╔╗╔╦╔═╗╔═╗  ┌─┐┌┐  ┬┌─┐┌─┐┌┬┐//   ║ ║ ║╠╩╗║╣ ║║║║╔═╝║╣   │ │├┴┐ │├┤ │   │//   ╩ ╚═╝╩ ╩╚═╝╝╚╝╩╚═╝╚═╝  └─┘└─┘└┘└─┘└─┘ ┴// @obj {Object} - the token obj being processed// @processor {Object} - a value to insert between each key in the arrayvar tokenizeObject = function tokenizeObject(obj, processor, parent, isSubQuery, results) {  // If this obj represent a sub-query, add a sub query token  if (isSubQuery) {    results.push({      type: 'SUBQUERY',      value: null    });  }  // Determine whether we're currently processing a WHERE clause.  // We use a stack of counters to do this, adding and removing from the stack  // when subqueries are started / ended, and incrementing/decrementing the  // first counter in the stack when WHERE clauses are started and ended.  var inWhere = !!_.reduce(results, function(memo, item) {    if (item.type === 'IDENTIFIER' || item.value === 'WHERE') {      memo[0]++;    }    else if (item.type === 'ENDIDENTIFIER' || item.value === 'WHERE') {      memo[0]--;    }    else if (item.type === 'SUBQUERY') {      memo.unshift(0);    }    else if (item.type === 'ENDSUBQUERY') {      memo.unshift();    }    return memo;  }, [0])[0];  _.each(_.keys(obj), function tokenizeKey(key, idx) {    // Check if the key is a known identifier    var isIdentitifier = identifiers[key];    // If so, look ahead at it's value to determine what to do next.    if (isIdentitifier) {      //  ╦ ╦╦ ╦╔═╗╦═╗╔═╗  ╔═╗═╗ ╦╔═╗╔╦╗╔═╗╔╦╗      //  ║║║╠═╣║╣ ╠╦╝║╣───║╣ ╔╩╦╝║╣ ║║║╠═╝ ║      //  ╚╩╝╩ ╩╚═╝╩╚═╚═╝  ╚═╝╩ ╚═╚═╝╩ ╩╩   ╩      // If we're currently inside a WHERE clause and we encounter a key that is normally an identifier      // but is in the WHERE_EXEMPT list, process it as a regular key.      if (inWhere && _.contains(WHERE_EXEMPT, key)) {        results.push({          type:  'KEY',          value: key        });        if (_.isObject(obj[key])) {          tokenizeObject(obj[key], undefined, undefined, undefined, results);          return;        }        results.push({          type: 'VALUE',          value: obj[key]        });        return;      }      //  ╔═╗╔═╗╔═╗╦═╗╔═╗╔╦╗╔═╗╦═╗  ╔═╗╦═╗╔═╗╔╦╗╦╔═╗╔═╗╔╦╗╔═╗╔═╗      //  ║ ║╠═╝║╣ ╠╦╝╠═╣ ║ ║ ║╠╦╝  ╠═╝╠╦╝║╣  ║║║║  ╠═╣ ║ ║╣ ╚═╗      //  ╚═╝╩  ╚═╝╩╚═╩ ╩ ╩ ╚═╝╩╚═  ╩  ╩╚═╚═╝═╩╝╩╚═╝╩ ╩ ╩ ╚═╝╚═╝      // If the identifier is an OPERATOR, add it's tokens      if (identifiers[key] === 'OPERATOR') {        // If there is a parent and the previous key in the results isn't        // a KEY add it's key first. This is used when a key has multiple        // criteria. EX: { values: { '>': 100, '<': 200 }}        if (parent && _.last(results).type !== 'KEY') {          results.push({            type: 'KEY',            value: parent          });        }        processOperator(key, obj[key], results);        return;      }      // If the identifier is an IN      if (identifiers[key] === 'IN') {        processIn(obj[key], undefined, results);        return;      }      // If the identifier is an OR, start a group and add each token.      if (identifiers[key] === 'OR') {        processOr(obj[key], results);        return;      }      // If the identifier is an AND, start a group and add each token.      if (identifiers[key] === 'AND') {        processAnd(obj[key], results);        return;      }      // If the identifier is a NOT      if (identifiers[key] === 'NOT') {        processNot(obj[key], results);        return;      }      // If the identifier is a NOTIN      if (identifiers[key] === 'NOTIN') {        processIn(obj[key], true, results);        return;      }      //  ╔═╗ ╦ ╦╔═╗╦═╗╦╔═╗╔═╗      //  ║═╬╗║ ║║╣ ╠╦╝║║╣ ╚═╗      //  ╚═╝╚╚═╝╚═╝╩╚═╩╚═╝╚═╝      // If the identifier is a FROM, add it's token      if (identifiers[key] === 'FROM') {        processFrom(obj[key], results);        return;      }      // If the identifier is a WHERE, add it's token and process it's values      if (identifiers[key] === 'WHERE') {        processWhere(obj[key], results);        return;      }      // If the identifier is a GROUP BY aggregation      if (identifiers[key] === 'GROUPBY') {        processGroupBy(obj[key], results);        return;      }      // If the identifier is an ORDER BY, add the sort options      if (identifiers[key] === 'ORDERBY') {        processOrderBy(obj[key], results);        return;      }      //  ╔╦╗╔╦╗╦    ╔═╗╔═╗╔╦╗╔╦╗╔═╗╔╗╔╔╦╗╔═╗      //   ║║║║║║    ║  ║ ║║║║║║║╠═╣║║║ ║║╚═╗      //  ═╩╝╩ ╩╩═╝  ╚═╝╚═╝╩ ╩╩ ╩╩ ╩╝╚╝═╩╝╚═╝      // If the identifier is a SELECT, add it's token      if (identifiers[key] === 'SELECT') {        processSelect(obj[key], results);        return;      }      // If the identifier is an INSERT, add it's token      if (identifiers[key] === 'INSERT') {        processInsert(obj[key], results);        return;      }      // If the identifier is an UPDATE, add it's token      if (identifiers[key] === 'UPDATE') {        processUpdate(obj[key], results);        return;      }      // If the identifier is a DELETE, add it's token      if (identifiers[key] === 'DELETE') {        processDelete(results);        return;      }      // If the identifier is an INTO, add it's token      if (identifiers[key] === 'INTO') {        processInto(obj[key], results);        return;      }      // If the identifier is an USING, add it's token      if (identifiers[key] === 'USING') {        processUsing(obj[key], results);        return;      }      //  ╔═╗╔═╗╔═╗╦═╗╔═╗╔═╗╔═╗╔╦╗╔═╗╔═╗      //  ╠═╣║ ╦║ ╦╠╦╝║╣ ║ ╦╠═╣ ║ ║╣ ╚═╗      //  ╩ ╩╚═╝╚═╝╩╚═╚═╝╚═╝╩ ╩ ╩ ╚═╝╚═╝      // If the identifier is a AVG      if (identifiers[key] === 'AVG') {        processAggregations(obj[key], 'AVG', results);        return;      }      // If the identifier is a SUM      if (identifiers[key] === 'SUM') {        processAggregations(obj[key], 'SUM', results);        return;      }      // If the identifier is a MIN      if (identifiers[key] === 'MIN') {        processAggregations(obj[key], 'MIN', results);        return;      }      // If the identifier is a MAX      if (identifiers[key] === 'MAX') {        processAggregations(obj[key], 'MAX', results);        return;      }      // If the identifier is a COUNT      if (identifiers[key] === 'COUNT') {        processAggregations(obj[key], 'COUNT', results);        return;      }      //  ╔═╗╔╦╗╦ ╦╔═╗╦═╗      //  ║ ║ ║ ╠═╣║╣ ╠╦╝      //  ╚═╝ ╩ ╩ ╩╚═╝╩╚═      // If the identifier is a LIMIT      if (identifiers[key] === 'LIMIT') {        processPagination(obj[key], 'LIMIT', results);        return;      }      // If the indetifier is an SKIP      if (identifiers[key] === 'SKIP') {        processPagination(obj[key], 'SKIP', results);        return;      }      // AS is only available on sub queries      if (identifiers[key] === 'AS') {        if (!isSubQuery) {          return;        }        processAs(obj[key], results);        return;      }      // If the indetifier is an RETURNING      if (identifiers[key] === 'RETURNING') {        processReturning(obj[key], results);        return;      }      //   ╦╔═╗╦╔╗╔╔═╗      //   ║║ ║║║║║╚═╗      //  ╚╝╚═╝╩╝╚╝╚═╝      // If the identifier is a JOIN, add it's token and process the joins      if (identifiers[key] === 'JOIN') {        processJoin(obj[key], key, results);        return;      }      //  ╦ ╦╔╗╔╦╔═╗╔╗╔╔═╗      //  ║ ║║║║║║ ║║║║╚═╗      //  ╚═╝╝╚╝╩╚═╝╝╚╝╚═╝      // If the identifier is a UNION      if (identifiers[key] === 'UNION') {        processUnion(obj[key], 'UNION', results);        return;      }      // If the identifier is a UNIONALL      if (identifiers[key] === 'UNIONALL') {        processUnion(obj[key], 'UNIONALL', results);        return;      }      //  ╔═╗╔═╗╔╦╗╔═╗      //  ║ ║╠═╝ ║ ╚═╗      //  ╚═╝╩   ╩ ╚═╝      // Handle any known values in the opts. Opts must be a dictionary.      if (identifiers[key] === 'OPTS') {        if (!_.isPlainObject(obj[key])) {          return;        }        _.each(obj[key], function processOpt(val, key) {          // Handle PG schema values          if (key === 'schema') {            return processSchema(val, results);          }        });        return;      }      // Add the identifier      results.push({        type: identifiers[key],        value: key      });      // If the identifier is an array, loop through each item and tokenize      if (_.isArray(obj[key])) {        _.each(obj[key], function tokenizeJoinPiece(expr) {          tokenizeObject(expr, undefined, undefined, undefined, results);        });        return;      }      // If the identifier is an object, continue tokenizing it      if (_.isPlainObject(obj[key])) {        tokenizeObject(obj[key], undefined, key, undefined, results);        return;      }      // Otherwise WTF?      return;    }    // Otherwise add the token for the key    results.push({      type: 'KEY',      value: key    });    // If the value is an object, recursively parse it unless it matches as    // a sub query    if (_.isPlainObject(obj[key])) {      // Check if the value is a subquery first      var subQuery = checkForSubquery(obj[key], results);      if (subQuery) {        return;      }      // Otherwise parse the object      tokenizeObject(obj[key], undefined, key, undefined, results);      return;    }    // If the value is a primitive add it's token    results.push({      type: 'VALUE',      value: obj[key]    });    // If there is a processor and we are not on the last key, add it as well.    // This is used for things like:    // {    //   not: {    //     firstName: 'foo',    //     lastName: 'bar'    //   }    // }    // Where we need to insert a NOT statement between each key    if (processor && (_.keys(obj).length > idx + 1)) {      results.push(processor);    }  });  // If this obj represent a sub-query, close the sub query token  if (isSubQuery) {    results.push({      type: 'ENDSUBQUERY',      value: null    });  }};//  ╔═╗╦ ╦╔═╗╔═╗╦╔═  ╔═╗╔═╗╦═╗  ╔═╗╦ ╦╔╗ ╔═╗ ╦ ╦╔═╗╦═╗╦ ╦//  ║  ╠═╣║╣ ║  ╠╩╗  ╠╣ ║ ║╠╦╝  ╚═╗║ ║╠╩╗║═╬╗║ ║║╣ ╠╦╝╚╦╝//  ╚═╝╩ ╩╚═╝╚═╝╩ ╩  ╚  ╚═╝╩╚═  ╚═╝╚═╝╚═╝╚═╝╚╚═╝╚═╝╩╚═ ╩var checkForSubquery = function checkForSubquery(value, results) {  var isSubquery = false;  // Check if the object has any top level DML identifiers  _.each(value, function checkForIdentifier(val, key) {    if (_.indexOf(DML_IDENTIFIERS, key) < 0) {      return;    }    isSubquery = true;  });  // If this is a sub query, tokenize it as such  if (isSubquery) {    tokenizeObject(value, undefined, undefined, isSubquery, results);    return isSubquery;  }  return isSubquery;};//  ╔═╗╔═╗╔═╗╦═╗╔═╗╔╦╗╔═╗╦═╗╔═╗//  ║ ║╠═╝║╣ ╠╦╝╠═╣ ║ ║ ║╠╦╝╚═╗//  ╚═╝╩  ╚═╝╩╚═╩ ╩ ╩ ╚═╝╩╚═╚═╝var processOperator = function processOperator(operator, value, results) {  // Add the operator to the results  results.push({    type: 'OPERATOR',    value: operator  });  results.push({    type: 'VALUE',    value: value  });  // Add the operator to the results  results.push({    type: 'ENDOPERATOR',    value: operator  });};//  ╔═╗╔═╗╦  ╔═╗╔═╗╔╦╗  ╔═╗╔╦╗╔═╗╔╦╗╔═╗╔╦╗╔═╗╔╗╔╔╦╗//  ╚═╗║╣ ║  ║╣ ║   ║   ╚═╗ ║ ╠═╣ ║ ║╣ ║║║║╣ ║║║ ║//  ╚═╝╚═╝╩═╝╚═╝╚═╝ ╩   ╚═╝ ╩ ╩ ╩ ╩ ╚═╝╩ ╩╚═╝╝╚╝ ╩var processSelect = function processSelect(value, results) {  // Check if a distinct or other key is being used  if (_.isPlainObject(value) && !_.isArray(value)) {    if (value.distinct) {      // Add the distinct to the results      results.push({        type: 'IDENTIFIER',        value: 'DISTINCT'      });      // Add the value to the results      results.push({        type: 'VALUE',        value: value.distinct      });      // Add the enddistinct to the results      results.push({        type: 'ENDIDENTIFIER',        value: 'DISTINCT'      });      return;    }  }  // If the value is not an array or object, add the value  if (!_.isPlainObject(value) && !_.isArray(value)) {    // Add the SELECT to the results    results.push({      type: 'IDENTIFIER',      value: 'SELECT'    });    // Add the value to the results    results.push({      type: 'VALUE',      value: value    });    // Add the ENDSELECT to the results    results.push({      type: 'ENDIDENTIFIER',      value: 'SELECT'    });    return;  }  // If the value is not an array, make it one so that we can process each  // element.  if (!_.isArray(value)) {    value = [value];  }  // Process each item in there SELECT statement and process subqueries as  // needed.  _.each(value, function processSelectKey(val) {    // Add the SELECT to the results    results.push({      type: 'IDENTIFIER',      value: 'SELECT'    });    // If the value isn't an object, no need to process it further    if (!_.isPlainObject(val)) {      results.push({        type: 'VALUE',        value: val      });    }    // Check if the object is a sub-query    if (_.isPlainObject(val)) {      var isSubquery = checkForSubquery(val, results);      // If it's not, add it's value      if (!isSubquery) {        results.push({          type: 'VALUE',          value: val        });      }    }    // Add the ENDSELECT to the results    results.push({      type: 'ENDIDENTIFIER',      value: 'SELECT'    });  });};//  ╔═╗╦═╗╔═╗╔╦╗  ╔═╗╔╦╗╔═╗╔╦╗╔═╗╔╦╗╔═╗╔╗╔╔╦╗//  ╠╣ ╠╦╝║ ║║║║  ╚═╗ ║ ╠═╣ ║ ║╣ ║║║║╣ ║║║ ║//  ╚  ╩╚═╚═╝╩ ╩  ╚═╝ ╩ ╩ ╩ ╩ ╚═╝╩ ╩╚═╝╝╚╝ ╩var processFrom = function processFrom(value, results) {  // Check if a schema is being used  if (_.isObject(value) && !_.isFunction(value) && !_.isArray(value)) {    // Add the FROM identifier    results.push({      type: 'IDENTIFIER',      value: 'FROM'    });    // Check if a subquery is being used    var isSubQuery = checkForSubquery(value, results);    if (!isSubQuery && value.table) {      results.push({        type: 'VALUE',        value: value.table      });    }    results.push({      type: 'ENDIDENTIFIER',      value: 'FROM'    });    return;  }  // Otherwise just add the FROM identifier and value  results.push({    type: 'IDENTIFIER',    value: 'FROM'  });  results.push({    type: 'VALUE',    value: value  });  results.push({    type: 'ENDIDENTIFIER',    value: 'FROM'  });};//  ╔═╗╔═╗╦ ╦╔═╗╔╦╗╔═╗  ╔═╗╔═╗╔╦╗//  ╚═╗║  ╠═╣║╣ ║║║╠═╣  ║ ║╠═╝ ║//  ╚═╝╚═╝╩ ╩╚═╝╩ ╩╩ ╩  ╚═╝╩   ╩var processSchema = function processSchema(value, results) {  results.push({    type: 'IDENTIFIER',    value: 'SCHEMA'  });  results.push({    type: 'VALUE',    value: value  });  results.push({    type: 'ENDIDENTIFIER',    value: 'SCHEMA'  });};//  ╦╔╗╔╔═╗╔═╗╦═╗╔╦╗  ╔═╗╔╦╗╔═╗╔╦╗╔═╗╔╦╗╔═╗╔╗╔╔╦╗//  ║║║║╚═╗║╣ ╠╦╝ ║   ╚═╗ ║ ╠═╣ ║ ║╣ ║║║║╣ ║║║ ║//  ╩╝╚╝╚═╝╚═╝╩╚═ ╩   ╚═╝ ╩ ╩ ╩ ╩ ╚═╝╩ ╩╚═╝╝╚╝ ╩var processInsert = function processInsert(value, results) {  // Add the insert statment  results.push({    type: 'IDENTIFIER',    value: 'INSERT'  });  // Check if an array is being used  if (_.isArray(value)) {    _.each(value, function appendInsertEach(record, idx) {      // Add a group clause      results.push({        type: 'GROUP',        value: idx      });      // If the value is a plain object, proccess it      if (_.isObject(record) && !_.isFunction(record) && !_.isArray(record)) {        _.each(_.keys(record), function appendInsertValue(key) {          results.push({            type: 'KEY',            value: key          });          results.push({            type: 'VALUE',            value: record[key]          });        });      }      // Close the group clause      results.push({        type: 'ENDGROUP',        value: idx      });    });  }  // Check if a plain object value is being used  if (_.isObject(value) && !_.isFunction(value) && !_.isArray(value)) {    _.each(_.keys(value), function appendInsertValue(key) {      results.push({        type: 'KEY',        value: key      });      results.push({        type: 'VALUE',        value: value[key]      });    });  }  results.push({    type: 'ENDIDENTIFIER',    value: 'INSERT'  });};//  ╦╔╗╔╔╦╗╔═╗  ╔═╗╔╦╗╔═╗╔╦╗╔═╗╔╦╗╔═╗╔╗╔╔╦╗//  ║║║║ ║ ║ ║  ╚═╗ ║ ╠═╣ ║ ║╣ ║║║║╣ ║║║ ║//  ╩╝╚╝ ╩ ╚═╝  ╚═╝ ╩ ╩ ╩ ╩ ╚═╝╩ ╩╚═╝╝╚╝ ╩var processInto = function processInto(value, results) {  results.push({    type: 'IDENTIFIER',    value: 'INTO'  });  results.push({    type: 'VALUE',    value: value  });  results.push({    type: 'ENDIDENTIFIER',    value: 'INTO'  });};//  ╦ ╦╔═╗╔╦╗╔═╗╔╦╗╔═╗  ╔═╗╔╦╗╔═╗╔╦╗╔═╗╔╦╗╔═╗╔╗╔╔╦╗//  ║ ║╠═╝ ║║╠═╣ ║ ║╣   ╚═╗ ║ ╠═╣ ║ ║╣ ║║║║╣ ║║║ ║//  ╚═╝╩  ═╩╝╩ ╩ ╩ ╚═╝  ╚═╝ ╩ ╩ ╩ ╩ ╚═╝╩ ╩╚═╝╝╚╝ ╩var processUpdate = function processUpdate(value, results) {  // Add the update statment  results.push({    type: 'IDENTIFIER',    value: 'UPDATE'  });  // Check if a value is being used  if (_.isObject(value)) {    _.each(_.keys(value), function appendUpdateValue(key) {      results.push({        type: 'KEY',        value: key      });      results.push({        type: 'VALUE',        value: value[key]      });    });  }  results.push({    type: 'ENDIDENTIFIER',    value: 'UPDATE'  });};//  ╦ ╦╔═╗╦╔╗╔╔═╗  ╔═╗╔╦╗╔═╗╔╦╗╔═╗╔╦╗╔═╗╔╗╔╔╦╗//  ║ ║╚═╗║║║║║ ╦  ╚═╗ ║ ╠═╣ ║ ║╣ ║║║║╣ ║║║ ║//  ╚═╝╚═╝╩╝╚╝╚═╝  ╚═╝ ╩ ╩ ╩ ╩ ╚═╝╩ ╩╚═╝╝╚╝ ╩var processUsing = function processUsing(value, results) {  results.push({    type: 'IDENTIFIER',    value: 'USING'  });  results.push({    type: 'VALUE',    value: value  });  results.push({    type: 'ENDIDENTIFIER',    value: 'USING'  });};//  ╔╦╗╔═╗╦  ╔═╗╔╦╗╔═╗  ╔═╗╔╦╗╔═╗╔╦╗╔═╗╔╦╗╔═╗╔╗╔╔╦╗//   ║║║╣ ║  ║╣  ║ ║╣   ╚═╗ ║ ╠═╣ ║ ║╣ ║║║║╣ ║║║ ║//  ═╩╝╚═╝╩═╝╚═╝ ╩ ╚═╝  ╚═╝ ╩ ╩ ╩ ╩ ╚═╝╩ ╩╚═╝╝╚╝ ╩var processDelete = function processDelete(results) {  results.push({    type: 'IDENTIFIER',    value: 'DELETE'  });  results.push({    type: 'ENDIDENTIFIER',    value: 'DELETE'  });};//  ╔╗╔╔═╗╔╦╗  ╔═╗╔═╗╔╗╔╔╦╗╦╔╦╗╦╔═╗╔╗╔//  ║║║║ ║ ║   ║  ║ ║║║║ ║║║ ║ ║║ ║║║║//  ╝╚╝╚═╝ ╩   ╚═╝╚═╝╝╚╝═╩╝╩ ╩ ╩╚═╝╝╚╝var processNot = function processNot(value, results) {  // Add a condition  var condition = {    type: 'CONDITION',    value: 'NOT'  };  results.push(condition);  // Tokenize the values within the condition  if (_.isObject(value) && !_.isFunction(value) && !_.isArray(value)) {    tokenizeObject(value, condition, undefined, undefined, results);    return;  }  results.push({    type: 'VALUE',    value: value  });  results.push({    type: 'ENDCONDITION',    value: 'NOT'  });};//  ╦╔╗╔  ╔═╗╔═╗╔╗╔╔╦╗╦╔╦╗╦╔═╗╔╗╔//  ║║║║  ║  ║ ║║║║ ║║║ ║ ║║ ║║║║//  ╩╝╚╝  ╚═╝╚═╝╝╚╝═╩╝╩ ╩ ╩╚═╝╝╚╝var processIn = function processIn(value, negate, results) {  // Add a condition  var startCondition;  var endCondition;  if (negate) {    startCondition = {      type: 'CONDITION',      value: 'NOTIN'    };    endCondition = {      type: 'ENDCONDITION',      value: 'NOTIN'    };  } else {    startCondition = {      type: 'CONDITION',      value: 'IN'    };    endCondition = {      type: 'ENDCONDITION',      value: 'IN'    };  }  results.push(startCondition);  // If the value isn't an object, no need to process it further  if (!_.isPlainObject(value)) {    results.push({      type: 'VALUE',      value: value    });  }  // Check if the object is a sub-query  if (_.isObject(value) && !_.isFunction(value) && !_.isArray(value)) {    var isSubquery = checkForSubquery(value, results);    // If it's not, add it's value    if (!isSubquery) {      results.push({        type: 'VALUE',        value: value      });    }  }  results.push(endCondition);};//  ╦ ╦╦ ╦╔═╗╦═╗╔═╗  ╔═╗╔╦╗╔═╗╔╦╗╔═╗╔╦╗╔═╗╔╗╔╔╦╗//  ║║║╠═╣║╣ ╠╦╝║╣   ╚═╗ ║ ╠═╣ ║ ║╣ ║║║║╣ ║║║ ║//  ╚╩╝╩ ╩╚═╝╩╚═╚═╝  ╚═╝ ╩ ╩ ╩ ╩ ╚═╝╩ ╩╚═╝╝╚╝ ╩var processWhere = function processWhere(value, results) {  // Tokenize the where and then call the tokenizer on the where values  results.push({    type: 'IDENTIFIER',    value: 'WHERE'  });  tokenizeObject(value, undefined, undefined, undefined, results);  results.push({    type: 'ENDIDENTIFIER',    value: 'WHERE'  });};//  ╔═╗╦═╗  ╔═╗╦═╗╔═╗╦ ╦╔═╗╦╔╗╔╔═╗//  ║ ║╠╦╝  ║ ╦╠╦╝║ ║║ ║╠═╝║║║║║ ╦//  ╚═╝╩╚═  ╚═╝╩╚═╚═╝╚═╝╩  ╩╝╚╝╚═╝var processOr = function processOr(value, results) {  // Add the Or token  results.push({    type: 'CONDITION',    value: 'OR'  });  // For each condition in the OR, add a group token and process the criteria.  _.forEach(value, function appendOrCrieria(criteria, idx) {    // Start a group    results.push({      type: 'GROUP',      value: idx    });    tokenizeObject(criteria, undefined, undefined, undefined, results);    // End a group    results.push({      type: 'ENDGROUP',      value: idx    });  });  // Close the condition  results.push({    type: 'ENDCONDITION',    value: 'OR'  });};//  ╔═╗╔╗╔╔╦╗  ╔═╗╦═╗╔═╗╦ ╦╔═╗╦╔╗╔╔═╗//  ╠═╣║║║ ║║  ║ ╦╠╦╝║ ║║ ║╠═╝║║║║║ ╦//  ╩ ╩╝╚╝═╩╝  ╚═╝╩╚═╚═╝╚═╝╩  ╩╝╚╝╚═╝var processAnd = function processAnd(value, results) {  // Only process grouped AND's if the value is an array  if (!_.isArray(value)) {    return;  }  // Add the AND token  results.push({    type: 'CONDITION',    value: 'AND'  });  // For each condition in the OR, add a group token and process the criteria.  _.each(value, function appendAndCrieria(criteria, idx) {    // Start a group    results.push({      type: 'GROUP',      value: idx    });    tokenizeObject(criteria, undefined, undefined, undefined, results);    // End a group    results.push({      type: 'ENDGROUP',      value: idx    });  });  // Close the condition  results.push({    type: 'ENDCONDITION',    value: 'AND'  });};//   ╦╔═╗╦╔╗╔  ╔═╗╔╦╗╔═╗╔╦╗╔═╗╔╦╗╔═╗╔╗╔╔╦╗╔═╗//   ║║ ║║║║║  ╚═╗ ║ ╠═╣ ║ ║╣ ║║║║╣ ║║║ ║ ╚═╗//  ╚╝╚═╝╩╝╚╝  ╚═╝ ╩ ╩ ╩ ╩ ╚═╝╩ ╩╚═╝╝╚╝ ╩ ╚═╝var processJoin = function processJoin(value, joinType, results) {  // Ensure we have an array value  if (!_.isArray(value)) {    value = [value];  }  _.each(value, function processJoinInstructions(joinInstructions) {    // Add a JOIN token    results.push({      type: 'IDENTIFIER',      value: joinType.toUpperCase()    });    // Ensure the instructions include a FROM and an ON and that the ON    // is made up of two table keys.    if (!_.has(joinInstructions, 'from') || !_.has(joinInstructions, 'on')) {      throw new Error('Invalid join instructions');    }    // Check if this is an AND or an OR join statement. An AND statement will    // just be an array of conditions and an OR statement will have a single    // OR key as the value.    // Process AND    if (_.isArray(joinInstructions.on)) {      (function andInstructions() {        var JOIN_TABLE = joinInstructions.from;        results.push({ type: 'KEY', value: 'TABLE' });        results.push({ type: 'VALUE', value: JOIN_TABLE });        _.each(joinInstructions.on, function onSet(set) {          var PARENT_TABLE = _.first(_.keys(set));          var CHILD_TABLE = _.keys(set)[1];          var PARENT_COLUMN = set[_.first(_.keys(set))];          var CHILD_COLUMN = set[_.keys(set)[1]];          var setKeys = [            { type: 'COMBINATOR', value: 'AND' },            { type: 'KEY', value: 'TABLE_KEY' },            { type: 'VALUE', value: PARENT_TABLE },            { type: 'KEY', value: 'COLUMN_KEY' },            { type: 'VALUE', value: PARENT_COLUMN },            { type: 'KEY', value: 'TABLE_KEY' },            { type: 'VALUE', value: CHILD_TABLE },            { type: 'KEY', value: 'COLUMN_KEY' },            { type: 'VALUE', value: CHILD_COLUMN }          ];          _.each(setKeys, function appendSet(set) {            results.push(set);          });        });      })();      // Process OR    } else if (_.isArray(joinInstructions.on.or)) {      (function orInstructions() {        var JOIN_TABLE = joinInstructions.from;        results.push({ type: 'KEY', value: 'TABLE' });        results.push({ type: 'VALUE', value: JOIN_TABLE });        _.each(joinInstructions.on.or, function orSet(set) {          var PARENT_TABLE = _.first(_.keys(set));          var CHILD_TABLE = _.keys(set)[1];          var PARENT_COLUMN = set[_.first(_.keys(set))];          var CHILD_COLUMN = set[_.keys(set)[1]];          var setKeys = [            { type: 'COMBINATOR', value: 'OR' },            { type: 'KEY', value: 'TABLE_KEY' },            { type: 'VALUE', value: PARENT_TABLE },            { type: 'KEY', value: 'COLUMN_KEY' },            { type: 'VALUE', value: PARENT_COLUMN },            { type: 'KEY', value: 'TABLE_KEY' },            { type: 'VALUE', value: CHILD_TABLE },            { type: 'KEY', value: 'COLUMN_KEY' },            { type: 'VALUE', value: CHILD_COLUMN }          ];          _.each(setKeys, function appendSet(set) {            results.push(set);          });        });      })();      // Otherwise ensure that the ON key has two keys    } else if (!_.isPlainObject(joinInstructions.on) || _.keys(joinInstructions.on).length !== 2) {      throw new Error('Invalid join instructions');      // Handle normal, single level joins    } else {      (function buildJoinResults() {        var JOIN_TABLE = joinInstructions.from;        var PARENT_TABLE = _.first(_.keys(joinInstructions.on));        var CHILD_TABLE = _.keys(joinInstructions.on)[1];        var PARENT_COLUMN = joinInstructions.on[_.first(_.keys(joinInstructions.on))];        var CHILD_COLUMN = joinInstructions.on[_.keys(joinInstructions.on)[1]];        var joinResults = [          { type: 'KEY', value: 'TABLE' },          { type: 'VALUE', value: JOIN_TABLE },          { type: 'KEY', value: 'TABLE_KEY' },          { type: 'VALUE', value: PARENT_TABLE },          { type: 'KEY', value: 'COLUMN_KEY' },          { type: 'VALUE', value: PARENT_COLUMN },          { type: 'KEY', value: 'TABLE_KEY' },          { type: 'VALUE', value: CHILD_TABLE },          { type: 'KEY', value: 'COLUMN_KEY' },          { type: 'VALUE', value: CHILD_COLUMN }        ];        _.each(joinResults, function appendSet(set) {          results.push(set);        });      })();    }    results.push({      type: 'ENDIDENTIFIER',      value: joinType.toUpperCase()    });  });};//  ╔═╗╦═╗╔═╗╦ ╦╔═╗  ╔╗ ╦ ╦//  ║ ╦╠╦╝║ ║║ ║╠═╝  ╠╩╗╚╦╝//  ╚═╝╩╚═╚═╝╚═╝╩    ╚═╝ ╩var processGroupBy = function processGroupBy(value, results) {  results.push({    type: 'IDENTIFIER',    value: 'GROUPBY'  });  results.push({    type: 'VALUE',    value: value  });  results.push({    type: 'ENDIDENTIFIER',    value: 'GROUPBY'  });};//  ╔═╗╦═╗╔╦╗╔═╗╦═╗  ╔╗ ╦ ╦//  ║ ║╠╦╝ ║║║╣ ╠╦╝  ╠╩╗╚╦╝//  ╚═╝╩╚══╩╝╚═╝╩╚═  ╚═╝ ╩var processOrderBy = function processOrderBy(values, results) {  // Tokenize the order by and then call the tokenizer on the values  results.push({    type: 'IDENTIFIER',    value: 'ORDERBY'  });  if (!_.isArray(values)) {    values = [values];  }  _.each(values, function tokenizeSet(tokenSet) {    tokenizeObject(tokenSet, undefined, undefined, undefined, results);  });  results.push({    type: 'ENDIDENTIFIER',    value: 'ORDERBY'  });};//  ╔═╗╔═╗╔═╗╦═╗╔═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗//  ╠═╣║ ╦║ ╦╠╦╝║╣ ║ ╦╠═╣ ║ ║║ ║║║║╚═╗//  ╩ ╩╚═╝╚═╝╩╚═╚═╝╚═╝╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝var processAggregations = function processAggregations(value, aggregation, results) {  results.push({    type: 'IDENTIFIER',    value: aggregation  });  results.push({    type: 'VALUE',    value: value  });  results.push({    type: 'ENDIDENTIFIER',    value: aggregation  });};//  ╔═╗╔═╗╔═╗╦╔╗╔╔═╗╔╦╗╦╔═╗╔╗╔//  ╠═╝╠═╣║ ╦║║║║╠═╣ ║ ║║ ║║║║//  ╩  ╩ ╩╚═╝╩╝╚╝╩ ╩ ╩ ╩╚═╝╝╚╝var processPagination = function processPagination(value, operator, results) {  results.push({    type: 'IDENTIFIER',    value: operator  });  results.push({    type: 'VALUE',    value: value  });  results.push({    type: 'ENDIDENTIFIER',    value: operator  });};//  ╔═╗╦═╗╔═╗╔═╗╔═╗╔═╗╔═╗  ╦ ╦╔╗╔╦╔═╗╔╗╔//  ╠═╝╠╦╝║ ║║  ║╣ ╚═╗╚═╗  ║ ║║║║║║ ║║║║//  ╩  ╩╚═╚═╝╚═╝╚═╝╚═╝╚═╝  ╚═╝╝╚╝╩╚═╝╝╚╝var processUnion = function processUnion(values, type, results) {  results.push({    type: 'UNION',    value: type  });  _.each(values, function processUnionValue(value, idx) {    // Start each union subquery with an ENDGROUP    results.push({      type: 'GROUP',      value: idx    });    // Build the subquery    checkForSubquery(value, results);    // Close each subquery with an ENDGROUP token    results.push({      type: 'ENDGROUP',      value: idx    });  });  results.push({    type: 'ENDUNION',    value: type  });};//  ╔═╗╦═╗╔═╗╔═╗╔═╗╔═╗╔═╗  ╔═╗╔═╗//  ╠═╝╠╦╝║ ║║  ║╣ ╚═╗╚═╗  ╠═╣╚═╗//  ╩  ╩╚═╚═╝╚═╝╚═╝╚═╝╚═╝  ╩ ╩╚═╝var processAs = function processAs(value, results) {  results.push({    type: 'IDENTIFIER',    value: 'AS'  });  results.push({    type: 'VALUE',    value: value  });  results.push({    type: 'ENDIDENTIFIER',    value: 'AS'  });};//  ╦═╗╔═╗╔╦╗╦ ╦╦═╗╔╗╔╦╔╗╔╔═╗//  ╠╦╝║╣  ║ ║ ║╠╦╝║║║║║║║║ ╦//  ╩╚═╚═╝ ╩ ╚═╝╩╚═╝╚╝╩╝╚╝╚═╝var processReturning = function processReturning(value, results) {  // Add the RETURNING to the results  results.push({    type: 'IDENTIFIER',    value: 'RETURNING'  });  results.push({    type: 'VALUE',    value: value  });  results.push({    type: 'ENDIDENTIFIER',    value: 'RETURNING'  });};module.exports = function tokenizer(expression) {  if (!expression) {    throw new Error('Missing expression');  }  // Hold the built up results  var results = [];  // Kick off recursive parsing of the RQL object  tokenizeObject(expression, undefined, undefined, undefined, results);  // Return the tokenenized result set  return results;};/* eslint-enable no-use-before-define */
 |