@steedos/odata-v4-sql 2.2.52-beta.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +22 -0
- package/README.md +62 -0
- package/lib/index.d.ts +31 -0
- package/lib/index.js +22 -0
- package/lib/index.js.map +1 -0
- package/lib/visitor.d.ts +72 -0
- package/lib/visitor.js +393 -0
- package/lib/visitor.js.map +1 -0
- package/package.json +46 -0
- package/src/index.ts +45 -0
- package/src/visitor.ts +411 -0
- package/test/mssql.spec.js +142 -0
- package/test/oracle.spec.js +138 -0
- package/test/query.spec.js +93 -0
- package/test/where-parameters.spec.js +141 -0
- package/test/where.spec.js +149 -0
- package/tsconfig.json +11 -0
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@steedos/odata-v4-sql",
|
|
3
|
+
"version": "2.2.52-beta.40",
|
|
4
|
+
"description": "OData to SQL query compiler",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"typings": "lib/index",
|
|
7
|
+
"directories": {
|
|
8
|
+
"test": "test"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"pretest": "npm run build",
|
|
13
|
+
"test": "mocha",
|
|
14
|
+
"pretdd": "npm run build",
|
|
15
|
+
"tdd": "mocha -w",
|
|
16
|
+
"prepare": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/jaystack/odata-v4-sql.git"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"OData",
|
|
24
|
+
"V4",
|
|
25
|
+
"sql"
|
|
26
|
+
],
|
|
27
|
+
"author": "JayStack",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/jaystack/odata-v4-sql/issues"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/jaystack/odata-v4-sql#readme",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"odata-v4-literal": "^0.1.0",
|
|
35
|
+
"odata-v4-parser": "^0.1.29"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"chai": "^3.5.0",
|
|
42
|
+
"mocha": "^3.1.2",
|
|
43
|
+
"typescript": "^2.5.1"
|
|
44
|
+
},
|
|
45
|
+
"gitHead": "2cb19790ec035728e2df8eacf78df5e1c0b4cb6a"
|
|
46
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Visitor, SQLLang } from "./visitor";
|
|
2
|
+
export { SQLLang } from "./visitor";
|
|
3
|
+
import { filter, query } from "odata-v4-parser";
|
|
4
|
+
import { Token } from "odata-v4-parser/lib/lexer";
|
|
5
|
+
|
|
6
|
+
export interface SqlOptions{
|
|
7
|
+
useParameters?:boolean
|
|
8
|
+
type?:SQLLang
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates an SQL query descriptor from an OData query string
|
|
13
|
+
* @param {string} odataQuery - An OData query string
|
|
14
|
+
* @return {string} SQL query descriptor
|
|
15
|
+
* @example
|
|
16
|
+
* const filter = createQuery("$filter=Size eq 4 and Age gt 18");
|
|
17
|
+
* let sqlQuery = `SELECT * FROM table WHERE ${filter.where}`;
|
|
18
|
+
*/
|
|
19
|
+
export function createQuery(odataQuery:string, options?:SqlOptions):Visitor;
|
|
20
|
+
export function createQuery(odataQuery:string, options?:SqlOptions, type?:SQLLang):Visitor;
|
|
21
|
+
export function createQuery(odataQuery:Token, options?:SqlOptions):Visitor;
|
|
22
|
+
export function createQuery(odataQuery:Token, options?:SqlOptions, type?:SQLLang):Visitor;
|
|
23
|
+
export function createQuery(odataQuery:string | Token, options = <SqlOptions>{}, type?:SQLLang):Visitor{
|
|
24
|
+
if (typeof type != "undefined" && type) options.type = type;
|
|
25
|
+
let ast:Token = <Token>(typeof odataQuery == "string" ? query(<string>odataQuery) : odataQuery);
|
|
26
|
+
return new Visitor(options).Visit(ast).asType();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Creates an SQL WHERE clause from an OData filter expression string
|
|
31
|
+
* @param {string} odataFilter - A filter expression in OData $filter format
|
|
32
|
+
* @return {string} SQL WHERE clause
|
|
33
|
+
* @example
|
|
34
|
+
* const filter = createFilter("Size eq 4 and Age gt 18");
|
|
35
|
+
* let sqlQuery = `SELECT * FROM table WHERE ${filter}`;
|
|
36
|
+
*/
|
|
37
|
+
export function createFilter(odataFilter:string, options?:SqlOptions):Visitor;
|
|
38
|
+
export function createFilter(odataFilter:string, options?:SqlOptions, type?:SQLLang):Visitor;
|
|
39
|
+
export function createFilter(odataFilter:Token, options?:SqlOptions):Visitor;
|
|
40
|
+
export function createFilter(odataFilter:Token, options?:SqlOptions, type?:SQLLang):Visitor;
|
|
41
|
+
export function createFilter(odataFilter:string | Token, options = <SqlOptions>{}, type?:SQLLang):Visitor{
|
|
42
|
+
if (typeof type != "undefined" && type) options.type = type;
|
|
43
|
+
let ast:Token = <Token>(typeof odataFilter == "string" ? filter(<string>odataFilter) : odataFilter);
|
|
44
|
+
return new Visitor(options).Visit(ast).asType();
|
|
45
|
+
}
|
package/src/visitor.ts
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import { Token } from "odata-v4-parser/lib/lexer";
|
|
2
|
+
import { Literal } from "odata-v4-literal";
|
|
3
|
+
import { SqlOptions } from "./index";
|
|
4
|
+
|
|
5
|
+
export class SQLLiteral extends Literal{
|
|
6
|
+
static convert(type:string, value:string):any {
|
|
7
|
+
return (new SQLLiteral(type, value)).valueOf();
|
|
8
|
+
}
|
|
9
|
+
'Edm.String'(value:string){ return "'" + decodeURIComponent(value).slice(1, -1).replace(/''/g, "'") + "'"; }
|
|
10
|
+
'Edm.Guid'(value:string){ return "'" + decodeURIComponent(value) + "'"; }
|
|
11
|
+
'Edm.Date'(value:string){ return "'" + value + "'"; }
|
|
12
|
+
'Edm.DateTimeOffset'(value:string):any{ return "'" + value.replace("T", " ").replace("Z", " ").trim() + "'"; }
|
|
13
|
+
'Edm.Boolean'(value:string):any{
|
|
14
|
+
value = value || '';
|
|
15
|
+
switch (value.toLowerCase()){
|
|
16
|
+
case 'true': return 1;
|
|
17
|
+
case 'false': return 0;
|
|
18
|
+
default: return "NULL";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
'null'(value:string){ return "NULL"; }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export enum SQLLang{
|
|
25
|
+
ANSI,
|
|
26
|
+
MsSql,
|
|
27
|
+
MySql,
|
|
28
|
+
PostgreSql,
|
|
29
|
+
Oracle
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class Visitor{
|
|
33
|
+
protected options:SqlOptions
|
|
34
|
+
type:SQLLang;
|
|
35
|
+
select:string = "";
|
|
36
|
+
where:string = "";
|
|
37
|
+
orderby:string = "";
|
|
38
|
+
skip:number
|
|
39
|
+
limit:number
|
|
40
|
+
inlinecount:boolean
|
|
41
|
+
navigationProperty:string
|
|
42
|
+
includes:Visitor[] = [];
|
|
43
|
+
parameters:any = new Map();
|
|
44
|
+
protected parameterSeed:number = 0;
|
|
45
|
+
protected originalWhere:string;
|
|
46
|
+
ast:Token
|
|
47
|
+
|
|
48
|
+
constructor(options = <SqlOptions>{}){
|
|
49
|
+
this.options = options;
|
|
50
|
+
if (this.options.useParameters != false) this.options.useParameters = true;
|
|
51
|
+
this.type = options.type || SQLLang.ANSI;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
from(table:string){
|
|
55
|
+
let sql = `SELECT ${this.select} FROM [${table}] WHERE ${this.where} ORDER BY ${this.orderby}`;
|
|
56
|
+
switch (this.type){
|
|
57
|
+
case SQLLang.Oracle:
|
|
58
|
+
case SQLLang.MsSql:
|
|
59
|
+
if (typeof this.skip == "number") sql += ` OFFSET ${this.skip} ROWS`;
|
|
60
|
+
if (typeof this.limit == "number"){
|
|
61
|
+
if (typeof this.skip != "number") sql += " OFFSET 0 ROWS";
|
|
62
|
+
sql += ` FETCH NEXT ${this.limit} ROWS ONLY`;
|
|
63
|
+
}
|
|
64
|
+
break;
|
|
65
|
+
case SQLLang.MySql:
|
|
66
|
+
case SQLLang.PostgreSql:
|
|
67
|
+
default:
|
|
68
|
+
if (typeof this.limit == "number") sql += ` LIMIT ${this.limit}`;
|
|
69
|
+
if (typeof this.skip == "number") sql += ` OFFSET ${this.skip}`;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
return sql;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
asMsSql(){
|
|
76
|
+
this.type = SQLLang.MsSql;
|
|
77
|
+
let rx = new RegExp("\\?", "g");
|
|
78
|
+
let keys = this.parameters.keys();
|
|
79
|
+
this.originalWhere = this.where;
|
|
80
|
+
this.where = this.where.replace(rx, () => `@${keys.next().value}`);
|
|
81
|
+
this.includes.forEach((item) => item.asMsSql());
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
asOracleSql(){
|
|
86
|
+
this.type = SQLLang.Oracle;
|
|
87
|
+
let rx = new RegExp("\\?", "g");
|
|
88
|
+
let keys = this.parameters.keys();
|
|
89
|
+
this.originalWhere = this.where;
|
|
90
|
+
this.where = this.where.replace(rx, () => `:${keys.next().value}`);
|
|
91
|
+
this.includes.forEach((item) => item.asOracleSql());
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
asAnsiSql(){
|
|
96
|
+
this.type = SQLLang.ANSI;
|
|
97
|
+
this.where = this.originalWhere || this.where;
|
|
98
|
+
this.includes.forEach((item) => item.asAnsiSql());
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
asType(){
|
|
103
|
+
switch (this.type){
|
|
104
|
+
case SQLLang.MsSql: return this.asMsSql();
|
|
105
|
+
case SQLLang.ANSI:
|
|
106
|
+
case SQLLang.MySql:
|
|
107
|
+
case SQLLang.PostgreSql: return this.asAnsiSql();
|
|
108
|
+
case SQLLang.Oracle: return this.asOracleSql();
|
|
109
|
+
default: return this;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
Visit(node:Token, context?:any){
|
|
114
|
+
this.ast = this.ast || node;
|
|
115
|
+
context = context || { target: "where" };
|
|
116
|
+
|
|
117
|
+
if (node){
|
|
118
|
+
var visitor = this[`Visit${node.type}`];
|
|
119
|
+
if (visitor) visitor.call(this, node, context);
|
|
120
|
+
else console.log(`Unhandled node type: ${node.type}`, node);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (node == this.ast){
|
|
124
|
+
if (!this.select) this.select = `*`;
|
|
125
|
+
if (!this.where) this.where = "1 = 1";
|
|
126
|
+
if (!this.orderby) this.orderby = "1";
|
|
127
|
+
}
|
|
128
|
+
return this;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
protected VisitODataUri(node:Token, context:any){
|
|
132
|
+
this.Visit(node.value.resource, context);
|
|
133
|
+
this.Visit(node.value.query, context);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
protected VisitExpand(node: Token, context: any) {
|
|
137
|
+
node.value.items.forEach((item) => {
|
|
138
|
+
let expandPath = item.value.path.raw;
|
|
139
|
+
let visitor = this.includes.filter(v => v.navigationProperty == expandPath)[0];
|
|
140
|
+
if (!visitor){
|
|
141
|
+
visitor = new Visitor(this.options);
|
|
142
|
+
visitor.parameterSeed = this.parameterSeed;
|
|
143
|
+
this.includes.push(visitor);
|
|
144
|
+
}
|
|
145
|
+
visitor.Visit(item);
|
|
146
|
+
this.parameterSeed = visitor.parameterSeed;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
protected VisitExpandItem(node: Token, context: any) {
|
|
151
|
+
this.Visit(node.value.path, context);
|
|
152
|
+
if (node.value.options) node.value.options.forEach((item) => this.Visit(item, context));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
protected VisitExpandPath(node: Token, context: any) {
|
|
156
|
+
this.navigationProperty = node.raw;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
protected VisitQueryOptions(node:Token, context:any){
|
|
160
|
+
node.value.options.forEach((option) => this.Visit(option, context));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
protected VisitInlineCount(node:Token, context:any){
|
|
164
|
+
this.inlinecount = Literal.convert(node.value.value, node.value.raw);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
protected VisitFilter(node:Token, context:any){
|
|
168
|
+
context.target = "where";
|
|
169
|
+
this.Visit(node.value, context);
|
|
170
|
+
if (!this.where) this.where = "1 = 1";
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
protected VisitOrderBy(node:Token, context:any){
|
|
174
|
+
context.target = "orderby";
|
|
175
|
+
node.value.items.forEach((item, i) => {
|
|
176
|
+
this.Visit(item, context);
|
|
177
|
+
if (i < node.value.items.length - 1) this.orderby += ", ";
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
protected VisitOrderByItem(node:Token, context:any){
|
|
182
|
+
this.Visit(node.value.expr, context);
|
|
183
|
+
this.orderby += node.value.direction > 0 ? " ASC" : " DESC";
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
protected VisitSkip(node:Token, context:any){
|
|
187
|
+
this.skip = +node.value.raw;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
protected VisitTop(node:Token, context:any){
|
|
191
|
+
this.limit = +node.value.raw;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
protected VisitSelect(node:Token, context:any){
|
|
195
|
+
context.target = "select";
|
|
196
|
+
node.value.items.forEach((item, i) => {
|
|
197
|
+
this.Visit(item, context);
|
|
198
|
+
if (i < node.value.items.length - 1) this.select += ", ";
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
protected VisitSelectItem(node:Token, context:any){
|
|
203
|
+
let item = node.raw.replace(/\//g, '.');
|
|
204
|
+
this.select += `[${item}]`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
protected VisitAndExpression(node:Token, context:any){
|
|
208
|
+
this.Visit(node.value.left, context);
|
|
209
|
+
this.where += " AND ";
|
|
210
|
+
this.Visit(node.value.right, context);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
protected VisitOrExpression(node:Token, context:any){
|
|
214
|
+
this.Visit(node.value.left, context);
|
|
215
|
+
this.where += " OR ";
|
|
216
|
+
this.Visit(node.value.right, context);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
protected VisitBoolParenExpression(node:Token, context:any){
|
|
220
|
+
this.where += "(";
|
|
221
|
+
this.Visit(node.value, context);
|
|
222
|
+
this.where += ")";
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
protected VisitCommonExpression(node:Token, context:any){
|
|
226
|
+
this.Visit(node.value, context);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
protected VisitFirstMemberExpression(node:Token, context:any){
|
|
230
|
+
this.Visit(node.value, context);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
protected VisitMemberExpression(node:Token, context:any){
|
|
234
|
+
this.Visit(node.value, context);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
protected VisitPropertyPathExpression(node:Token, context:any){
|
|
238
|
+
if (node.value.current && node.value.next){
|
|
239
|
+
this.Visit(node.value.current, context);
|
|
240
|
+
context.identifier += ".";
|
|
241
|
+
this.Visit(node.value.next, context);
|
|
242
|
+
}else this.Visit(node.value, context);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
protected VisitSingleNavigationExpression(node:Token, context:any){
|
|
246
|
+
if (node.value.current && node.value.next){
|
|
247
|
+
this.Visit(node.value.current, context);
|
|
248
|
+
this.Visit(node.value.next, context);
|
|
249
|
+
}else this.Visit(node.value, context);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
protected VisitODataIdentifier(node:Token, context:any){
|
|
253
|
+
this[context.target] += `[${node.value.name}]`;
|
|
254
|
+
context.identifier = node.value.name;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
protected VisitEqualsExpression(node:Token, context:any){
|
|
258
|
+
this.Visit(node.value.left, context);
|
|
259
|
+
this.where += " = ";
|
|
260
|
+
this.Visit(node.value.right, context);
|
|
261
|
+
if (this.options.useParameters && context.literal == null){
|
|
262
|
+
this.where = this.where.replace(/= \?$/, "IS NULL").replace(new RegExp(`\\? = \\[${context.identifier}\\]$`), `[${context.identifier}] IS NULL`);
|
|
263
|
+
}else if (context.literal == "NULL"){
|
|
264
|
+
this.where = this.where.replace(/= NULL$/, "IS NULL").replace(new RegExp(`NULL = \\[${context.identifier}\\]$`), `[${context.identifier}] IS NULL`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
protected VisitNotEqualsExpression(node:Token, context:any){
|
|
269
|
+
this.Visit(node.value.left, context);
|
|
270
|
+
this.where += " <> ";
|
|
271
|
+
this.Visit(node.value.right, context);
|
|
272
|
+
if (this.options.useParameters && context.literal == null){
|
|
273
|
+
this.where = this.where.replace(/<> \?$/, "IS NOT NULL").replace(new RegExp(`\\? <> \\[${context.identifier}\\]$`), `[${context.identifier}] IS NOT NULL`);
|
|
274
|
+
}else if (context.literal == "NULL"){
|
|
275
|
+
this.where = this.where.replace(/<> NULL$/, "IS NOT NULL").replace(new RegExp(`NULL <> \\[${context.identifier}\\]$`), `[${context.identifier}] IS NOT NULL`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
protected VisitLesserThanExpression(node:Token, context:any){
|
|
280
|
+
this.Visit(node.value.left, context);
|
|
281
|
+
this.where += " < ";
|
|
282
|
+
this.Visit(node.value.right, context);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
protected VisitLesserOrEqualsExpression(node:Token, context:any){
|
|
286
|
+
this.Visit(node.value.left, context);
|
|
287
|
+
this.where += " <= ";
|
|
288
|
+
this.Visit(node.value.right, context);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
protected VisitGreaterThanExpression(node:Token, context:any){
|
|
292
|
+
this.Visit(node.value.left, context);
|
|
293
|
+
this.where += " > ";
|
|
294
|
+
this.Visit(node.value.right, context);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
protected VisitGreaterOrEqualsExpression(node:Token, context:any){
|
|
298
|
+
this.Visit(node.value.left, context);
|
|
299
|
+
this.where += " >= ";
|
|
300
|
+
this.Visit(node.value.right, context);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
protected VisitLiteral(node:Token, context:any){
|
|
304
|
+
if (this.options.useParameters){
|
|
305
|
+
let name = `p${this.parameterSeed++}`;
|
|
306
|
+
let value = Literal.convert(node.value, node.raw);
|
|
307
|
+
context.literal = value;
|
|
308
|
+
this.parameters.set(name, value);
|
|
309
|
+
this.where += "?";
|
|
310
|
+
}else this.where += (context.literal = SQLLiteral.convert(node.value, node.raw));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
protected VisitMethodCallExpression(node:Token, context:any){
|
|
314
|
+
var method = node.value.method;
|
|
315
|
+
var params = node.value.parameters || [];
|
|
316
|
+
switch (method){
|
|
317
|
+
case "contains":
|
|
318
|
+
this.Visit(params[0], context);
|
|
319
|
+
if (this.options.useParameters){
|
|
320
|
+
let name = `p${this.parameterSeed++}`;
|
|
321
|
+
let value = Literal.convert(params[1].value, params[1].raw);
|
|
322
|
+
this.parameters.set(name, `%${value}%`);
|
|
323
|
+
this.where += " like ?";
|
|
324
|
+
}else this.where += ` like '%${SQLLiteral.convert(params[1].value, params[1].raw).slice(1, -1)}%'`;
|
|
325
|
+
break;
|
|
326
|
+
case "endswith":
|
|
327
|
+
this.Visit(params[0], context);
|
|
328
|
+
if (this.options.useParameters){
|
|
329
|
+
let name = `p${this.parameterSeed++}`;
|
|
330
|
+
let value = Literal.convert(params[1].value, params[1].raw);
|
|
331
|
+
this.parameters.set(name, `%${value}`);
|
|
332
|
+
this.where += " like ?";
|
|
333
|
+
}else this.where += ` like '%${SQLLiteral.convert(params[1].value, params[1].raw).slice(1, -1)}'`;
|
|
334
|
+
break;
|
|
335
|
+
case "startswith":
|
|
336
|
+
this.Visit(params[0], context);
|
|
337
|
+
if (this.options.useParameters){
|
|
338
|
+
let name = `p${this.parameterSeed++}`;
|
|
339
|
+
let value = Literal.convert(params[1].value, params[1].raw);
|
|
340
|
+
this.parameters.set(name, `${value}%`);
|
|
341
|
+
this.where += " like ?";
|
|
342
|
+
}else this.where += ` like '${SQLLiteral.convert(params[1].value, params[1].raw).slice(1, -1)}%'`;
|
|
343
|
+
break;
|
|
344
|
+
case "indexof":
|
|
345
|
+
let fn = "";
|
|
346
|
+
switch (this.type) {
|
|
347
|
+
case SQLLang.MsSql:
|
|
348
|
+
fn = "CHARINDEX";
|
|
349
|
+
break;
|
|
350
|
+
case SQLLang.ANSI:
|
|
351
|
+
case SQLLang.MySql:
|
|
352
|
+
case SQLLang.PostgreSql:
|
|
353
|
+
default:
|
|
354
|
+
fn = "INSTR";
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
if (fn === "CHARINDEX"){
|
|
358
|
+
const tmp = params[0];
|
|
359
|
+
params[0] = params[1];
|
|
360
|
+
params[1] = tmp;
|
|
361
|
+
}
|
|
362
|
+
this.where += `${fn}(`;
|
|
363
|
+
this.Visit(params[0], context);
|
|
364
|
+
this.where += ', ';
|
|
365
|
+
this.Visit(params[1], context);
|
|
366
|
+
this.where += ") - 1";
|
|
367
|
+
break;
|
|
368
|
+
case "round":
|
|
369
|
+
this.where += "ROUND(";
|
|
370
|
+
this.Visit(params[0], context);
|
|
371
|
+
this.where += ")";
|
|
372
|
+
break;
|
|
373
|
+
case "length":
|
|
374
|
+
this.where += "LEN(";
|
|
375
|
+
this.Visit(params[0], context);
|
|
376
|
+
this.where += ")";
|
|
377
|
+
break;
|
|
378
|
+
case "tolower":
|
|
379
|
+
this.where += "LCASE(";
|
|
380
|
+
this.Visit(params[0], context);
|
|
381
|
+
this.where += ")";
|
|
382
|
+
break;
|
|
383
|
+
case "toupper":
|
|
384
|
+
this.where += "UCASE(";
|
|
385
|
+
this.Visit(params[0], context);
|
|
386
|
+
this.where += ")";
|
|
387
|
+
break;
|
|
388
|
+
case "floor":
|
|
389
|
+
case "ceiling":
|
|
390
|
+
case "year":
|
|
391
|
+
case "month":
|
|
392
|
+
case "day":
|
|
393
|
+
case "hour":
|
|
394
|
+
case "minute":
|
|
395
|
+
case "second":
|
|
396
|
+
this.where += `${method.toUpperCase()}(`;
|
|
397
|
+
this.Visit(params[0], context);
|
|
398
|
+
this.where += ")";
|
|
399
|
+
break;
|
|
400
|
+
case "now":
|
|
401
|
+
this.where += "NOW()";
|
|
402
|
+
break;
|
|
403
|
+
case "trim":
|
|
404
|
+
this.where += "TRIM(' ' FROM ";
|
|
405
|
+
this.Visit(params[0], context);
|
|
406
|
+
this.where += ")";
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
var createFilter = require('../lib').createFilter
|
|
2
|
+
var expect = require('chai').expect
|
|
3
|
+
|
|
4
|
+
describe("SQL WHERE useParameters (MS-SQL)", () => {
|
|
5
|
+
var f;
|
|
6
|
+
beforeEach(function() {
|
|
7
|
+
var match;
|
|
8
|
+
if (match = this.currentTest.title.match(/expression[^\:]*\: ?(.*)/)) {
|
|
9
|
+
f = createFilter(match[1], {
|
|
10
|
+
useParameters: true,
|
|
11
|
+
type: 1
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
//all numbers are referencing this:
|
|
17
|
+
//http://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part2-url-conventions/odata-v4.0-errata02-os-part2-url-conventions-complete.html#_Toc406398116
|
|
18
|
+
|
|
19
|
+
it("expression 5.1.1.6.1: NullValue eq null", () => {
|
|
20
|
+
expect(f.where).to.equal("[NullValue] IS NULL")
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it("expression 5.1.1.6.1: TrueValue eq true", () => {
|
|
24
|
+
expect(f.where).to.equal("[TrueValue] = @p0")
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it("expression 5.1.1.6.1: FalseValue eq false", () => {
|
|
28
|
+
expect(f.where).to.equal("[FalseValue] = @p0")
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it("expression 5.1.1.6.1: IntegerValue lt -128", () => {
|
|
32
|
+
expect(f.where).to.equal("[IntegerValue] < @p0")
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it("expression 5.1.1.6.1: DecimalValue eq 34.95", () => {
|
|
36
|
+
expect(f.where).to.equal("[DecimalValue] = @p0")
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it("expression 5.1.1.6.1: StringValue eq 'Say Hello,then go'", () => {
|
|
40
|
+
expect(f.where).to.equal("[StringValue] = @p0")
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it("expression 5.1.1.6.1: DurationValue eq duration'P12DT23H59M59.999999999999S'", () => {
|
|
44
|
+
expect(f.where).to.equal("[DurationValue] = @p0")
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it("expression 5.1.1.6.1: DateValue eq 2012-12-03", () => {
|
|
48
|
+
expect(f.where).to.equal("[DateValue] = @p0")
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it("expression 5.1.1.6.1: DateTimeOffsetValue eq 2012-12-03T07:16:23Z", () => {
|
|
52
|
+
expect(f.where).to.equal("[DateTimeOffsetValue] = @p0")
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it("expression 5.1.1.6.1: GuidValue eq 01234567-89ab-cdef-0123-456789abcdef", () => {
|
|
56
|
+
expect(f.where).to.equal("[GuidValue] = @p0")
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it("expression 5.1.1.6.1: Int64Value eq 0", () => {
|
|
60
|
+
expect(f.where).to.equal("[Int64Value] = @p0")
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it("expression 5.1.1.6.1: A eq 0.31415926535897931e1", () => {
|
|
64
|
+
expect(f.where).to.equal("[A] = @p0")
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it("expression 5.1.1.1.2: A ne 1", () => {
|
|
68
|
+
expect(f.where).to.equal("[A] <> @p0")
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it("expression 5.1.1.1.3: A gt 2", () => {
|
|
72
|
+
expect(f.where).to.equal("[A] > @p0")
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it("expression 5.1.1.1.4: A ge 3", () => {
|
|
76
|
+
expect(f.where).to.equal("[A] >= @p0")
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it("expression 5.1.1.1.5: A lt 2", () => {
|
|
80
|
+
expect(f.where).to.equal("[A] < @p0")
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it("expression 5.1.1.1.6: A le 2", () => {
|
|
84
|
+
expect(f.where).to.equal("[A] <= @p0")
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it("expression 5.1.1.3: (A eq 2) or (B lt 4) and ((E gt 5) or (E lt -1))", () => {
|
|
88
|
+
expect(f.where).to.equal("([A] = @p0) OR ([B] < @p1) AND (([E] > @p2) OR ([E] < @p3))")
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it("expression 5.1.1.4.1: contains(A, 'BC')", () => {
|
|
92
|
+
expect(f.where).to.equal("[A] like @p0");
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it("expression 5.1.1.4.2: endswith(A, 'CD')", () => {
|
|
96
|
+
expect(f.where).to.equal("[A] like @p0");
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it("expression 5.1.1.4.3: startswith(A, 'CD')", () => {
|
|
100
|
+
expect(f.where).to.equal("[A] like @p0");
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it("expression 5.1.1.4.4: length(A) eq 3", () => {
|
|
104
|
+
expect(f.where).to.equal("LEN([A]) = @p0")
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it("expression 5.1.1.4.5: indexof(A, 'BC') eq 1", () => {
|
|
108
|
+
expect(f.where).to.equal("CHARINDEX(@p0, [A]) - 1 = @p1")
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it("expression 5.1.1.4.7: tolower(A) eq 'abc'", () => {
|
|
112
|
+
expect(f.where).to.equal("LCASE([A]) = @p0")
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it("expression 5.1.1.4.8: toupper(A) eq 'ABC'", () => {
|
|
116
|
+
expect(f.where).to.equal("UCASE([A]) = @p0")
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it("expression 5.1.1.4.9: trim(A) eq 'abc'", () => {
|
|
120
|
+
expect(f.where).to.equal("TRIM(' ' FROM [A]) = @p0")
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it("expression 5.1.1.4.11: A eq year(2016-01-01T13:00Z)", () => {
|
|
124
|
+
expect(f.where).to.equal("[A] = YEAR(@p0)")
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it("expression 5.1.1.4.21: year(now())", () => {
|
|
128
|
+
expect(f.where).to.equal("YEAR(NOW())")
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it("expression 5.1.1.4.25: round(A) eq 42", () => {
|
|
132
|
+
expect(f.where).to.equal("ROUND([A]) = @p0")
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it("expression 5.1.1.4.26: floor(A) eq 42", () => {
|
|
136
|
+
expect(f.where).to.equal("FLOOR([A]) = @p0")
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it("expression 5.1.1.4.27: ceiling(A) eq 42", () => {
|
|
140
|
+
expect(f.where).to.equal("CEILING([A]) = @p0")
|
|
141
|
+
})
|
|
142
|
+
})
|