From 826b59716af91c8565dfde82a248fcab1975c31f Mon Sep 17 00:00:00 2001 From: Kyriakos Barbounakis Date: Sun, 21 Dec 2025 19:03:49 +0200 Subject: [PATCH 1/2] format cross join statement --- formatter.js | 35 +- query.d.ts | 9 +- query.js | 68 +++- spec/QueryExpressin.selectJson.spec.js | 420 +++++++++++++++++++++++ spec/test/config/models/SimpleOrder.json | 206 +++++++++++ 5 files changed, 718 insertions(+), 20 deletions(-) create mode 100644 spec/QueryExpressin.selectJson.spec.js create mode 100644 spec/test/config/models/SimpleOrder.json diff --git a/formatter.js b/formatter.js index 1286552..8565001 100644 --- a/formatter.js +++ b/formatter.js @@ -6,7 +6,7 @@ var { Args, AbstractMethodError} = require('@themost/common'); const { QueryExpression, QueryField, QueryEntity } = require('./query'); const { JSONArray, JSONObject } = require('@themost/json'); const {MethodCallExpression} = require('./expressions'); -const {isNameReference, trimNameReference} = require('./name-reference'); +const {isNameReference, trimNameReference, isMethodOrNameReference} = require('./name-reference'); var instanceOf = require('./instance-of').instanceOf; var ObjectNameValidator = require('./object-name.validator').ObjectNameValidator; @@ -1081,26 +1081,39 @@ SqlFormatter.prototype.formatSelect = function(obj) { //enumerate joins _.forEach(joins, function(x) { - var joinType; + var joinType = (x.$entity.$join || 'inner').toUpperCase(); if (instanceOf(x.$entity, QueryExpression)) { - // get join type - joinType = (x.$entity.$join || 'inner').toUpperCase(); // append join statement sql = sql.concat(sprintf(' %s JOIN (%s)', joinType, $this.format(x.$entity))); // add alias if (x.$entity.$alias) { sql = sql.concat(getAliasKeyword.bind($this)()).concat($this.escapeName(x.$entity.$alias)); } - } - else { - // get join table name + } else { + sql += ' ' + joinType + ' JOIN '; + if (typeof x.$entity === 'object') { + const [key] = Object.keys(x.$entity); + if (isMethodOrNameReference(key)) { + sql += $this.escape(x.$entity); + const alias = x.$as || x.$entity.$as; + if (alias) { + sql += getAliasKeyword.bind($this)(); + sql += $this.escapeName(alias); + } + const strWhere = $this.formatWhere(x.$with); + if (typeof strWhere === 'string' && strWhere.length > 0) { + sql += ' ON ' + strWhere; + } + return; + } + } + //get join table name var table = Object.key(x.$entity); - // get on statement (the join comparison) - joinType = (x.$entity.$join || 'inner').toUpperCase(); - sql = sql.concat(' '+ joinType + ' JOIN ').concat($this.escapeName(table)); + sql += $this.escapeEntity(table); // add alias - if (x.$entity.$as) + if (x.$entity.$as) { sql = sql.concat(getAliasKeyword.bind($this)()).concat($this.escapeName(x.$entity.$as)); + } } if (_.isArray(x.$with)) { diff --git a/query.d.ts b/query.d.ts index 530b02c..bcdf8d4 100644 --- a/query.d.ts +++ b/query.d.ts @@ -44,11 +44,10 @@ export declare class QueryExpression { from(entity: QueryExpression): this; from(entity: (string | QueryEntity | QueryExpression), ...additionalEntity: (string | QueryEntity | QueryExpression)[]): this; join(entity: any, props?: any, alias?: any): this; - join(entity: QueryEntity): this; - leftJoin(entity: any, props?: any, alias?: any): this; - leftJoin(entity: QueryEntity): this; - rightJoin(entity: any, props?: any, alias?: any): this; - rightJoin(entity: QueryEntity): this; + join(entity: QueryEntity | any): this; + leftJoin(entity: QueryEntity | any): this; + rightJoin(entity: QueryEntity | any): this; + crossJoin(entity: any): this; with(obj: any): this; with(expr: (value: T, otherValue: J, ...params: any[]) => any, ...params: any[]): this; orderBy(expr: string | any): this; diff --git a/query.js b/query.js index 6ed4402..9cc835f 100644 --- a/query.js +++ b/query.js @@ -10,6 +10,7 @@ var SyncSeriesEventEmitter = require('@themost/events').SyncSeriesEventEmitter; //noinspection JSUnusedLocalSymbols require('./natives'); var {Expression} = require("./expressions"); +var {isMethodOrNameReference} = require('./name-reference'); /** * @class * @constructor @@ -264,6 +265,12 @@ QueryExpression.prototype.fields = function() { //todo::add fields if any } else { + if (typeof x.$entity === 'object') { + var [key] = Object.keys(x.$entity); + if (isMethodOrNameReference(key)) { + return; + } + } var table = Object.key(x.$entity), tableFields = x.$entity[table] || []; _.forEach(tableFields, function(y) { if (typeof x === 'string') { @@ -728,11 +735,54 @@ QueryExpression.prototype.join = function(entity, props, alias) { else if (entity instanceof QueryExpression) { //do nothing (clone object) obj = entity; - } - else { + } else { + // check if the given entity is a table-scalar function + // e.g. { $jsonEach: [ '$Users.group' ] } or + // using an alias { groups: { $jsonEach: [ '$Users.group' ] } } + var [key] = Object.keys(entity); + if (key && isMethodOrNameReference(key)) { + // the entity is a table-scalar function without alias + // set temporary expand object + if (alias == null) { + throw new Error('Alias is required when joining a table-like expression.'); + } + this.privates.expand = { + $entity: obj, + $as: alias // use alias if any + }; + Object.defineProperty(this, '$joinCollection', { + configurable: true, + enumerable: false, + writable: true, + value: alias + }); + return this; + } else if (key) { + var [methodKey] = Object.keys(entity[key]); + if (methodKey && isMethodOrNameReference(methodKey)) { + // the entity is a table-scalar function with alias + obj = entity[key]; + if (alias != null && alias !== key) { + throw new Error('Alias mismatch when joining a table-like expression. Given alias does not match the entity alias.'); + } + // set temporary expand object + this.privates.expand = { + $entity: obj, + $as: alias || key // use alias if any + }; + Object.defineProperty(this, '$joinCollection', { + configurable: true, + enumerable: false, + writable: true, + value: alias || key + }); + return this; + } + } obj[entity] = props || []; - if (typeof alias === 'string') - obj.$as=alias; + if (typeof alias === 'string') { + obj.$as = alias; + } } this.privates.expand = { $entity: obj }; // set current join entity @@ -783,6 +833,16 @@ QueryExpression.prototype.join = function(entity, props, alias) { return this; } +QueryExpression.prototype.crossJoin = function(entity) { + this.join(entity); + if (this.privates.expand && this.privates.expand.$entity) { + this.privates.expand.$entity.$join = 'cross'; + } + // a cross join cannot have a with expression + this.with(false); + return this; +} + /** * Sets the join expression of the last join entity * @param obj {*} diff --git a/spec/QueryExpressin.selectJson.spec.js b/spec/QueryExpressin.selectJson.spec.js new file mode 100644 index 0000000..9dfd7b3 --- /dev/null +++ b/spec/QueryExpressin.selectJson.spec.js @@ -0,0 +1,420 @@ +// noinspection SpellCheckingInspection + +import {MemberExpression, MethodCallExpression} from '../index'; +import { QueryEntity, QueryExpression } from '../index'; +import { SqliteFormatter } from '@themost/sqlite'; +import { MemoryAdapter } from './test/TestMemoryAdapter'; +import { MemoryFormatter } from './test/TestMemoryFormatter'; +import SimpleOrderSchema from './test/config/models/SimpleOrder.json'; + +if (typeof SqliteFormatter.prototype.$jsonGet !== 'function') { + SqliteFormatter.prototype.$jsonGet = function(expr) { + if (typeof expr.$name !== 'string') { + throw new Error('Invalid json expression. Expected a string'); + } + const parts = expr.$name.split('.'); + const extract = this.escapeName(parts.splice(0, 2).join('.')); + return `json_extract(${extract}, '$.${parts.join('.')}')`; + }; + SqliteFormatter.prototype.$jsonArray = function(expr) { + return `json_each(${this.escapeName(expr)})`; + } + // const superEscape = SqlUtils.escape; + // SqlUtils.escape = function(value) { + // if (isObjectDeep(value)) { + // return `'${JSON.stringify(value)}'`; + // } else { + // const args = Array.from(arguments) + // return superEscape.apply(null, args); + // } + // } +} + +/** + * @param { MemoryAdapter } db + * @returns {Promise} + */ +async function createSimpleOrders(db) { + const { source } = SimpleOrderSchema; + const exists = await db.table(source).existsAsync(); + if (!exists) { + await db.table(source).createAsync(SimpleOrderSchema.fields); + } + // get some orders + const orders = await db.executeAsync( + new QueryExpression().from('OrderBase').select( + ({orderDate, discount, discountCode, orderNumber, paymentDue, + dateCreated, dateModified, createdBy, modifiedBy, + orderStatus, orderedItem, paymentMethod, customer}) => { + return { orderDate, discount, discountCode, orderNumber, paymentDue, + dateCreated, dateModified, createdBy, modifiedBy, + orderStatus, orderedItem, paymentMethod, customer}; + }) + .orderByDescending((x) => x.orderDate).take(10), [] + ); + const paymentMethods = await db.executeAsync( + new QueryExpression().from('PaymentMethodBase').select( + ({id, name, alternateName, description}) => { + return { id, name, alternateName, description }; + }), [] + ); + const orderStatusTypes = await db.executeAsync( + new QueryExpression().from('OrderStatusTypeBase').select( + ({id, name, alternateName, description}) => { + return { id, name, alternateName, description }; + }), [] + ); + const orderedItems = await db.executeAsync( + new QueryExpression().from('ProductData').select( + ({id, name, category, model, releaseDate, price}) => { + return { id, name, category, model, releaseDate, price }; + }), [] + ); + const customers = await db.executeAsync( + new QueryExpression().from('PersonData').select( + ({id, familyName, givenName, jobTitle, email, description, address}) => { + return { id, familyName, givenName, jobTitle, email, description, address }; + }), [] + ); + const postalAddresses = await db.executeAsync( + new QueryExpression().from('PostalAddressData').select( + ({id, streetAddress, postalCode, addressLocality, addressCountry, telephone}) => { + return {id, streetAddress, postalCode, addressLocality, addressCountry, telephone }; + }), [] + ); + // get + const items = orders.map((order) => { + const { orderDate, discount, discountCode, orderNumber, paymentDue, + dateCreated, dateModified, createdBy, modifiedBy } = order; + const orderStatus = orderStatusTypes.find((x) => x.id === order.orderStatus); + const orderedItem = orderedItems.find((x) => x.id === order.orderedItem); + const paymentMethod = paymentMethods.find((x) => x.id === order.paymentMethod); + const customer = customers.find((x) => x.id === order.customer); + if (customer) { + customer.address = postalAddresses.find((x) => x.id === customer.address); + delete customer.address?.id; + } + return { + orderDate, + discount, + discountCode, + orderNumber, + paymentDue, + orderStatus, + orderedItem, + paymentMethod, + customer, + dateCreated, + dateModified, + createdBy, + modifiedBy + } + }); + for (const item of items) { + await db.executeAsync(new QueryExpression().insert(item).into(source), []); + } +} + +function onResolvingJsonMember(event) { + let member = event.fullyQualifiedMember.split('.'); + const field = SimpleOrderSchema.fields.find((x) => x.name === member[0]); + if (field == null) { + return; + } + if (field.type !== 'Json') { + return; + } + event.object = event.target.$collection; + event.member = new MethodCallExpression('jsonGet', [ + new MemberExpression(event.target.$collection + '.' + event.fullyQualifiedMember) + ]); +} + +function onResolvingJsonEachQualifiedMember(event) { + if (typeof event.member !== 'string') { + return; + } + const fullyQualifiedMember = event.fullyQualifiedMember.split('.'); + const [member] = fullyQualifiedMember; + const query = event.target; + const joins = Array.isArray(query.$expand) ? query.$expand : (query.$expand ? [ + query.$expand + ] : []); + const join = joins.find((x) => { + return x.$as === member; + }); + if (join && Object.prototype.hasOwnProperty.call(join.$entity, '$jsonEach')) { + event.object = member; + fullyQualifiedMember.splice(1,0, 'value'); + // noinspection JSValidateTypes + event.member = new MethodCallExpression('jsonGet', [ + new MemberExpression(fullyQualifiedMember.join('.')) + ]); + } +} + +function onResolvingJsonEachMember(event) { + if (typeof event.member !== 'string') { + return; + } + const fullyQualifiedMember = event.member.split('.'); + const [,member] = fullyQualifiedMember; + const query = event.target; + const joins = Array.isArray(query.$expand) ? query.$expand : (query.$expand ? [ + query.$expand + ] : []); + const join = joins.find((x) => { + return x.$as === member; + }); + + if (join && Object.prototype.hasOwnProperty.call(join.$entity, '$jsonEach')) { + event.object = member; + // noinspection JSValidateTypes + event.member = new MemberExpression([ + member, + 'value' + ].join('.')); + } +} + +describe('SqlFormatter', () => { + + /** + * @type {MemoryAdapter} + */ + let db; + beforeAll(() => {return new Promise(done => { + MemoryAdapter.create({ + name: 'local', + database: './spec/db/local.db', + logLevel: 'debug' + }).then((adapter) => { + db = adapter; + return done(); + }).catch((err) => { + return done(err); + }); + })}); + afterAll(() => {return new Promise(done => { + if (db) { + return db.close(() => { + return MemoryAdapter.drop(db).then(() => { + return done(); + }); + }); + } + return done(); + })}); + + it('should select json field', async () => { + await createSimpleOrders(db); + const Orders = new QueryEntity('SimpleOrders'); + const query = new QueryExpression(); + query.resolvingJoinMember.subscribe(onResolvingJsonMember); + query.select((x) => { + // noinspection JSUnresolvedReference + return { + id: x.id, + customer: x.customer.description + } + }) + .from(Orders); + const formatter = new MemoryFormatter(); + const sql = formatter.format(query); + expect(sql).toEqual('SELECT `SimpleOrders`.`id` AS `id`, json_extract(`SimpleOrders`.`customer`, \'$.description\') AS `customer` FROM `SimpleOrders`'); + /** + * @type {Array<{id: number, customer: string}>} + */ + const results = await db.executeAsync(sql, []); + expect(results).toBeTruthy(); + for (const result of results) { + expect(result).toBeTruthy(); + expect(result.id).toBeTruthy(); + expect(result.customer).toBeTruthy(); + } + }); + + it('should select nested json field', async () => { + await createSimpleOrders(db); + const Orders = new QueryEntity('SimpleOrders'); + const query = new QueryExpression(); + query.resolvingJoinMember.subscribe(onResolvingJsonMember); + query.select((x) => { + // noinspection JSUnresolvedReference + return { + id: x.id, + customer: x.customer.description, + address: x.customer.address.streetAddress + } + }) + .from(Orders); + const formatter = new MemoryFormatter(); + const sql = formatter.format(query); + expect(sql).toEqual('SELECT `SimpleOrders`.`id` AS `id`, ' + + 'json_extract(`SimpleOrders`.`customer`, \'$.description\') AS `customer`, ' + + 'json_extract(`SimpleOrders`.`customer`, \'$.address.streetAddress\') AS `address` ' + + 'FROM `SimpleOrders`'); + /** + * @type {Array<{id: number, customer: string}>} + */ + const results = await db.executeAsync(sql, []); + expect(results).toBeTruthy(); + for (const result of results) { + expect(result).toBeTruthy(); + expect(result.id).toBeTruthy(); + expect(result.customer).toBeTruthy(); + } + }); + + it('should select nested json field with method', async () => { + await createSimpleOrders(db); + const Orders = new QueryEntity('SimpleOrders'); + const query = new QueryExpression(); + query.resolvingJoinMember.subscribe(onResolvingJsonMember); + query.select((x) => { + // noinspection JSUnresolvedReference + return { + id: x.id, + customer: x.customer.description, + releaseYear: x.orderedItem.releaseDate.getFullYear() + } + }) + .from(Orders); + const formatter = new MemoryFormatter(); + const sql = formatter.format(query); + /** + * @type {Array<{id: number, customer: string, releaseYear: number}>} + */ + const results = await db.executeAsync(sql, []); + expect(results).toBeTruthy(); + for (const result of results) { + expect(result).toBeTruthy(); + expect(result.releaseYear).toBeTruthy(); + } + }); + + it('should select json object', async () => { + await createSimpleOrders(db); + const Orders = new QueryEntity('SimpleOrders'); + const query = new QueryExpression(); + query.resolvingJoinMember.subscribe(onResolvingJsonMember); + query.select((x) => { + // noinspection JSUnresolvedReference + return { + id: x.id, + customer: x.customer, + orderedItem: x.orderedItem + } + }) + .from(Orders); + const formatter = new MemoryFormatter(); + const sql = formatter.format(query); + /** + * @type {Array<{id: number, customer: string, releaseYear: number}>} + */ + const results = await db.executeAsync(sql, []); + expect(results).toBeTruthy(); + for (const result of results) { + if (typeof result.customer === 'string') { + const customer = JSON.parse(result.customer); + expect(customer).toBeTruthy(); + } + } + }); + + it('should use json each with filter', async () => { + const Users = new QueryEntity('Users'); + const userGroup = { + $jsonEach: [ + '$Users.groups' + ] + }; + const query = new QueryExpression(); + query.resolvingJoinMember.subscribe(onResolvingJsonEachQualifiedMember); + query.select( + (x) => { + return { + id: x.id, + username: x.username, + email: x.email, + groups: x.groups, + } + } + ).from(Users).leftJoin({ + userGroup + }).with(false).where((x) => { + // noinspection JSUnresolvedReference + return x.userGroup.group_name === 'Administrators'; + }); + + const formatter = new MemoryFormatter(); + const sql = formatter.format(query); + const results = await db.executeAsync(sql, []); + expect(results).toBeTruthy(); + expect(results.length).toEqual(2); + }); + + it('should use json each with join expression', async () => { + const Users = new QueryEntity('Users'); + const userGroup = { + $jsonEach: [ + '$Users.groups' + ] + }; + const query = new QueryExpression(); + query.resolvingJoinMember.subscribe(onResolvingJsonEachQualifiedMember); + query.select( + (x) => { + return { + id: x.id, + username: x.username, + email: x.email + } + } + ).from(Users).crossJoin({ + userGroup + }).where((x) => { + // noinspection JSUnresolvedReference + return x.userGroup.group_name === 'Administrators'; + }); + + const formatter = new MemoryFormatter(); + const sql = formatter.format(query); + const results = await db.executeAsync(sql, []); + expect(results).toBeTruthy(); + expect(results.length).toEqual(2); + }); + + it('should use json each values with filter', async () => { + const Users = new QueryEntity('Users'); + const userTag = { + $jsonEach: [ + '$Users.tags' + ] + }; + const query = new QueryExpression(); + query.resolvingMember.subscribe(onResolvingJsonEachMember); + query.resolvingJoinMember.subscribe(onResolvingJsonEachQualifiedMember); + query.from(Users).select( + (x) => { + return { + id: x.id, + username: x.username, + email: x.email, + tags: x.tags, + } + } + ).crossJoin({ + userTag + }).where((x) => { + // noinspection JSUnresolvedReference + return x.userTag === 'admin'; + }); + const formatter = new MemoryFormatter(); + const sql = formatter.format(query); + const results = await db.executeAsync(sql, []); + expect(results).toBeTruthy(); + expect(results.length).toEqual(2); + }); + +}); \ No newline at end of file diff --git a/spec/test/config/models/SimpleOrder.json b/spec/test/config/models/SimpleOrder.json new file mode 100644 index 0000000..4befe1a --- /dev/null +++ b/spec/test/config/models/SimpleOrder.json @@ -0,0 +1,206 @@ +{ + "$schema": "https://themost-framework.github.io/themost/models/2018/2/schema.json", + "name": "SimpleOrder", + "title": "SimpleOrders", + "source": "SimpleOrders", + "hidden": false, + "sealed": false, + "abstract": false, + "version": "1.0.0", + "fields": [ + { + "@id": "https://themost.io/schemas/id", + "name": "id", + "title": "ID", + "description": "The identifier of the item.", + "type": "Counter", + "primary": true + }, + { + "name": "acceptedOffer", + "title": "Accepted Offer", + "description": "The offer e.g. product included in the order.", + "type": "Json", + "additionalType": "Offer" + }, + { + "name": "billingAddress", + "title": "Billing Address", + "description": "The billing address for the order.", + "type": "Json", + "additionalType": "PostalAddress" + }, + { + "name": "customer", + "title": "Customer", + "description": "Party placing the order.", + "type": "Json", + "additionalType": "Person", + "editable": false, + "nullable": false + }, + { + "name": "discount", + "title": "Discount", + "description": "Any discount applied (to an Order).", + "type": "Number" + }, + { + "name": "discountCode", + "title": "Discount Code", + "description": "Code used to redeem a discount.", + "type": "Text" + }, + { + "name": "discountCurrency", + "title": "Discount Currency", + "description": "The currency (in 3-letter ISO 4217 format) of the discount.", + "type": "Text" + }, + { + "name": "isGift", + "title": "Is Gift", + "description": "Was the offer accepted as a gift for someone other than the buyer.", + "type": "Boolean" + }, + { + "name": "merchant", + "title": "Merchant", + "description": "The party taking the order (e.g. Amazon.com is a merchant for many sellers).", + "type": "Json", + "additionalType": "Party" + }, + { + "name": "orderDate", + "title": "Order Date", + "description": "Date order was placed.", + "type": "DateTime", + "value": "javascript:return new Date();" + }, + { + "name": "orderedItem", + "title": "Ordered Item", + "description": "The item ordered.", + "type": "Json", + "additionalType": "Product", + "expandable": true, + "editable": true, + "nullable": false + }, + { + "name": "orderNumber", + "title": "Order Number", + "description": "The identifier of the transaction.", + "type": "Text", + "readonly": true, + "value": "javascript:return this.numbers(12);" + }, + { + "name": "orderStatus", + "title": "Order Status", + "description": "The current status of the order.", + "type": "Json", + "additionalType": "OrderStatusType", + "expandable": true, + "nullable": false, + "value": "javascript:return { alternateName: 'OrderProcessing' };" + }, + { + "name": "paymentDue", + "title": "Payment Due", + "description": "The date that payment is due.", + "type": "DateTime" + }, + { + "name": "paymentMethod", + "title": "Payment Method", + "description": "The name of the credit card or other method of payment for the order.", + "type": "Json", + "additionalType": "PaymentMethod", + "expandable": true + }, + { + "name": "paymentUrl", + "title": "Payment Url", + "description": "The URL for sending a payment.", + "type": "URL" + }, + { + "name": "additionalType", + "title": "Additional Type", + "description": "An additional type for the item, typically used for adding more specific types from external vocabularies in microdata syntax. This is a relationship between something and a class that the thing is in. In RDFa syntax, it is better to use the native RDFa syntax - the 'typeof' attribute - for multiple types. Schema.org tools may have only weaker understanding of extra types, in particular those defined externally.", + "type": "Text", + "readonly": true, + "value": "javascript:return this.model.name;" + }, + { + "name": "description", + "title": "Description", + "description": "A short description of the item.", + "type": "Text" + }, + { + "name": "dateCreated", + "title": "Date Created", + "description": "The date on which this item was created.", + "type": "Date", + "value": "javascript:return (new Date());", + "readonly": true + }, + { + "name": "dateModified", + "title": "Date Modified", + "description": "The date on which this item was most recently modified.", + "type": "Date", + "readonly": true, + "value": "javascript:return (new Date());", + "calculation": "javascript:return (new Date());" + }, + { + "name": "createdBy", + "title": "Created By", + "description": "Created by user.", + "type": "Integer", + "value": "javascript:return this.user();", + "readonly": true + }, + { + "name": "modifiedBy", + "title": "Modified By", + "description": "Modified by user.", + "type": "Integer", + "calculation": "javascript:return this.user();", + "readonly": true + } + ], + "views": [ + { + "name": "delivered", + "title": "Delivered Orders", + "filter": "orderStatus eq 1", + "order": "dateCreated desc" + }, + { + "name": "latest", + "title": "Latest Orders", + "filter": "orderDate gt lastMonth()", + "order": "dateCreated desc" + } + ], + "privileges": [ + { + "mask": 15, + "type": "global" + }, + { + "mask": 15, + "type": "global", + "account": "Administrators" + }, + { + "mask": 1, + "type": "self", + "filter": "customer/user eq me()" + } + ] +} \ No newline at end of file From 676ae967d07b91289fdd98b0ac6d2d22ccd51fcd Mon Sep 17 00:00:00 2001 From: Kyriakos Barbounakis Date: Sun, 21 Dec 2025 19:04:54 +0200 Subject: [PATCH 2/2] 2.6.82 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 64dccee..3916a78 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@themost/query", - "version": "2.6.81", + "version": "2.6.82", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@themost/query", - "version": "2.6.81", + "version": "2.6.82", "license": "BSD-3-Clause", "dependencies": { "@themost/events": "^1.5.0", diff --git a/package.json b/package.json index d41276a..ebb55e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@themost/query", - "version": "2.6.81", + "version": "2.6.82", "description": "@themost/query is a query builder for SQL. It includes a wide variety of helper functions for building complex SQL queries under node.js.", "main": "index.js", "scripts": {