Mercurial > hg > ng2-query-ismi
view src/app/query.service.ts @ 48:f8d6f8479e77 ng2-final
first working version with angular 2.4 (using old table w/o pager).
author | casties |
---|---|
date | Mon, 20 Mar 2017 18:12:48 +0100 |
parents | b65a031c4967 |
children | 308c96f734c8 |
line wrap: on
line source
import {Injectable} from '@angular/core'; import {Http, Headers} from '@angular/http'; import 'rxjs/Rx'; // import all RxJS operators //import 'rxjs/add/operator/map'; import {NEO4J_BASE_URL, NEO4J_AUTHENTICATION} from './app-config'; import {QueryMode, QUERY_MODES, FIRST_QUERY_MODES} from './query-mode'; import {QueryState} from './query-state'; import {QueryStep} from './query-step'; import {getResultType} from './result-type'; import {ISMI_RESULT_TYPES} from './ismi-result-types'; import {getRelationType} from './ismi-relation-types'; @Injectable() export class QueryService { public typeAttribute = '_type'; public excludedAttributes = {}; public state: QueryState; public objectTypes: string[]; constructor(private _http: Http) { // init query state this.state = new QueryState(); } setup() { this.setupObjectTypes(); } getState() { return this.state; } getQueryModes(index: number): QueryMode[] { if (index == 0) { return FIRST_QUERY_MODES; } else { return QUERY_MODES; } } /** * return the first set of options for the given query mode. */ getQueryOptions(queryMode: QueryMode) { let options: any[] = []; if (queryMode == null) return options; if (queryMode.id === 'type_is') { options = this.objectTypes; } else if (queryMode.id === 'relation_is') { options = this.state.resultRelations; } else if (queryMode.id === 'att_contains') { options = this.filterAttributes(this.state.resultAttributes); } else if (queryMode.id === 'att_contains_norm') { options = this.filterAttributes(this.state.resultAttributes, true); } else if (queryMode.id === 'att_num_range') { options = this.filterAttributes(this.state.resultAttributes); } console.debug("getQueryOptions returns: ", options); return options; } /** * fetch all object types from Neo4j and store in this.objectTypes. */ setupObjectTypes() { let query = `MATCH (n) WITH DISTINCT labels(n) AS labels UNWIND labels AS label RETURN DISTINCT label ORDER BY label`; let res = this.fetchCypherResults([query]); res.subscribe( data => { console.debug("neo4j data=", data); this.objectTypes = data.results[0].data.map(elem => elem.row[0]).filter(elem => elem[0] != "_"); console.debug("object types=", this.objectTypes); }, err => console.error("neo4j error=", err), () => console.debug('neo4j query Complete') ); } /** * Set the query step at index. */ setQueryStep(index: number, step: QueryStep) { this.state.steps[index] = step; } /** * Create the cypher queries for the current query state. * * Updates the queries for results, attributes and relations. */ createCypherQuery() { let queryMatch = ''; let queryWhere = ''; let queryReturn = ''; let queryParams = {}; let resultQuery = ''; let attributesQuery = ''; let outRelsQuery = ''; let inRelsQuery = ''; let returnType = ''; let nIdx = 1; this.state.steps.forEach((step, stepIdx) => { let mode = step.mode.id; let params = step.params; /* * step: object type is */ if (mode === 'type_is') { queryMatch = `MATCH (n${nIdx}:${params.objectType})`; queryWhere = ''; queryReturn = `RETURN n${nIdx}`; returnType = 'node'; } /* * step: object id is */ if (mode === 'id_is') { if (!queryMatch) { // first step - use match clause queryMatch = `MATCH (n${nIdx} {ismi_id: {att_val${stepIdx}}})`; queryParams[`att_val${stepIdx}`] = parseInt(params.value, 10); queryWhere = ''; queryReturn = `RETURN n${nIdx}`; returnType = 'node'; } else { // use where clause if (!queryWhere) { queryWhere = 'WHERE '; } else { queryWhere += ' AND '; } queryWhere += `n${nIdx}.ismi_id = {att_val${stepIdx}}`; queryParams[`att_val${stepIdx}`] = parseInt(params.value, 10); } } /* * step: relation type is */ if (mode === 'relation_is') { nIdx += 1; let rel = params.relationType; if (rel.isOutgoing()) { queryMatch += `-[:\`${rel.getRelType()}\`]->(n${nIdx})`; } else { // inverse relation queryMatch += `<-[:\`${rel.getRelType()}\`]-(n${nIdx})`; } queryReturn = `RETURN DISTINCT n${nIdx}`; returnType = 'node'; } /* * step: attribute contains(_norm) */ if (mode === 'att_contains' || mode === 'att_contains_norm') { if (!queryWhere) { queryWhere = 'WHERE '; } else { queryWhere += ' AND '; } if (params.attribute === 'ismi_id') { // ismi_id is integer queryWhere += `n${nIdx}.ismi_id = {att_val${stepIdx}}`; queryParams[`att_val${stepIdx}`] = parseInt(params.value, 10); } else { if (mode === 'att_contains_norm') { // match _n_attribute with normValue queryWhere += `lower(n${nIdx}._n_${params.attribute}) CONTAINS lower({att_val${stepIdx}})`; queryParams[`att_val${stepIdx}`] = params.normValue; } else { queryWhere += `lower(n${nIdx}.${params.attribute}) CONTAINS lower({att_val${stepIdx}})`; queryParams[`att_val${stepIdx}`] = params.value; } } } /* * step: attribute number range */ if (mode === 'att_num_range') { if (!queryWhere) { queryWhere = 'WHERE '; } else { queryWhere += ' AND '; } queryWhere += `toint(n${nIdx}.${params.attribute}) >= toint({att_nlo${stepIdx}})` + ` AND toint(n${nIdx}.${params.attribute}) <= toint({att_nhi${stepIdx}})`; queryParams[`att_nlo${stepIdx}`] = params.numLo; queryParams[`att_nhi${stepIdx}`] = params.numHi; } }); // compose query resultQuery = queryMatch + (queryWhere ? '\n'+queryWhere : '') + '\n' + queryReturn; // compose query for attributes of result attributesQuery = queryMatch + ' ' + queryWhere + ` WITH DISTINCT keys(n${nIdx}) AS atts` + ` UNWIND atts AS att RETURN DISTINCT att ORDER BY att`; // compose query for relations of result outRelsQuery = queryMatch + '-[r]->() ' + queryWhere + ' RETURN DISTINCT type(r)'; inRelsQuery = queryMatch + '<-[r]-() ' + queryWhere + ' RETURN DISTINCT type(r)'; this.state.resultCypherQuery = resultQuery; this.state.cypherQueryParams = queryParams; this.state.attributesCypherQuery = attributesQuery; this.state.outRelsCypherQuery = outRelsQuery; this.state.inRelsCypherQuery = inRelsQuery; this.state.resultTypes = returnType; } /** * Create and run the cypher queries for the current query state. * * Updates the results and nextQuery attributes and relations. */ runQuery() { this.createCypherQuery(); this.state.resultInfo = 'loading...'; /* * run query for result table */ let queries = [this.state.resultCypherQuery]; let params = [this.state.cypherQueryParams]; if (this.state.attributesCypherQuery) { queries.push(this.state.attributesCypherQuery); params.push(this.state.cypherQueryParams); } if (this.state.outRelsCypherQuery) { queries.push(this.state.outRelsCypherQuery); params.push(this.state.cypherQueryParams); } if (this.state.inRelsCypherQuery) { queries.push(this.state.inRelsCypherQuery); params.push(this.state.cypherQueryParams); } let res = this.fetchCypherResults(queries, params); res.subscribe( data => { console.debug("neo4j result data=", data); let resIdx = 0; /* * results for result table */ this.state.results = data.results[resIdx].data.map(elem => elem.row[0]); this.state.numResults = this.state.results.length; // count all types let resTypes = {}; this.state.results.forEach((r) => { let t = r[this.typeAttribute]; if (resTypes[t] == null) { resTypes[t] = 1; } else { resTypes[t] += 1; } }); let info = ''; for (let t in resTypes) { info += t + '(' + resTypes[t] + ') '; } info = info.substr(0, info.length-1); this.state.resultInfo = info; // save info also in last step this.state.steps[this.state.steps.length-1].resultInfo = info; /* * results for attribute list */ if (this.state.attributesCypherQuery) { resIdx += 1; let atts = data.results[resIdx].data.map(elem => elem.row[0]); this.state.resultAttributes = atts; // the following assumes only one type in the result for (let t in resTypes) { this.state.resultType = getResultType(t, ISMI_RESULT_TYPES); break; } this.state.resultColumns = this.state.resultType.getColumns(atts); } /* * results for relations list */ if (this.state.outRelsCypherQuery) { // outgoing aka forward relations resIdx += 1; let rels = data.results[resIdx].data.map(elem => elem.row[0]) .filter(elem => elem[0] != "_") .map(elem => getRelationType(elem, true)); this.state.resultRelations = rels; } if (this.state.inRelsCypherQuery) { // incoming aka reverse relations resIdx += 1; let rels = data.results[resIdx].data.map(elem => elem.row[0]) .filter(elem => elem[0] != "_") .map(elem => getRelationType(elem, false)); this.state.resultRelations = this.state.resultRelations.concat(rels); } }, err => console.error("neo4j result error=", err), () => console.debug('neo4j result query Complete') ); } filterAttributes(attributes: string[], normalized=false) { let atts = []; if (normalized) { attributes.forEach((att) => { if (att.substr(0, 3) == "_n_") { atts.push(att.substr(3)); } }); } else { atts = attributes.filter(elem => elem[0] != "_" && !this.excludedAttributes[elem]); } return atts; } /** * Run the given queries on the Neo4J server. * * Returns an Observable with the results. */ fetchCypherResults(queries: string[], params=[{}]) { console.debug("fetching cypher queries: ", queries); let headers = new Headers(); let auth = NEO4J_AUTHENTICATION; headers.append('Authorization', 'Basic ' + btoa(`${auth.user}:${auth.password}`)); headers.append('Content-Type', 'application/json'); headers.append('Accept', 'application/json'); // put headers in options let opts = {'headers': headers}; // unpack queries into statements let statements = queries.map((q, i) => { return {'statement': q, 'parameters': (params[i])?params[i]:{}}; }); // create POST data from query let data = JSON.stringify({'statements': statements}); // make post request asynchronously let resp = this._http.post(NEO4J_BASE_URL+'/transaction/commit', data, opts) // filter result as JSON .map(res => res.json()); // return Observable return resp; } }