UNPKG

persistanz

Version:

Object relational mapping (ORM) library with unique features.

722 lines (669 loc) 31.1 kB
"use strict"; var co = require("co"); var helper = require("./helper"); var DslResolver = require("./DslResolver.js"); function getRootQuery (persQuery) { if (!persQuery.parentQuery) return persQuery; return getRootQuery(persQuery.parentQuery); } /** * The class that provides the query interface. * * **Do not create instances directly, instead use [Persistanz.query()](#Persistanz#query) method * or Model.query() static method.** * @class */ class PersQuery { constructor(opts) { this.opts = opts; this.pers = opts.pers; this.inTransaction = !! opts.tx; //used in adapters to decide weather to destroy acquired connections in eventedQueries. this.abstractAffixes = Object.assign({}, this.pers.abstractAffixes); //create a copy. this.queryAlreadyBuilt = false; this.events = {}; //keys are eventNames, values are array of functions that are listeners. this.resolver = new DslResolver(this); //tomany related props: this.parentQuery = null; this.toManyQueryies = null; //Map. this.toManyField = null; //only toMany queries have this. Comes from modelMeta. this.prefixToParent = null; //only toMany queries have this. } /** * For short: **.f** * * Sets the from clause of the query. * @chainable * @param {string} modelName The name of the model you want to query. * @return {persistanz.PersQuery} The query object itself. */ from (modelName) { //Model.q() already calls from, so prevent setting .from() on model.q(): if (this.resolver.dsl["from"] != null) throw new Error(".from() was already called before."); this.resolver.setDsl("from", modelName); //we process "from" here unlike other clauses because toMany() calls require //the base alias object to be available immediately. this.resolver.processFrom(); return this; } /** * Makes the query so that results are indexed by one of the fields/columns of * the model/table. * * If you called this method, the return value from .exec() is * not an array anymore, but a javascript Map instance, where keys are indexes * and values are objects. So .index("id") will return a Map of rows where keys * are ids. * * See [index field usage](#indexed-result-set) discussion in the guide. * * @param {string|function} indexField A field conformant to the [fieldList](#PersQuery#select) expression or a callback function that accepts one object from the query result set. * @return {persistanz.PersQuery} The query object itself. */ index (fieldExpOrCallback) { this.resolver.setDsl("index", fieldExpOrCallback); return this; } /** * For short: **.s** * * Adds to the query's SELECT clause. * * fieldList is either an array or a comma separated string of fields. Any whitespace is trimmed. A field can be one of the following: * * * A real column name in the table, * * \*: to select all columns just as in normal SQL. * * dot (or a custom separator) separated bridge field names with final part being either a real column name or \*. For example "customer.name", "product.brand.\*". * * dot (or a custom separator) separated toMany field names. For example: "customer.orders.\*". * * A negation expression starting with the ! character. This is to negate the expression, meaning that the columns * generated by the expression won't be present in the select clause of the sql (nor in the returned objects). * This is useful for example when you need to select all columns of the "User" (with \*) except the * "password" field. In this case, your fieldList can be written as "\*, !password", instead of * laboriously listing all columns except the password. Works on bridge field names too, for example: * "customer.\*, !customer.password, !customer.passwordResetToken". * * A partial field name understood by the "field abstraction over affix" mechanism. * * Select clause must exist in a select sql query, so if .select() is not called or fieldList is passed a _null_, persistanz treats it '\*'. * * This method can be called more than once, each time adding more fields to select. * @chainable * @param [fieldList="*"] {string|Array} fieldList A comma separated field list or an array of fields to be added in the select clause. * @return {persistanz.PersQuery} The query object itself. */ select (fieldList) { if (fieldList === "" || fieldList == undefined) return this; this.resolver.addDsl("selects", fieldList); return this; } /** * For short: **.sw** * * Adds to the query's SELECT clause. * * Sometimes selecting individual columns from a bridge field or toMany field becomes too verbose * and require too much typing because of repeated bridges. Using this method, you can shorten it * by setting a bridge field or toMany expression once and writing their fields separately. * * This is essentially a macro which expands to regular .select fields. * * ! and .\* are usable in the fieldList. See [PersQuery.select](#PersQuery#select). * * This method can be called more than once, each time adding more fields to select. * @example * //Instead of * pers.q().f("OrderItem") * .s("product.category.title, product.category.parentCategoryId, product.category.isOnSale"); * //You can * pers.q().f("OrderItem") * .selectWith("product.category", "title, parentCategoryId, isOnSale"); * @chainable * @param {string} withPart An expression resolving to bridge field or a toMany field. * @param [fieldList="*"] {string|Array} fieldList A comma separated field list or an array of fields to be added in the select clause. See [PersQuery.select](#PersQuery#select). * @return {persistanz.PersQuery} The query object itself. */ selectWith (withPart, fieldList) { this.resolver.addDsl("withSelects", {withPart, fieldList}); return this; } /** * For short **.sa** * * Adds to the SELECT clause of the query, but uses [curlyExpression](#quot-curly-expression-quot) syntax. * This is useful when you need to add things to select clause other than regular fields. * * This method can be called more than once, each time adding more fields to select. * @chainable * @example * pers.q().sa("1+1, MAX({id}) as maxID"); * @param {string} selectExpression An expression to add to select clause in [curlyExpression](#quot-curly-expression-quot) syntax. * @return {persistanz.PersQuery} The query object itself. */ selectAlias (selectExpression) { this.resolver.addDsl("aliasSelects", selectExpression); return this; } /** * For short: **.w** * * Sets the WHERE clause of the query. * * If your where clause (whereClause param) does not contain question marks, you may omit the values argument. * values can be a single value, or an array of values. Either case they are properly escaped * by the underlying database binding before * replacing question mark placeHolders in the _whereClause_. * * This method can also be called as a [tagged template](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals). * In this mode, values argument is unused as values are provided by replacement items in the template. * Tagged template mode allows 2 neat tricks which is not available with the normal method call syntax. * 1: When a value is an array, each item in the array is escaped and the sql is filled with ?, ?, ?... sequence for each item. * 2: A value can be a PersQuery instance to enable subqueries like "... where someColumn in (select id from...)". * @chainable * @example * pers.q().f("SomeModel").where("{sentence}=?", "Hello world."); * pers.q().f("SomeModel").where("{sentence}=? and {id}>?", * ["Hello world.", 99]); * //There is no way to escape the curly braces * //in the curlyExpression argument, so having a where clause like * //{sentence}='{hahaha}' confuses the query builder, * //in such cases simply use * pers.q().f("SomeModel").w("{sentence}=?",'{hahaha}'); * * //Using tagged template with an array of values to be used * //in a "IN" query: * var ids = [4, 7, 9]; * pers.q().f("SomeModel").w`{id} IN (${ids})`; * //Above example will generate an SQL similar to this: * //SELECT ... WHERE id IN (?, ?, ?) * //and the values will be passed to the adapter to be escaped * //before the execution. * * //Using tagged template with another PersQuery instance to make * //a subquery: * var subQ = pers.q().f("SomeModel").select("aForeignKeyColumn") * .where("{id} = ?", 5); * pers.q().f("AnotherModel") * .w`{id} IN (${subQ}) and {someColumn} < ${77}`. * //The query builder will generate an SQL similar to this: * //SELECT * from AnotherModel WHERE id IN * // (SELECT aForeignKeyColumn from SomeModel where id = ?) * // and someColumn < ? * //Then before being executed, the query is passed the following * //values (in order) to be escaped: [5, 77] * @param {string} whereClause An expression to set the where clause of the query in [curlyExpression](#quot-curly-expression-quot) syntax. * @param [values=null] {Array|string|numeric} List of values to replace the question mark placeHolders in the whereClause. * @return {persistanz.PersQuery} The query object itself. */ where (whereClause, values) { if (typeof whereClause === "object" && "raw" in whereClause) { //assume tagged template var strings = whereClause.slice(0); var values = Array.from(arguments).slice(1); var resultValues = []; var sql = values.map( (v, i) => { var placeHolder ; //if array multiply ? by the amount of values: if (Array.isArray(v)) { placeHolder = v.map( v => '?' ).join(', '); resultValues = resultValues.concat(v); } else if (v instanceof PersQuery) { //we don't put a placeholder in sql, but put the entire escaped query: var q = v.build().getQuery(); placeHolder = q.sql; if (q.values && q.values.length) resultValues = resultValues.concat(q.values); //assume a primitive type: } else { placeHolder = '?'; resultValues.push(v); } return strings[i] + placeHolder; }).join('') + strings.pop(); this.resolver.setDsl("where", sql); this.resolver.setDsl("whereValues", resultValues); return this; } this.resolver.setDsl("where", whereClause); if (values !== undefined) (values instanceof Array) ? this.resolver.setDsl("whereValues", values) : this.resolver.addDsl("whereValues", values) ; return this; } /** * For short: **.h** * * Sets HAVING clause of the query. Values in values argument are escaped before replacing the question marks in having argument. * * This method can also be called as a [tagged template](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals). * In this mode, values argument is unused as values are provided by replacement items in the template. * @example pers.q().h("COUNT({order.customerId}) = ?", 5740); * pers.q().h`COUNT({order.customerId}) = ${7540}`; * @param {string} having having clause in [curlyExpression](#quot-curly-expression-quot) syntax. * @param [values=null] {Array|string|numeric} List of values to replace the question mark placeHolders in the having param. * @return {persistanz.PersQuery} The query object itself. */ having (having, values) { if (typeof having === "object" && "raw" in having) { //assume tagged template var strings = having.slice(0); values = Array.from(arguments).slice(1); var sql = values.map( (v, i) => strings[i] + "?").join('') + strings.pop(); this.resolver.setDsl("having", sql); this.resolver.setDsl("havingValues", values); return this; } this.resolver.setDsl("having", having); if (values !== undefined) (values instanceof Array) ? this.resolver.setDsl("havingValues", values) : this.resolver.addDsl("havingValues", values) ; return this; } /** * For short: **.o** * * Sets ORDER BY clause of the query. * @example * pers.q().f('Booking').order("{customer.name} desc"); * @param {string} orderBy order by clause in [curlyExpression](#quot-curly-expression-quot) syntax. * @return {persistanz.PersQuery} The query object itself. */ order (orderBy) { this.resolver.setDsl("orderBy", orderBy); return this; } /** * For short: **.l** (lowercase L) * * Sets LIMIT clause of the query. * * If your limit clause (limitExpression param) does not contain question marks, you may omit the values argument. * values can be a single value, or an array of values. Either case they are properly escaped * by the underlying database binding before * replacing question mark placeHolders in the _limitExpression_. * * This method can also be called as a [tagged template](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals). * In this mode, values argument is unused as values are provided by replacement items in the template. * @example * pers.q().f('Customer').limit(10).exec(); * pers.q().f('Customer').limit`${5} offset ${30}`.exec(); * //The above tagged template usage will result in an sql like * //"... limit ? offset ?", and the adapter will be given the * //following values: [5, 30]. * @param {string|numeric} limitExpression limit clause of the query. * @param [values=null] {Array|string|numeric} List of values to replace the question mark placeHolders in the limitExpression. * @return {persistanz.PersQuery} The query object itself. */ limit (limitExpression, values) { if (typeof limitExpression === "object" && "raw" in limitExpression) { //assume tagged template var strings = limitExpression.slice(0); values = Array.from(arguments).slice(1); var sql = values.map( (v, i) => strings[i] + "?").join('') + strings.pop(); this.resolver.setDsl("limit", sql); this.resolver.setDsl("limitValues", values); return this; } this.resolver.setDsl("limit", limitExpression); if (values !== undefined) (values instanceof Array) ? this.resolver.setDsl("limitValues", values) : this.resolver.addDsl("limitValues", values) ; return this; } /** * For short: **.g** * * Sets GROUP BY clause of the query. * @example * pers.q().f('Customer').group("{lastName}"); * @param {string} groupBy group by clause in [curlyExpression](#quot-curly-expression-quot) syntax. * @return {persistanz.PersQuery} The query object itself. */ group (groupBy) { this.resolver.setDsl("groupBy", groupBy); return this; } /** * Requests the query builder to calculate how many rows would have returned * if the query hadn't contained a LIMIT clause. Query builder then creates * behind the scenes a second query to find out the number. * * Note that this changes the structure of the result returned by the .exec method. * Instead of array of objects, return value is an object with 2 properties: * * - **count**: total row count without the limit clause * * - **objects**: actual objects (if not an evented query) array of the original query * * ###### &nbsp; * @example * pers.q().f("User").l(4).calc().exec().then(function(result){ * console.log(result.objects); //Array of User objects * console.log(result.count); //a number * }); * * @return {persistanz.PersQuery} The query object itself. */ calc () { this.resolver.setDsl("calcLimitless", true); return this; } /** * Adds a DISTINCT after the "SELECT" keyword. For SELECT DISTINCT queries. * @chainable * @return {persistanz.PersQuery} The query object itself. */ distinct () { this.resolver.setDsl("distinct", true); return this; } /** * Adds all args, space separated, after the "SELECT" keyword without any changes. * * This is to handle engine specific query options like CALC_FOUND_ROWS, SQL_NO_CACHE etc. * @example * pers.q().options("SQL_NO_CACHE").f("Customer").exec(); * @param {string} options Options to pass to the query. * @return {persistanz.PersQuery} The query object itself. */ options (options) { this.resolver.setDsl("queryOptions", options); return this; } /** * Creates a new toMany query on the parent query. * * See [toMany method guide](#tomany-method) * @param {string} toManyExp Only one field from a [fieldList](#field-list) expression where the part after the final separator being a [toMany field](#tomany-field). * @return {persistanz.PersQuery} A toMany query. */ toMany (toManyExp) { var rootQuery = getRootQuery(this); var q = rootQuery.resolver.resolveFieldExpression(toManyExp, "toMany"); if (! (q instanceof PersQuery)) throw new Error(`'${toManyExp}' does not resolve to a toMany query.`); return q; } _createToManyQuery(prefixParts, toManyField, modelMeta) { var toManyPrefix = prefixParts.join(this.pers.separator); if (! this.toManyQueries) this.toManyQueries = new Map(); if (this.toManyQueries.has(toManyPrefix)) return this.toManyQueries.get(toManyPrefix); var q = new PersQuery(this.opts); q.toManyField = toManyField; q.from(modelMeta.name); q.parentQuery = this; prefixParts.pop(); //remove the toManyQuery part q.prefixToParent = prefixParts.join(this.pers.separator) || ''; q.exec = q.exec.bind(getRootQuery(this)); //use the parent's exec. q.one = q.one.bind(getRootQuery(this)); //use the parent's one. this.toManyQueries.set(toManyPrefix, q); return q; } /** * Registers an event callback to the query and renders it a "streaming query". * *Following event types are defined and can be registered by calling this method multiple times, as many times for each: * * **"object"**: Callback is called with an object (a row) is read from the database. Callback has (object, index) signature. object is the mapped object (a model instance), index is index value of the row if you called .index() method before exec. * * **"end"**: Callback is called when all the rows are fetched. Nothing is passed to the callback arguments. * * **"error"**: Callback is called when an error occurs in the query. Callback has (err) signature where err is the error object. * * **"calc"**: Callback is called if you called .calc() before. Has (count) signature where count is filled with the number representing the number of rows that would have come if the query had no limit clause. * * Some database drivers accept other events too, if you define them, they are also called. * * This method modifies the .exec() result. The difference is that you can't obtain the results from .exec() anymore, you get them via "object" event callback. * * Please see [streaming queries](#streaming-queries) guide. * * @param {string} eventName One of the "object", "end", "error", "calc" or a database specific event name. * @param {function} callback Callback function with various signatures. See the description above. * @return {persistanz.PersQuery} The query object itself. */ on (eventName, cb) { if (! this.events[eventName]) this.events[eventName] = []; //we intercept "object" event so that we can map: if (eventName === "object") { this.events["object"].push( row => { var mapped = this.resolver.mapRow(row); cb(mapped.object, mapped.index); }); } else this.events[eventName].push(cb); return this; } /** * Executes a query and maps the resulting rows to objects. * * Return value (either as the second the parameter to callback or the return promise) * is one of the following, depending on what methods called before the exec: * * - if none of the following methods called before, return value is an Array of objects: .index(), .calc(), .on(). * * - if .index() is called, return value is a Map object where each key is the value of the field you passed to the index method, and each value * is an object associated with that key. * * - if .calc() is called, return value is an object with two properties: .objects and .count. .count gives the total number of rows without the LIMIT clause, * and .objects contain the returning objects either as a Map if .index() called, or a regular Array. * * - if .on() is called, return value does not contain objects, objects are passed to "object" event callback instead. * If .calc() is called it still has the {objects,count} form and .count still gives the total * number of rows without the LIMIT clause, but value of .objects field is meaningless and is set to _true_. If .calc() is not called, return value * is _true_. If .index() is also called, index of each object is passed to the second argument to "calc" event callback. * * ###### * * @example * var ret=function (err, result){}; * //result is an Array object Customer objects: * pers.q().f("Customer").exec(ret); * //result is a Map object where keys are ids of Customers and * //values are the objects with that id: * pers.q().f("Customer").index("id").exec(ret); * //result is an object with .count and .objects fields. .objects is an * //Array and can contain maximum of 3 items. Total items without limit * //is read from result.count: * pers.q().f("Customer").limit(3).calc().exec(ret); * //result is true, objects are passed to the "object" event callback: * pers.q().f("Customer").on("object", function(anObject){}).exec(ret); * //result in {count,objects} form. .objects is a Map: * pers.q().f("Customer").limit(3).calc().index("id").exec(ret); * //result in {count,objects} form. .objects reads true, count value can * //be read both from calc event callback or as result.count. * pers.q().f("Customer") * .on("object", function(anObject, index){ * console.log(index, anObject); * }) * .on("calc", function(totalRows){console.log(totalRows);}) * .exec(ret); * //Like the rest of this library, as an async method, .exec() can * //return promises. So results can be obtained as a promise * //if callback is omitted: * var resultPromise=pers.q().f("Customer").limit(3).calc().exec(); * resultPromise.then(function(result){ * //result is identical to "result" param of the "ret" function. * }); * @param {function=} cb An optional callback in the (err, result) signature. * @return {Promise} Returns a promise which resolves to different things according to what methods called on the query object before .exec(). */ exec (cb) { var fn = co.wrap(function* (persQuery) { persQuery.build(); var adapter = persQuery.opts.tx || persQuery.pers.adapter var mq = persQuery.resolver.sqlClauses.mainQuery; var lq = persQuery.resolver.sqlClauses.limitlessQuery; var hasChildQueries = persQuery.toManyQueries && persQuery.toManyQueries.size > 0; var evented = Object.keys(persQuery.events).length > 0; var rows, calcCount = null; if (evented && hasChildQueries) { throw new Error("Streaming queries may not have toMany fields."); } //we do this first because streaming queries need this value before "end" //event. if (lq) { var calcResult = yield adapter.query(lq.sql, lq.values); var calcCount = + calcResult[0].count; if (persQuery.events.calc) persQuery.events.calc.forEach( cb => cb(calcCount)); } if (! evented) { rows = yield adapter.query(mq.sql, mq.values); if (hasChildQueries && rows.length) { //if child queries exists, lets collect the ids and process them before mapping. for (var childQDef of persQuery.toManyQueries) { //we are looping a Map object var mountPoint = childQDef[0]; var childQ = childQDef[1]; /* ToManyField { name: 'test2s', fkColumn: 'test3Id', isAutoGenerated: true, modelName: 'Test2' } */ //TODO: following works but is not entirely correct. //In pers, foreign keys always map to primary keys so this works. //Ideally, we should get the toColumn property which is the correct //way and we also have that information. var modelMeta = persQuery.resolver.objectAliasMap[childQ.prefixToParent].modelMeta; var pkName = modelMeta.table.pks[0]; var aliasName = persQuery.resolver.objectAliasMap[childQ.prefixToParent].name; var fieldName = `${aliasName}.${pkName}`; var foreignIds = rows.map(row => row[fieldName]); var placeHolders = foreignIds.map(fId => "?").join(', '); var remoteFkColumnName = childQ.toManyField.fkColumn.name; //remoteFkColumnName may have characters that need to be escaped: var sep = persQuery.pers.separator; var eRFKName = remoteFkColumnName.split(sep).join("\\" + sep); childQ.resolver.setDsl("toManyIdsIn", `{${eRFKName}} IN ( ${placeHolders} )`); childQ.resolver.setDsl("toManyIdsInValues", foreignIds); childQ.exec = getRootQuery(childQ).exec.bind(childQ); yield childQ.exec(); } } } var results = evented ? yield adapter.eventedQuery(persQuery.events, mq.sql, mq.values, persQuery.inTransaction) : persQuery.resolver.mapRows(rows) ; //if parent query exists, lets group our results based on parent's foreign key. if (persQuery.parentQuery) { var groupChildObjectsByParentId = (q, results) => { var groupedRows = new Map(); var foreignKeyColumn = persQuery.toManyField.fkColumn.name; //TODO: indexed (Map) version is inefficient because we are creating //Maps TWICE for each row. During _mapRows() they are mapped, but here we Map them //again. This is because our q.aliasMap structure looks like #1.id, so by the //time we get here, we have already lost this information due to mapping has //already been performed. Ideal situation is to create a map only here (and) //not in _mapRows, but we need to find a a way to work with index fields //directy on the objects. if (results instanceof Map) results.forEach(function(object, key) { if (!groupedRows.has(object[foreignKeyColumn])) groupedRows.set(object[foreignKeyColumn], new Map()); groupedRows.get(object[foreignKeyColumn]).set(key, object); }); else results.forEach(function(object){ if (!groupedRows.has(object[foreignKeyColumn])) groupedRows.set(object[foreignKeyColumn],[]); groupedRows.get(object[foreignKeyColumn]).push(object); }); return groupedRows; } return persQuery.groupedRows = groupChildObjectsByParentId(persQuery, results); } if (lq) { //requested: how many rows would have returned w/o limit return { count: calcCount, objects: results, }; } return results; }); return helper.polycallTx(this.opts.tx, fn, cb, null, this); } /** * Similar to .exec() but tries to return only one object if query brought any, null if not. * * Automatically calls .limit(1) so previous call to this methods are overridden. * If .calc() or .index() with a not-null value called, the result structure is identical to calling .exec(). * @param {function=} cb An optional callback in the (err, result) signature. * @return {Promise} Returns a promise which resolves to different things according to what methods called on the query object before .one(). */ one (cb) { var fn = co.wrap(function *(persQuery){ persQuery.limit(1); var results = yield persQuery.exec(); if (Array.isArray(results)) return results[0] || null; return results; //it must be evented query which the caller handles. }); return helper.polycall(fn, cb, null, this); } /** * Builds the SQL query. * * Explicitly calling this method is normally not necessary, as .exec method * automatically calls it. However, if you are interested only in the generated * SQL and not the results, you can get it without executing the query. * @example * var sql=pers.q().f("Customer").s("name").w("{id}=4").build().getQuery(); * console.log(sql); //logs the generated SQL query. * @return {persistanz.PersQuery} The query object itself. */ build () { if (!this.queryAlreadyBuilt) this.resolver.build(); this.queryAlreadyBuilt = true; return this; } getQuery () { return this.resolver.sqlClauses.mainQuery; } /** * When persistanz can't find a * column listed in clauses it attempts to add the affix to the given field name and * sees if the new version matches a column name. * * PersQuery instance inherits affixes from the Persistanz object, so it has * its own copy of affixes. * * To clear a particular affix, pass _null_ as type argument. * To clear all abstractions pass _null_ as affix argument. * @example * pers.q().f("Product").affix("special_", "prefix"); * //if Product doesn't have price column but a special_price * //column, the returning object will have a .price column whose * //value is filled with the value of special_price. * @see [Field abstraction over affix](#field-abstraction-over-affix) * @param {string|null} affix A string that will be appended or prepended to all the field names when persistanz can't find the given field. * @param [type='suffix'] {string} Possible values are "prefix" and "suffix". Depending on this value, affix will be appended or prepended. * @return {persistanz.PersQuery} The query object itself. */ affix (affix, type) { if (affix == null) { this.abstractAffixes = {}; return this; } if (type === null) { delete this.abstractAffixes[affix]; return this; } if (type == undefined) type = "suffix"; if (type !== "suffix" && type !== "prefix") throw new Error("Abstract affix type must be one of suffix, prefix"); this.abstractAffixes[affix] = {type, affix}; return this; } } PersQuery.prototype.f = PersQuery.prototype.from; PersQuery.prototype.s = PersQuery.prototype.select; PersQuery.prototype.sa = PersQuery.prototype.selectAlias; PersQuery.prototype.sw = PersQuery.prototype.selectWith; PersQuery.prototype.g = PersQuery.prototype.group; PersQuery.prototype.w = PersQuery.prototype.where; PersQuery.prototype.l = PersQuery.prototype.limit; PersQuery.prototype.h = PersQuery.prototype.having; PersQuery.prototype.o = PersQuery.prototype.order; module.exports = PersQuery;