Skip to content

Commit aea6a05

Browse files
authored
format insert into expression (#103)
* format insert into expression * remove table alias * validate field name * 2.5.25 * 2.5.26
1 parent 8dc79f5 commit aea6a05

File tree

9 files changed

+285
-24
lines changed

9 files changed

+285
-24
lines changed

formatter.d.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,16 +67,17 @@ export declare class SqlFormatter {
6767
branches: {case: any, then: any}[], defaultValue?: any
6868
}): string | any;
6969

70-
formatCount(query: QueryExpression|Object): string;
71-
formatFixedSelect(query: QueryExpression|Object): string;
72-
formatSelect(query: QueryExpression|Object): string;
73-
formatLimitSelect(query: QueryExpression|Object): string;
70+
formatCount(query: QueryExpression|any): string;
71+
formatFixedSelect(query: QueryExpression|any): string;
72+
formatSelect(query: QueryExpression|any): string;
73+
formatLimitSelect(query: QueryExpression|any): string;
7474
formatField(obj: any): string;
7575
formatOrder(obj: any): string;
7676
formatGroupBy(obj: any): string;
77-
formatInsert(query: QueryExpression|Object): string;
78-
formatUpdate(query: QueryExpression|Object): string;
79-
formatDelete(query: QueryExpression|Object): string;
77+
formatInsert(query: QueryExpression|any): string;
78+
protected formatInsertInto(query: QueryExpression): string;
79+
formatUpdate(query: QueryExpression|any): string;
80+
formatDelete(query: QueryExpression|any): string;
8081
escapeName(name: string): string;
8182
formatFieldEx(obj: any, format: string);
8283
format(obj: any, s?: string);

formatter.js

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ var SqlUtils = require('./utils').SqlUtils;
33
var sprintf = require('sprintf-js').sprintf;
44
var _ = require('lodash');
55
var query = require('./query');
6-
var QueryExpression = query.QueryExpression;
7-
var QueryField = query.QueryField;
6+
var { Args } = require('@themost/common');
7+
const { QueryExpression, QueryField } = require('./query');
88
var instanceOf = require('./instance-of').instanceOf;
9-
var ObjectNameValidator = require('./object-name.validator').ObjectNameValidator
9+
var ObjectNameValidator = require('./object-name.validator').ObjectNameValidator;
1010

