Mercurial > hg > ng2-query-ismi
view app/query.service.ts @ 31:4926885f8a99
selectable result columns. nicer cypher query output.
author | casties |
---|---|
date | Mon, 01 Feb 2016 20:10:55 +0100 |
parents | 193271b6b9d2 |
children | 4c046f3244ec |
line wrap: on
line source
import {Injectable} from 'angular2/core'; import {Http, Headers} from 'angular2/http'; import 'rxjs/Rx'; // import all RxJS operators //import 'rxjs/add/operator/map'; import {QueryMode, 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'; @Injectable() export class QueryService { //public neo4jBaseUrl = 'https://ismi-dev.mpiwg-berlin.mpg.de/neo4j-ismi/db/data'; public neo4jBaseUrl = 'http://localhost:7474/db/data'; public neo4jAuthentication = {'user': 'neo4j', 'password': 'neo5j'}; public excludedAttributes = {'type': true}; public invRelPrefix = '<- '; 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(): QueryMode[] { return QUERY_MODES; } /** * return the first set of options for the given query mode. */ getQueryOptions(queryMode: QueryMode) { var options = []; 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() { var query = `MATCH (n) WITH DISTINCT labels(n) AS labels UNWIND labels AS label RETURN DISTINCT label ORDER BY label`; var 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() { var queryMatch = ''; var queryWhere = ''; var queryReturn = ''; var queryParams = {}; var resultQuery = ''; var attributesQuery = ''; var relationsQuery = ''; var returnType = ''; var nIdx = 1; this.state.steps.forEach((step, stepIdx) => { var mode = step.mode.id; var 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: relation type is */ if (mode === 'relation_is') { nIdx += 1; var rel = params.relationType; if (rel.indexOf(this.invRelPrefix) == 0) { // inverse relation rel = rel.substr(this.invRelPrefix.length); queryMatch += `<-[:${rel}]-(n${nIdx})`; } else { queryMatch += `-[:${rel}]->(n${nIdx})`; } queryReturn = `RETURN 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 + '\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 relationsQuery = queryMatch + '-[r]-() ' + queryWhere + ' RETURN DISTINCT type(r)'; this.state.resultCypherQuery = resultQuery; this.state.cypherQueryParams = queryParams; this.state.attributesCypherQuery = attributesQuery; this.state.relationsCypherQuery = relationsQuery; this.state.resultTypes = returnType; } /** * Create and run the cypher queries for the current query state. * * Updates the results and nextQuery attributes and relations. */ updateQuery() { this.createCypherQuery(); this.state.resultInfo = 'loading...'; /* * run query for result table */ var queries = [this.state.resultCypherQuery]; var params = [this.state.cypherQueryParams]; if (this.state.attributesCypherQuery) { queries.push(this.state.attributesCypherQuery); params.push(this.state.cypherQueryParams); } if (this.state.relationsCypherQuery) { queries.push(this.state.relationsCypherQuery); params.push(this.state.cypherQueryParams); } var res = this.fetchCypherResults(queries, params); res.subscribe( data => { console.debug("neo4j result data=", data); var 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 var resTypes = {}; this.state.results.forEach((r) => { if (resTypes[r.type] == null) { resTypes[r.type] = 1; } else { resTypes[r.type] += 1; } }); var info = ''; for (var 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.relationsCypherQuery) { resIdx += 1; var rels = data.results[resIdx].data.map(elem => elem.row[0]) .filter(elem => elem[0] != "_"); // add inverse relations var invrels = rels.concat(rels.map((r) => this.invRelPrefix + r)); this.state.resultRelations = invrels; } }, err => console.error("neo4j result error=", err), () => console.debug('neo4j result query Complete') ); } filterAttributes(attributes: string[], normalized=false) { var 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); var headers = new Headers(); var auth = this.neo4jAuthentication; headers.append('Authorization', 'Basic ' + btoa(`${auth.user}:${auth.password}`)); headers.append('Content-Type', 'application/json'); headers.append('Accept', 'application/json'); // put headers in options var opts = {'headers': headers}; // unpack queries into statements var statements = queries.map((q, i) => { return {'statement': q, 'parameters': (params[i])?params[i]:{}}; }); // create POST data from query var data = JSON.stringify({'statements': statements}); // make post request asynchronously var resp = this._http.post(this.neo4jBaseUrl+'/transaction/commit', data, opts) // filter result as JSON .map(res => res.json()); // return Observable return resp; } }