comparison src/app/query.service.ts @ 47:b65a031c4967 ng2-final

first step to angular2-final (2.4) version of the query browser.
author casties
date Fri, 17 Mar 2017 20:16:52 +0100
parents app/query.service.ts@dc4f0541f04d
children 308c96f734c8
comparison
equal deleted inserted replaced
46:1f3fed01aef6 47:b65a031c4967
1 import {Injectable} from '@angular/core';
2 import {Http, Headers} from '@angular/http';
3
4 import 'rxjs/Rx'; // import all RxJS operators
5 //import 'rxjs/add/operator/map';
6
7 import {NEO4J_BASE_URL, NEO4J_AUTHENTICATION} from './app-config';
8 import {QueryMode, QUERY_MODES, FIRST_QUERY_MODES} from './query-mode';
9 import {QueryState} from './query-state';
10 import {QueryStep} from './query-step';
11 import {getResultType} from './result-type';
12 import {ISMI_RESULT_TYPES} from './ismi-result-types';
13 import {getRelationType} from './ismi-relation-types';
14
15 @Injectable()
16 export class QueryService {
17
18 public typeAttribute = '_type';
19 public excludedAttributes = {};
20 public state: QueryState;
21 public objectTypes: string[];
22
23 constructor(private _http: Http) {
24 // init query state
25 this.state = new QueryState();
26 }
27
28 setup() {
29 this.setupObjectTypes();
30 }
31
32 getState() {
33 return this.state;
34 }
35
36 getQueryModes(index: number): QueryMode[] {
37 if (index == 0) {
38 return FIRST_QUERY_MODES;
39 } else {
40 return QUERY_MODES;
41 }
42 }
43
44 /**
45 * return the first set of options for the given query mode.
46 */
47 getQueryOptions(queryMode: QueryMode) {
48 let options: any[] = [];
49 if (queryMode == null) return options;
50 if (queryMode.id === 'type_is') {
51 options = this.objectTypes;
52 } else if (queryMode.id === 'relation_is') {
53 options = this.state.resultRelations;
54 } else if (queryMode.id === 'att_contains') {
55 options = this.filterAttributes(this.state.resultAttributes);
56 } else if (queryMode.id === 'att_contains_norm') {
57 options = this.filterAttributes(this.state.resultAttributes, true);
58 } else if (queryMode.id === 'att_num_range') {
59 options = this.filterAttributes(this.state.resultAttributes);
60 }
61 console.debug("getQueryOptions returns: ", options);
62 return options;
63 }
64
65 /**
66 * fetch all object types from Neo4j and store in this.objectTypes.
67 */
68 setupObjectTypes() {
69 let query = `MATCH (n) WITH DISTINCT labels(n) AS labels
70 UNWIND labels AS label
71 RETURN DISTINCT label ORDER BY label`;
72
73 let res = this.fetchCypherResults([query]);
74 res.subscribe(
75 data => {
76 console.debug("neo4j data=", data);
77 this.objectTypes = data.results[0].data.map(elem => elem.row[0]).filter(elem => elem[0] != "_");
78 console.debug("object types=", this.objectTypes);
79 },
80 err => console.error("neo4j error=", err),
81 () => console.debug('neo4j query Complete')
82 );
83 }
84
85 /**
86 * Set the query step at index.
87 */
88 setQueryStep(index: number, step: QueryStep) {
89 this.state.steps[index] = step;
90 }
91
92 /**
93 * Create the cypher queries for the current query state.
94 *
95 * Updates the queries for results, attributes and relations.
96 */
97 createCypherQuery() {
98 let queryMatch = '';
99 let queryWhere = '';
100 let queryReturn = '';
101 let queryParams = {};
102 let resultQuery = '';
103 let attributesQuery = '';
104 let outRelsQuery = '';
105 let inRelsQuery = '';
106 let returnType = '';
107 let nIdx = 1;
108 this.state.steps.forEach((step, stepIdx) => {
109 let mode = step.mode.id;
110 let params = step.params;
111
112 /*
113 * step: object type is
114 */
115 if (mode === 'type_is') {
116 queryMatch = `MATCH (n${nIdx}:${params.objectType})`;
117 queryWhere = '';
118 queryReturn = `RETURN n${nIdx}`;
119 returnType = 'node';
120 }
121
122 /*
123 * step: object id is
124 */
125 if (mode === 'id_is') {
126 if (!queryMatch) {
127 // first step - use match clause
128 queryMatch = `MATCH (n${nIdx} {ismi_id: {att_val${stepIdx}}})`;
129 queryParams[`att_val${stepIdx}`] = parseInt(params.value, 10);
130 queryWhere = '';
131 queryReturn = `RETURN n${nIdx}`;
132 returnType = 'node';
133 } else {
134 // use where clause
135 if (!queryWhere) {
136 queryWhere = 'WHERE ';
137 } else {
138 queryWhere += ' AND ';
139 }
140 queryWhere += `n${nIdx}.ismi_id = {att_val${stepIdx}}`;
141 queryParams[`att_val${stepIdx}`] = parseInt(params.value, 10);
142 }
143 }
144
145 /*
146 * step: relation type is
147 */
148 if (mode === 'relation_is') {
149 nIdx += 1;
150 let rel = params.relationType;
151 if (rel.isOutgoing()) {
152 queryMatch += `-[:\`${rel.getRelType()}\`]->(n${nIdx})`;
153 } else {
154 // inverse relation
155 queryMatch += `<-[:\`${rel.getRelType()}\`]-(n${nIdx})`;
156 }
157 queryReturn = `RETURN DISTINCT n${nIdx}`;
158 returnType = 'node';
159 }
160
161 /*
162 * step: attribute contains(_norm)
163 */
164 if (mode === 'att_contains' || mode === 'att_contains_norm') {
165 if (!queryWhere) {
166 queryWhere = 'WHERE ';
167 } else {
168 queryWhere += ' AND ';
169 }
170 if (params.attribute === 'ismi_id') {
171 // ismi_id is integer
172 queryWhere += `n${nIdx}.ismi_id = {att_val${stepIdx}}`;
173 queryParams[`att_val${stepIdx}`] = parseInt(params.value, 10);
174 } else {
175 if (mode === 'att_contains_norm') {
176 // match _n_attribute with normValue
177 queryWhere += `lower(n${nIdx}._n_${params.attribute}) CONTAINS lower({att_val${stepIdx}})`;
178 queryParams[`att_val${stepIdx}`] = params.normValue;
179 } else {
180 queryWhere += `lower(n${nIdx}.${params.attribute}) CONTAINS lower({att_val${stepIdx}})`;
181 queryParams[`att_val${stepIdx}`] = params.value;
182 }
183 }
184 }
185
186 /*
187 * step: attribute number range
188 */
189 if (mode === 'att_num_range') {
190 if (!queryWhere) {
191 queryWhere = 'WHERE ';
192 } else {
193 queryWhere += ' AND ';
194 }
195 queryWhere += `toint(n${nIdx}.${params.attribute}) >= toint({att_nlo${stepIdx}})`
196 + ` AND toint(n${nIdx}.${params.attribute}) <= toint({att_nhi${stepIdx}})`;
197 queryParams[`att_nlo${stepIdx}`] = params.numLo;
198 queryParams[`att_nhi${stepIdx}`] = params.numHi;
199 }
200
201 });
202 // compose query
203 resultQuery = queryMatch + (queryWhere ? '\n'+queryWhere : '') + '\n' + queryReturn;
204 // compose query for attributes of result
205 attributesQuery = queryMatch + ' ' + queryWhere + ` WITH DISTINCT keys(n${nIdx}) AS atts`
206 + ` UNWIND atts AS att RETURN DISTINCT att ORDER BY att`;
207 // compose query for relations of result
208 outRelsQuery = queryMatch + '-[r]->() ' + queryWhere + ' RETURN DISTINCT type(r)';
209 inRelsQuery = queryMatch + '<-[r]-() ' + queryWhere + ' RETURN DISTINCT type(r)';
210 this.state.resultCypherQuery = resultQuery;
211 this.state.cypherQueryParams = queryParams;
212 this.state.attributesCypherQuery = attributesQuery;
213 this.state.outRelsCypherQuery = outRelsQuery;
214 this.state.inRelsCypherQuery = inRelsQuery;
215 this.state.resultTypes = returnType;
216 }
217
218 /**
219 * Create and run the cypher queries for the current query state.
220 *
221 * Updates the results and nextQuery attributes and relations.
222 */
223 runQuery() {
224 this.createCypherQuery();
225 this.state.resultInfo = 'loading...';
226 /*
227 * run query for result table
228 */
229 let queries = [this.state.resultCypherQuery];
230 let params = [this.state.cypherQueryParams];
231 if (this.state.attributesCypherQuery) {
232 queries.push(this.state.attributesCypherQuery);
233 params.push(this.state.cypherQueryParams);
234 }
235 if (this.state.outRelsCypherQuery) {
236 queries.push(this.state.outRelsCypherQuery);
237 params.push(this.state.cypherQueryParams);
238 }
239 if (this.state.inRelsCypherQuery) {
240 queries.push(this.state.inRelsCypherQuery);
241 params.push(this.state.cypherQueryParams);
242 }
243 let res = this.fetchCypherResults(queries, params);
244 res.subscribe(
245 data => {
246 console.debug("neo4j result data=", data);
247 let resIdx = 0;
248 /*
249 * results for result table
250 */
251 this.state.results = data.results[resIdx].data.map(elem => elem.row[0]);
252 this.state.numResults = this.state.results.length;
253 // count all types
254 let resTypes = {};
255 this.state.results.forEach((r) => {
256 let t = r[this.typeAttribute];
257 if (resTypes[t] == null) {
258 resTypes[t] = 1;
259 } else {
260 resTypes[t] += 1;
261 }
262 });
263 let info = '';
264 for (let t in resTypes) {
265 info += t + '(' + resTypes[t] + ') ';
266 }
267 info = info.substr(0, info.length-1);
268 this.state.resultInfo = info;
269 // save info also in last step
270 this.state.steps[this.state.steps.length-1].resultInfo = info;
271 /*
272 * results for attribute list
273 */
274 if (this.state.attributesCypherQuery) {
275 resIdx += 1;
276 let atts = data.results[resIdx].data.map(elem => elem.row[0]);
277 this.state.resultAttributes = atts;
278 // the following assumes only one type in the result
279 for (let t in resTypes) {
280 this.state.resultType = getResultType(t, ISMI_RESULT_TYPES);
281 break;
282 }
283 this.state.resultColumns = this.state.resultType.getColumns(atts);
284 }
285 /*
286 * results for relations list
287 */
288 if (this.state.outRelsCypherQuery) {
289 // outgoing aka forward relations
290 resIdx += 1;
291 let rels = data.results[resIdx].data.map(elem => elem.row[0])
292 .filter(elem => elem[0] != "_")
293 .map(elem => getRelationType(elem, true));
294 this.state.resultRelations = rels;
295 }
296 if (this.state.inRelsCypherQuery) {
297 // incoming aka reverse relations
298 resIdx += 1;
299 let rels = data.results[resIdx].data.map(elem => elem.row[0])
300 .filter(elem => elem[0] != "_")
301 .map(elem => getRelationType(elem, false));
302 this.state.resultRelations = this.state.resultRelations.concat(rels);
303 }
304 },
305 err => console.error("neo4j result error=", err),
306 () => console.debug('neo4j result query Complete')
307 );
308 }
309
310
311 filterAttributes(attributes: string[], normalized=false) {
312 let atts = [];
313 if (normalized) {
314 attributes.forEach((att) => {
315 if (att.substr(0, 3) == "_n_") {
316 atts.push(att.substr(3));
317 }
318 });
319 } else {
320 atts = attributes.filter(elem => elem[0] != "_" && !this.excludedAttributes[elem]);
321 }
322 return atts;
323 }
324
325 /**
326 * Run the given queries on the Neo4J server.
327 *
328 * Returns an Observable with the results.
329 */
330 fetchCypherResults(queries: string[], params=[{}]) {
331 console.debug("fetching cypher queries: ", queries);
332 let headers = new Headers();
333 let auth = NEO4J_AUTHENTICATION;
334 headers.append('Authorization', 'Basic ' + btoa(`${auth.user}:${auth.password}`));
335 headers.append('Content-Type', 'application/json');
336 headers.append('Accept', 'application/json');
337 // put headers in options
338 let opts = {'headers': headers};
339 // unpack queries into statements
340 let statements = queries.map((q, i) => {
341 return {'statement': q, 'parameters': (params[i])?params[i]:{}};
342 });
343 // create POST data from query
344 let data = JSON.stringify({'statements': statements});
345 // make post request asynchronously
346 let resp = this._http.post(NEO4J_BASE_URL+'/transaction/commit', data, opts)
347 // filter result as JSON
348 .map(res => res.json());
349 // return Observable
350 return resp;
351 }
352
353 }