1111
if (typeof Object.key !== 'function') {
1212
/**
@@ -1162,12 +1162,16 @@ SqlFormatter.prototype.formatGroupBy = function(obj)
11621162
SqlFormatter.prototype.formatInsert = function(obj)
11631163
{
11641164
var self= this, sql = '';
1165-
if (_.isNil(obj.$insert))
1165+
if (obj.$insert == null) {
11661166
throw new Error('Insert expression cannot be empty at this context.');
1167-
//get entity name
1167+
}
1168+
// get entity name
11681169
var entity = Object.key(obj.$insert);
1169-
//get entity fields
1170+
// get entity fields
11701171
var obj1 = obj.$insert[entity];
1172+
if (obj1 instanceof QueryExpression) {
1173+
return self.formatInsertInto(obj);
1174+
}
11711175
var props = [];
11721176
for(var prop in obj1)
11731177
if (Object.prototype.hasOwnProperty.call(obj1, prop))
@@ -1180,6 +1184,57 @@ SqlFormatter.prototype.formatInsert = function(obj)
11801184
}).join(', ') ,')');
11811185
return sql;
11821186
};
1187+
/**
1188+
* @protected
1189+
* @param {QueryExpression} expr
1190+
* @returns {string}
1191+
*/
1192+
SqlFormatter.prototype.formatInsertInto = function(expr) {
1193+
var self= this;
1194+
if (expr.$insert == null) {
1195+
throw new Error('Insert expression cannot be empty at this context.');
1196+
}
1197+
//get entity name
1198+
var entity = Object.key(expr.$insert);
1199+
var insertExpr = expr.$insert[entity];
1200+
Args.check(insertExpr instanceof QueryExpression, new Error('Invalid insert expression. Expected a valid query expression.'));
1201+
const select = insertExpr.$select;
1202+
Args.check(select != null, new Error('Invalid insert expression. Expected a valid select expression.'));
1203+
var sql = 'INSERT INTO ' + self.escapeName(entity);
1204+
// get fields
1205+
var fields = [];
1206+
var FormatterCtor = Object.getPrototypeOf(self).constructor;
1207+
/**
1208+
* @type {SqlFormatter}
1209+
*/
1210+
var formatter = new FormatterCtor();
1211+
for (var key in select) {
1212+
if (Object.prototype.hasOwnProperty.call(select, key)) {
1213+
var selectFields = select[key];
1214+
fields = selectFields.map(function(selectField) {
1215+
var name;
1216+
if (selectField instanceof QueryField) {
1217+
name = selectField.as() || selectField.getName();
1218+
} else {
1219+
var field = new QueryField(selectField);
1220+
name = field.as() || field.getName();
1221+
}
1222+
Args.check(name != null, new Error('Invalid select field. Expected a valid field name.'));
1223+
var qualified = name.split('.');
1224+
return formatter.escapeName(qualified[qualified.length - 1]);
1225+
});
1226+
break;
1227+
}
1228+
}
1229+
if (fields.length === 0) {
1230+
throw new Error('Invalid insert into expression. Fields cannot be empty.');
1231+
}
1232+
sql += ' ';
1233+
sql += '(' + fields.join(', ') + ')';
1234+
sql += ' ' + formatter.format(insertExpr);
1235+
return sql;
1236+
1237+
}
11831238

11841239
/**
11851240
* Formats an update query to the equivalent SQL statement

package-lock.json

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@themost/query",
3-
"version": "2.5.24",
3+
"version": "2.5.26",
44
"description": "MOST Web Framework Codename Blueshift - Query Module",
55
"main": "index.js",
66
"scripts": {

query.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export declare class QueryExpression {
2525
injectWhere(where: any);
2626
delete(entity: string): QueryExpression;
2727
insert(obj: any): QueryExpression;
28+
insert(expr: QueryExpression): QueryExpression;
2829
into(entity: string): QueryExpression;
2930
update(entity: string): QueryExpression;
3031
set(obj: any): QueryExpression;

query.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -429,9 +429,11 @@ QueryExpression.prototype.delete = function(entity)
429429
*/
430430
QueryExpression.prototype.insert = function(obj)
431431
{
432-
if (_.isNil(obj))
433-
return this;
432+
Args.check(obj != null, new Error('Invalid argument. Insert object cannot be empty.'));
434433
if (_.isArray(obj) || _.isObject(obj)) {
434+
if (obj instanceof QueryExpression) {
435+
Args.check(obj.$select != null, new Error('Invalid query expression. An insert into query must be a valid select expression.'));
436+
}
435437
this.$insert = { table1: obj };
436438
//delete other properties (if any)
437439
delete this.$delete;

spec/QueryExpression.insert.spec.js

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import { MemoryAdapter } from './test/TestMemoryAdapter';
2+
import { MemoryFormatter } from './test/TestMemoryFormatter';
3+
import { QueryExpression, SqlFormatter } from '../index';
4+
import { executeInTransactionAsync } from './utils';
5+
import { QueryField } from '../query';
6+
7+
describe('QueryExpression', () => {
8+
9+
/**
10+
* @type {MemoryAdapter}
11+
*/
12+
let db;
13+
beforeAll(() => {
14+
db = new MemoryAdapter({
15+
name: 'local',
16+
database: './spec/db/local.db'
17+
});
18+
});
19+
afterAll((done) => {
20+
if (db) {
21+
db.close();
22+
return done();
23+
}
24+
});
25+
it('should use insert into statement', async () => {
26+
const q = new QueryExpression().insert(
27+
new QueryExpression()
28+
.select('name', 'description', 'dateCreated', 'dateModified')
29+
.from('table1')
30+
.where('status').equal('active')
31+
).into('table2');
32+
expect(q.$insert).toBeTruthy();
33+
const formatter = new SqlFormatter();
34+
const sql = formatter.format(q);
35+
expect(sql).toBe('INSERT INTO table2 (name, description, dateCreated, dateModified) SELECT table1.name, table1.description, table1.dateCreated, table1.dateModified FROM table1 WHERE (status=\'active\')');
36+
});
37+
38+
it('should use insert into with select', async () => {
39+
await executeInTransactionAsync(db, async () => {
40+
// create table
41+
await db.migrateAsync({
42+
appliesTo: 'OrderStatistics',
43+
version: '1.0.0',
44+
add: [
45+
{
46+
"name": "id",
47+
"type": "Counter",
48+
"primary": true
49+
},
50+
{
51+
"name": "orderedItem",
52+
"type": "Integer",
53+
"nullable": false
54+
},
55+
{
56+
"name": "total",
57+
"type": "Integer",
58+
"nullable": false
59+
}
60+
]
61+
});
62+
// use select insert into statement
63+
const q = new QueryExpression().insert(
64+
new QueryExpression()
65+
.select(
66+
new QueryField('orderedItem'),
67+
new QueryField().count('id').as('total')
68+
)
69+
.from('OrderData')
70+
.groupBy(
71+
new QueryField('orderedItem')
72+
)
73+
).into('OrderStatistics');
74+
await db.executeAsync(q);
75+
const items= await db.executeAsync(new QueryExpression().select(
76+
'orderedItem',
77+
'total'
78+
).from('OrderStatistics'));
79+
expect(items).toBeTruthy();
80+
expect(items.length).toBeTruthy();
81+
})
82+
});
83+
84+
it('should use insert into with select #2', async () => {
85+
await executeInTransactionAsync(db, async () => {
86+
// create table
87+
await db.migrateAsync({
88+
appliesTo: 'OrderStatistics',
89+
version: '1.0.0',
90+
add: [
91+
{
92+
"name": "id",
93+
"type": "Counter",
94+
"primary": true
95+
},
96+
{
97+
"name": "orderedItem",
98+
"type": "Integer",
99+
"nullable": false
100+
},
101+
{
102+
"name": "total",
103+
"type": "Integer",
104+
"nullable": false
105+
}
106+
]
107+
});
108+
// use select insert into statement
109+
const q = new QueryExpression().insert(
110+
new QueryExpression()
111+
.select(
112+
{
113+
"orderedItem": "orderedItem",
114+
},
115+
{
116+
"total": {
117+
"$count": [
118+
"id"
119+
]
120+
}
121+
}
122+
)
123+
.from('OrderData')
124+
.groupBy(
125+
new QueryField('orderedItem')
126+
)
127+
).into('OrderStatistics');
128+
await db.executeAsync(q);
129+
const items= await db.executeAsync(new QueryExpression().select(
130+
'orderedItem',
131+
'total'
132+
).from('OrderStatistics'));
133+
expect(items).toBeTruthy();
134+
expect(items.length).toBeTruthy();
135+
})
136+
});
137+
138+
});

spec/TestTemplate.spec.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { MemoryAdapter } from './test/TestMemoryAdapter';
2+
3+
describe('TestTemplate', () => {
4+
5+
/**
6+
* @type {MemoryAdapter}
7+
*/
8+
let db;
9+
beforeAll(() => {
10+
db = new MemoryAdapter({
11+
name: 'local',
12+
database: './spec/db/local.db'
13+
});
14+
});
15+
afterAll((done) => {
16+
if (db) {
17+
db.close();
18+
return done();
19+
}
20+
});
21+
it('should use test', async () => {
22+
// write your test here...
23+
});
24+
25+
});

spec/utils.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
export class CancelTransactionError extends Error {
2+
constructor() {
3+
super();
4+
}
5+
}
6+
/**
7+
*
8+
* @param {import('@themost/common').DataAdapterBase} db
9+
* @param {function():Promise<void>} func
10+
* @returns
11+
*/
12+
export function executeInTransactionAsync(db, func) {
13+
return new Promise((resolve, reject) => {
14+
// start transaction
15+
db.executeInTransaction((cb) => {
16+
try {
17+
func().then(() => {
18+
return cb(new CancelTransactionError());
19+
}).catch( err => {
20+
return cb(err);
21+
});
22+
}
23+
catch (err) {
24+
return cb(err);
25+
}
26+
27+
}, err => {
28+
// if error is an instance of CancelTransactionError
29+
if (err && err instanceof CancelTransactionError) {
30+
return resolve();
31+
}
32+
if (err) {
33+
return reject(err);
34+
}
35+
// exit
36+
return resolve();
37+
});
38+
});
39+
}

0 commit comments

Comments
 (0)