X-Git-Url: http://nitlanguage.org?ds=sidebyside diff --git a/lib/neo4j/neo4j.nit b/lib/neo4j/neo4j.nit index 385b966..88c151a 100644 --- a/lib/neo4j/neo4j.nit +++ b/lib/neo4j/neo4j.nit @@ -64,6 +64,7 @@ module neo4j import curl_json +import error # Handles Neo4j server start and stop command # @@ -115,16 +116,15 @@ class Neo4jClient init(base_url: String) do self.base_url = base_url var root = service_root - if not root isa JsonObject then - print "Neo4jClientError: cannot connect to server at {base_url}" - abort + assert root isa JsonObject else + sys.stderr.write "Neo4jClientError: cannot connect to server at <{base_url}>.\n" end self.node_url = root["node"].to_s self.batch_url = root["batch"].to_s self.cypher_url = root["cypher"].to_s end - fun service_root: Jsonable do return get("{base_url}/db/data") + fun service_root: Jsonable do return get(base_url / "db/data") # Is the connection with the Neo4j server ok? fun is_ok: Bool do return service_root isa JsonObject @@ -260,7 +260,7 @@ class Neo4jClient # assert nodes.has(andres) # assert nodes.has(kate) fun nodes_with_label(lbl: String): Array[NeoNode] do - var res = get("{base_url}/db/data/label/{lbl}/nodes") + var res = get(base_url / "db/data/label/{lbl.to_percent_encoding}/nodes") var nodes = new Array[NeoNode] for json in res.as(JsonArray) do var obj = json.as(JsonObject) @@ -287,7 +287,21 @@ class Neo4jClient # assert not nodes.has(kate) fun nodes_with_labels(labels: Array[String]): Array[NeoNode] do assert not labels.is_empty - var res = cypher(new CypherQuery.from_string("MATCH (n:{labels.join(":")}) RETURN n")) + + # Build the query. + var buffer = new RopeBuffer + buffer.append "match n where \{label_0\} in labels(n)" + for i in [1..labels.length[ do + buffer.append " and \{label_{i}\} in labels(n)" + end + buffer.append " return n" + var query = new CypherQuery.from_string(buffer.write_to_string) + for i in [0..labels.length[ do + query.params["label_{i}"] = labels[i] + end + + # Retrieve the answer. + var res = cypher(query) var nodes = new Array[NeoNode] for json in res.as(JsonObject)["data"].as(JsonArray) do var obj = json.as(JsonArray).first.as(JsonObject) @@ -301,19 +315,19 @@ class Neo4jClient # Perform a `CypherQuery` # see: CypherQuery fun cypher(query: CypherQuery): Jsonable do - return post("{cypher_url}", query.to_json) + return post("{cypher_url}", query.to_rest) end # GET JSON data from `url` fun get(url: String): Jsonable do - var request = new JsonGET(url, curl) + var request = new JsonGET(url) var response = request.execute return parse_response(response) end # POST `params` to `url` fun post(url: String, params: Jsonable): Jsonable do - var request = new JsonPOST(url, curl) + var request = new JsonPOST(url) request.data = params var response = request.execute return parse_response(response) @@ -321,7 +335,7 @@ class Neo4jClient # PUT `params` at `url` fun put(url: String, params: Jsonable): Jsonable do - var request = new JsonPUT(url, curl) + var request = new JsonPUT(url) request.data = params var response = request.execute return parse_response(response) @@ -329,7 +343,7 @@ class Neo4jClient # DELETE `url` fun delete(url: String): Jsonable do - var request = new JsonDELETE(url, curl) + var request = new JsonDELETE(url) var response = request.execute return parse_response(response) end @@ -337,29 +351,31 @@ class Neo4jClient # Parse the cURL `response` as a JSON string private fun parse_response(response: CurlResponse): Jsonable do if response isa CurlResponseSuccess then - if response.body_str.is_empty then + var str = response.body_str + if str.is_empty then return new JsonObject + var res = str.parse_json + if res isa JsonParseError then + var e = new NeoError(res.to_s, "JsonParseError") + e.cause = res + return e + end + if res == null then + # empty response wrap it in empty object return new JsonObject - else - var str = response.body_str - var res = str.to_jsonable - if res == null then - # empty response wrap it in empty object - return new JsonObject - else if res isa JsonObject and res.has_key("exception") then - var error = "Neo4jError::{res["exception"] or else "null"}" - var msg = "" - if res.has_key("message") then - msg = res["message"].to_s - end - return new JsonError(error, msg.to_json) - else - return res + else if res isa JsonObject and res.has_key("exception") then + var error = "Neo4jError::{res["exception"] or else "null"}" + var msg = "" + if res.has_key("message") then + msg = res["message"].to_s end + return new NeoError(msg, error) + else + return res end else if response isa CurlResponseFailed then - return new JsonError("Curl error", "{response.error_msg} ({response.error_code})") + return new NeoError("{response.error_msg} ({response.error_code})", "CurlError") else - return new JsonError("Curl error", "Unexpected response '{response}'") + return new NeoError("Unexpected response \"{response}\".", "CurlError") end end end @@ -389,8 +405,6 @@ class CypherQuery # `params` to embed in the query like in prepared statements var params = new JsonObject - init do end - # init the query from a query string init from_string(query: String) do self.query = query @@ -437,8 +451,8 @@ class CypherQuery return self end - # Translate the query to JSON - fun to_json: JsonObject do + # Translate the query to the body of a corresponding Neo4j REST request. + fun to_rest: JsonObject do var obj = new JsonObject obj["query"] = query if not params.is_empty then @@ -447,7 +461,7 @@ class CypherQuery return obj end - redef fun to_s do return to_json.to_s + redef fun to_s do return to_rest.to_s end # The fundamental units that form a graph are nodes and relationships. @@ -525,7 +539,7 @@ abstract class NeoEntity private var internal_properties: nullable JsonObject = null private fun load_properties: JsonObject do - var obj = neo.get("{url.to_s}/properties").as(JsonObject) + var obj = neo.get(url.to_s / "properties").as(JsonObject) internal_properties = obj return obj end @@ -547,9 +561,6 @@ abstract class NeoEntity # Is the property `key` set? fun has_key(key: String): Bool do return properties.has_key(key) - - # Translate `self` to JSON - fun to_json: JsonObject do return properties end # Nodes are used to represent entities stored in base. @@ -596,7 +607,7 @@ class NeoNode var tpl = new FlatBuffer tpl.append "\{" tpl.append "labels: [{labels.join(", ")}]," - tpl.append "data: {to_json}" + tpl.append "data: {properties.to_json}" tpl.append "\}" return tpl.write_to_string end @@ -613,7 +624,7 @@ class NeoNode private fun load_labels: Array[String] do var labels = new Array[String] - var res = neo.get("{url.to_s}/labels") + var res = neo.get(url.to_s / "labels") if res isa JsonArray then for val in res do labels.add val.to_s end @@ -628,7 +639,7 @@ class NeoNode private fun load_in_edges: List[NeoEdge] do var edges = new List[NeoEdge] - var res = neo.get("{url.to_s}/relationships/in").as(JsonArray) + var res = neo.get(url.to_s / "relationships/in").as(JsonArray) for obj in res do edges.add(new NeoEdge.from_json(neo, obj.as(JsonObject))) end @@ -643,7 +654,7 @@ class NeoNode private fun load_out_edges: List[NeoEdge] do var edges = new List[NeoEdge] - var res = neo.get("{url.to_s}/relationships/out") + var res = neo.get(url.to_s / "relationships/out") for obj in res.as(JsonArray) do edges.add(new NeoEdge.from_json(neo, obj.as(JsonObject))) end @@ -744,7 +755,8 @@ class NeoEdge # Get edge type fun rel_type: nullable String do return internal_type - redef fun to_json do + # Get the JSON body of a REST request that create the relationship. + private fun to_rest: JsonObject do var obj = new JsonObject if to.is_linked then obj["to"] = to.url @@ -892,18 +904,18 @@ class NeoBatch else job.to = "\{{edge.from.batch_id.to_s}\}/relationships" end - job.body = edge.to_json + job.body = edge.to_rest end # Create multiple edges fun save_edges(edges: Collection[NeoEdge]) do for edge in edges do save_edge(edge) # Execute the batch and update local nodes - fun execute: List[JsonError] do - var request = new JsonPOST(client.batch_url, client.curl) + fun execute: List[NeoError] do + var request = new JsonPOST(client.batch_url) # request.headers["X-Stream"] = "true" var json_jobs = new JsonArray - for job in jobs.values do json_jobs.add job.to_json + for job in jobs.values do json_jobs.add job.to_rest request.data = json_jobs var response = request.execute var res = client.parse_response(response) @@ -911,16 +923,16 @@ class NeoBatch end # Associate data from response in original nodes and edges - private fun finalize_batch(response: Jsonable): List[JsonError] do - var errors = new List[JsonError] + private fun finalize_batch(response: Jsonable): List[NeoError] do + var errors = new List[NeoError] if not response isa JsonArray then - errors.add(new JsonError("Neo4jError", "Unexpected batch response format")) + errors.add(new NeoError("Unexpected batch response format.", "Neo4jError")) return errors end # print " {res.length} jobs executed" for res in response do if not res isa JsonObject then - errors.add(new JsonError("Neo4jError", "Unexpected job format in batch response")) + errors.add(new NeoError("Unexpected job format in batch response.", "Neo4jError")) continue end var id = res["id"].as(Int) @@ -1004,7 +1016,7 @@ class NeoJob var body: nullable Jsonable = null # JSON formated job - fun to_json: JsonObject do + fun to_rest: JsonObject do var job = new JsonObject job["id"] = id job["method"] = method