graph.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. /*
  2. * EliasDB
  3. *
  4. * Copyright 2016 Matthias Ladkau. All rights reserved.
  5. *
  6. * This Source Code Form is subject to the terms of the Mozilla Public
  7. * License, v. 2.0. If a copy of the MPL was not distributed with this
  8. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  9. */
  10. package v1
  11. import (
  12. "encoding/json"
  13. "fmt"
  14. "net/http"
  15. "sort"
  16. "strconv"
  17. "devt.de/krotik/eliasdb/api"
  18. "devt.de/krotik/eliasdb/graph"
  19. "devt.de/krotik/eliasdb/graph/data"
  20. )
  21. /*
  22. EndpointGraph is the graph endpoint URL (rooted). Handles everything under graph/...
  23. */
  24. const EndpointGraph = api.APIRoot + APIv1 + "/graph/"
  25. /*
  26. GraphEndpointInst creates a new endpoint handler.
  27. */
  28. func GraphEndpointInst() api.RestEndpointHandler {
  29. return &graphEndpoint{}
  30. }
  31. /*
  32. Handler object for graph operations.
  33. */
  34. type graphEndpoint struct {
  35. *api.DefaultEndpointHandler
  36. }
  37. /*
  38. HandleGET handles REST calls to retrieve data from the graph database.
  39. */
  40. func (ge *graphEndpoint) HandleGET(w http.ResponseWriter, r *http.Request, resources []string) {
  41. // Check parameters
  42. if !checkResources(w, resources, 3, 5, "Need a partition, entity type (n or e) and a kind; optional key and traversal spec") {
  43. return
  44. }
  45. if resources[1] != "n" && resources[1] != "e" {
  46. http.Error(w, "Entity type must be n (nodes) or e (edges)", http.StatusBadRequest)
  47. return
  48. }
  49. if len(resources) == 3 {
  50. // Iterate over a list of nodes
  51. if resources[1] == "n" {
  52. // Get limit parameter; -1 if not set
  53. limit, ok := queryParamPosNum(w, r, "limit")
  54. if !ok {
  55. return
  56. }
  57. // Get offset parameter; -1 if not set
  58. offset, ok := queryParamPosNum(w, r, "offset")
  59. if !ok {
  60. return
  61. }
  62. it, err := api.GM.NodeKeyIterator(resources[0], resources[2])
  63. if err != nil {
  64. http.Error(w, err.Error(), http.StatusInternalServerError)
  65. return
  66. } else if it == nil {
  67. http.Error(w, "Unknown partition or node kind", http.StatusBadRequest)
  68. return
  69. }
  70. i := 0
  71. if offset != -1 {
  72. for i = 0; i < offset; i++ {
  73. if !it.HasNext() {
  74. http.Error(w, "Offset exceeds available nodes", http.StatusInternalServerError)
  75. return
  76. }
  77. if it.Next(); it.LastError != nil {
  78. http.Error(w, it.LastError.Error(), http.StatusInternalServerError)
  79. return
  80. }
  81. }
  82. } else {
  83. offset = 0
  84. }
  85. var data []interface{}
  86. if limit == -1 {
  87. data = make([]interface{}, 0)
  88. } else {
  89. data = make([]interface{}, 0, limit)
  90. }
  91. for i = offset; it.HasNext(); i++ {
  92. // Break out if the limit was reached
  93. if limit != -1 && i > offset+limit-1 {
  94. break
  95. }
  96. key := it.Next()
  97. if it.LastError != nil {
  98. http.Error(w, it.LastError.Error(), http.StatusInternalServerError)
  99. return
  100. }
  101. node, err := api.GM.FetchNode(resources[0], key, resources[2])
  102. if err != nil {
  103. http.Error(w, err.Error(), http.StatusInternalServerError)
  104. return
  105. }
  106. data = append(data, node.Data())
  107. }
  108. // Set total count header
  109. w.Header().Add(HTTPHeaderTotalCount, strconv.FormatUint(api.GM.NodeCount(resources[2]), 10))
  110. // Write data
  111. w.Header().Set("content-type", "application/json; charset=utf-8")
  112. ret := json.NewEncoder(w)
  113. ret.Encode(data)
  114. } else {
  115. http.Error(w, "Entity type must be n (nodes) when requesting all items", http.StatusBadRequest)
  116. return
  117. }
  118. } else if len(resources) == 4 {
  119. // Fetch a specific node or relationship
  120. var data map[string]interface{}
  121. if resources[1] == "n" {
  122. node, err := api.GM.FetchNode(resources[0], resources[3], resources[2])
  123. if err != nil {
  124. http.Error(w, err.Error(), http.StatusInternalServerError)
  125. return
  126. } else if node == nil {
  127. http.Error(w, "Unknown partition or node kind", http.StatusBadRequest)
  128. return
  129. }
  130. data = node.Data()
  131. } else {
  132. edge, err := api.GM.FetchEdge(resources[0], resources[3], resources[2])
  133. if err != nil {
  134. http.Error(w, err.Error(), http.StatusInternalServerError)
  135. return
  136. } else if edge == nil {
  137. http.Error(w, "Unknown partition or edge kind", http.StatusBadRequest)
  138. return
  139. }
  140. data = edge.Data()
  141. }
  142. // Write data
  143. w.Header().Set("content-type", "application/json; charset=utf-8")
  144. ret := json.NewEncoder(w)
  145. ret.Encode(data)
  146. } else {
  147. if resources[1] == "n" {
  148. node, err := api.GM.FetchNodePart(resources[0], resources[3], resources[2], []string{"key", "kind"})
  149. if err != nil {
  150. http.Error(w, err.Error(), http.StatusInternalServerError)
  151. return
  152. } else if node == nil {
  153. http.Error(w, "Unknown partition or node kind", http.StatusBadRequest)
  154. return
  155. }
  156. nodes, edges, err := api.GM.TraverseMulti(resources[0], resources[3],
  157. resources[2], resources[4], true)
  158. if err != nil {
  159. http.Error(w, err.Error(), http.StatusInternalServerError)
  160. return
  161. }
  162. data := make([][]map[string]interface{}, 2)
  163. dataNodes := make([]map[string]interface{}, 0, len(nodes))
  164. dataEdges := make([]map[string]interface{}, 0, len(edges))
  165. if nodes != nil && edges != nil {
  166. for i, n := range nodes {
  167. e := edges[i]
  168. dataNodes = append(dataNodes, n.Data())
  169. dataEdges = append(dataEdges, e.Data())
  170. }
  171. }
  172. data[0] = dataNodes
  173. data[1] = dataEdges
  174. // Sort the result
  175. sort.Stable(&traversalResultComparator{data})
  176. // Write data
  177. w.Header().Set("content-type", "application/json; charset=utf-8")
  178. ret := json.NewEncoder(w)
  179. ret.Encode(data)
  180. } else {
  181. http.Error(w, "Entity type must be n (nodes) when requesting traversal results", http.StatusBadRequest)
  182. return
  183. }
  184. }
  185. }
  186. /*
  187. HandlePUT handles a REST call to insert new elements into the graph or update
  188. existing elements. Nodes are updated if they already exist. Edges are replaced
  189. if they already exist.
  190. */
  191. func (ge *graphEndpoint) HandlePUT(w http.ResponseWriter, r *http.Request, resources []string) {
  192. ge.handleGraphRequest(w, r, resources,
  193. func(trans graph.Trans, part string, node data.Node) error {
  194. return trans.UpdateNode(part, node)
  195. },
  196. func(trans graph.Trans, part string, edge data.Edge) error {
  197. return trans.StoreEdge(part, edge)
  198. })
  199. }
  200. /*
  201. HandlePOST handles a REST call to insert new elements into the graph or update
  202. existing elements. Nodes and edges are replaced if they already exist.
  203. */
  204. func (ge *graphEndpoint) HandlePOST(w http.ResponseWriter, r *http.Request, resources []string) {
  205. ge.handleGraphRequest(w, r, resources,
  206. func(trans graph.Trans, part string, node data.Node) error {
  207. return trans.StoreNode(part, node)
  208. },
  209. func(trans graph.Trans, part string, edge data.Edge) error {
  210. return trans.StoreEdge(part, edge)
  211. })
  212. }
  213. /*
  214. HandleDELETE handles a REST call to delete elements from the graph.
  215. */
  216. func (ge *graphEndpoint) HandleDELETE(w http.ResponseWriter, r *http.Request, resources []string) {
  217. ge.handleGraphRequest(w, r, resources,
  218. func(trans graph.Trans, part string, node data.Node) error {
  219. return trans.RemoveNode(part, node.Key(), node.Kind())
  220. },
  221. func(trans graph.Trans, part string, edge data.Edge) error {
  222. return trans.RemoveEdge(part, edge.Key(), edge.Kind())
  223. })
  224. }
  225. /*
  226. handleGraphRequest handles a graph query REST call.
  227. */
  228. func (ge *graphEndpoint) handleGraphRequest(w http.ResponseWriter, r *http.Request, resources []string,
  229. transFuncNode func(trans graph.Trans, part string, node data.Node) error,
  230. transFuncEdge func(trans graph.Trans, part string, edge data.Edge) error) {
  231. var nDataList []map[string]interface{}
  232. var eDataList []map[string]interface{}
  233. // Check parameters
  234. if !checkResources(w, resources, 1, 2, "Need a partition; optional entity type (n or e)") {
  235. return
  236. }
  237. dec := json.NewDecoder(r.Body)
  238. if len(resources) == 1 {
  239. // No explicit type given - expecting a graph
  240. gdata := make(map[string][]map[string]interface{})
  241. if err := dec.Decode(&gdata); err != nil {
  242. http.Error(w, "Could not decode request body as object with list of nodes and/or edges: "+err.Error(), http.StatusBadRequest)
  243. return
  244. }
  245. nDataList = gdata["nodes"]
  246. eDataList = gdata["edges"]
  247. } else if resources[1] == "n" {
  248. nDataList = make([]map[string]interface{}, 1)
  249. if err := dec.Decode(&nDataList); err != nil {
  250. http.Error(w, "Could not decode request body as list of nodes: "+err.Error(), http.StatusBadRequest)
  251. return
  252. }
  253. } else if resources[1] == "e" {
  254. eDataList = make([]map[string]interface{}, 1)
  255. if err := dec.Decode(&eDataList); err != nil {
  256. http.Error(w, "Could not decode request body as list of edges: "+err.Error(), http.StatusBadRequest)
  257. return
  258. }
  259. }
  260. // Create a transaction
  261. trans := graph.NewGraphTrans(api.GM)
  262. if nDataList != nil {
  263. // Store nodes in transaction
  264. for _, ndata := range nDataList {
  265. node := data.NewGraphNodeFromMap(ndata)
  266. if err := transFuncNode(trans, resources[0], node); err != nil {
  267. http.Error(w, err.Error(), http.StatusBadRequest)
  268. return
  269. }
  270. }
  271. }
  272. if eDataList != nil {
  273. // Store edges in transaction
  274. for _, edata := range eDataList {
  275. edge := data.NewGraphEdgeFromNode(data.NewGraphNodeFromMap(edata))
  276. if err := transFuncEdge(trans, resources[0], edge); err != nil {
  277. http.Error(w, err.Error(), http.StatusBadRequest)
  278. return
  279. }
  280. }
  281. }
  282. // Commit transaction
  283. if err := trans.Commit(); err != nil {
  284. http.Error(w, err.Error(), http.StatusInternalServerError)
  285. return
  286. }
  287. }
  288. /*
  289. SwaggerDefs is used to describe the endpoint in swagger.
  290. */
  291. func (ge *graphEndpoint) SwaggerDefs(s map[string]interface{}) {
  292. partitionParams := []map[string]interface{}{
  293. map[string]interface{}{
  294. "name": "partition",
  295. "in": "path",
  296. "description": "Partition to select.",
  297. "required": true,
  298. "type": "string",
  299. },
  300. }
  301. entityParams := []map[string]interface{}{
  302. map[string]interface{}{
  303. "name": "entity_type",
  304. "in": "path",
  305. "description": "Datastore entity type which should selected. " +
  306. "Either n for nodes or e for edges.",
  307. "required": true,
  308. "type": "string",
  309. },
  310. }
  311. defaultParams := []map[string]interface{}{
  312. map[string]interface{}{
  313. "name": "kind",
  314. "in": "path",
  315. "description": "Node or edge kind to be queried.",
  316. "required": true,
  317. "type": "string",
  318. },
  319. }
  320. defaultParams = append(defaultParams, partitionParams...)
  321. defaultParams = append(defaultParams, entityParams...)
  322. optionalQueryParams := []map[string]interface{}{
  323. map[string]interface{}{
  324. "name": "limit",
  325. "in": "query",
  326. "description": "How many list items to return.",
  327. "required": false,
  328. "type": "number",
  329. "format": "integer",
  330. },
  331. map[string]interface{}{
  332. "name": "offset",
  333. "in": "query",
  334. "description": "Offset in the dataset.",
  335. "required": false,
  336. "type": "number",
  337. "format": "integer",
  338. },
  339. }
  340. keyParam := []map[string]interface{}{
  341. map[string]interface{}{
  342. "name": "key",
  343. "in": "path",
  344. "description": "Node or edge key to be queried.",
  345. "required": true,
  346. "type": "string",
  347. },
  348. }
  349. travParam := []map[string]interface{}{
  350. map[string]interface{}{
  351. "name": "traversal_spec",
  352. "in": "path",
  353. "description": "Traversal to be followed from a single node.",
  354. "required": true,
  355. "type": "string",
  356. },
  357. }
  358. graphPost := []map[string]interface{}{
  359. map[string]interface{}{
  360. "name": "entities",
  361. "in": "body",
  362. "description": "Nodes and Edges which should be stored",
  363. "required": true,
  364. "schema": map[string]interface{}{
  365. "type": "object",
  366. "properties": map[string]interface{}{
  367. "nodes": map[string]interface{}{
  368. "description": "List of nodes to be inserted / updated.",
  369. "type": "array",
  370. "items": map[string]interface{}{
  371. "description": "Node to be inserted / updated.",
  372. "type": "object",
  373. },
  374. },
  375. "edges": map[string]interface{}{
  376. "description": "List of edges to be inserted / updated.",
  377. "type": "array",
  378. "items": map[string]interface{}{
  379. "description": "Edge to be inserted / updated.",
  380. "type": "object",
  381. },
  382. },
  383. },
  384. },
  385. },
  386. }
  387. entitiesPost := []map[string]interface{}{
  388. map[string]interface{}{
  389. "name": "entities",
  390. "in": "body",
  391. "description": "Nodes or Edges which should be stored",
  392. "required": true,
  393. "schema": map[string]interface{}{
  394. "type": "array",
  395. "items": map[string]interface{}{
  396. "description": "Node or edge to be inserted / updated.",
  397. "type": "object",
  398. },
  399. },
  400. },
  401. }
  402. defaultError := map[string]interface{}{
  403. "description": "Error response",
  404. "schema": map[string]interface{}{
  405. "$ref": "#/definitions/Error",
  406. },
  407. }
  408. // Add endpoint to insert a graph with nodes and edges
  409. s["paths"].(map[string]interface{})["/v1/graph/{partition}"] = map[string]interface{}{
  410. "post": map[string]interface{}{
  411. "summary": "Data can be send by using POST requests.",
  412. "description": "A whole graph can be send. " +
  413. "POST will store data in the datastore and always overwrite any existing data.",
  414. "consumes": []string{
  415. "application/json",
  416. },
  417. "produces": []string{
  418. "text/plain",
  419. "application/json",
  420. },
  421. "parameters": append(partitionParams, graphPost...),
  422. "responses": map[string]interface{}{
  423. "200": map[string]interface{}{
  424. "description": "No data is returned when data is created.",
  425. },
  426. "default": defaultError,
  427. },
  428. },
  429. }
  430. // Add endpoint to insert nodes / edges
  431. s["paths"].(map[string]interface{})["/v1/graph/{partition}/{entity_type}"] = map[string]interface{}{
  432. "post": map[string]interface{}{
  433. "summary": "Data can be send by using POST requests.",
  434. "description": "A list of nodes / edges can be send. " +
  435. "POST will store data in the datastore and always overwrite any existing data.",
  436. "consumes": []string{
  437. "application/json",
  438. },
  439. "produces": []string{
  440. "text/plain",
  441. "application/json",
  442. },
  443. "parameters": append(append(partitionParams, entityParams...), entitiesPost...),
  444. "responses": map[string]interface{}{
  445. "200": map[string]interface{}{
  446. "description": "No data is returned when data is created.",
  447. },
  448. "default": defaultError,
  449. },
  450. },
  451. }
  452. // Add endpoint to query nodes for a specific node kind
  453. s["paths"].(map[string]interface{})["/v1/graph/{partition}/{entity_type}/{kind}"] = map[string]interface{}{
  454. "get": map[string]interface{}{
  455. "summary": "The graph endpoint is the main entry point to request data.",
  456. "description": "GET requests can be used to query a series of nodes. " +
  457. "The X-Total-Count header contains the total number of nodes which were found.",
  458. "produces": []string{
  459. "text/plain",
  460. "application/json",
  461. },
  462. "parameters": append(defaultParams, optionalQueryParams...),
  463. "responses": map[string]interface{}{
  464. "200": map[string]interface{}{
  465. "description": "The return data is a list of objects",
  466. "schema": map[string]interface{}{
  467. "type": "array",
  468. "items": map[string]interface{}{
  469. "type": "object",
  470. },
  471. },
  472. },
  473. "default": defaultError,
  474. },
  475. },
  476. }
  477. // Add endpoint to query/create a specific node
  478. s["paths"].(map[string]interface{})["/v1/graph/{partition}/{entity_type}/{kind}/{key}"] = map[string]interface{}{
  479. "get": map[string]interface{}{
  480. "summary": "The graph endpoint is the main entry point to request data.",
  481. "description": "GET requests can be used to query a single node.",
  482. "produces": []string{
  483. "text/plain",
  484. "application/json",
  485. },
  486. "parameters": append(append(defaultParams, keyParam...), optionalQueryParams...),
  487. "responses": map[string]interface{}{
  488. "200": map[string]interface{}{
  489. "description": "The return data is a single object",
  490. "schema": map[string]interface{}{
  491. "type": "object",
  492. },
  493. },
  494. "default": defaultError,
  495. },
  496. },
  497. }
  498. // Add endpoint to traverse from a single node
  499. s["paths"].(map[string]interface{})["/v1/graph/{partition}/{entity_type}/{kind}/{key}/{traversal_spec}"] = map[string]interface{}{
  500. "get": map[string]interface{}{
  501. "summary": "The graph endpoint is the main entry point to request data.",
  502. "description": "GET requests can be used to query a single node and then traverse to its neighbours.",
  503. "produces": []string{
  504. "text/plain",
  505. "application/json",
  506. },
  507. "parameters": append(append(defaultParams, keyParam...), travParam...),
  508. "responses": map[string]interface{}{
  509. "200": map[string]interface{}{
  510. "description": "The return data are two lists containing traversed nodes and edges. " +
  511. "The traversal endpoint does NOT support limit and offset parameters. " +
  512. "Also the X-Total-Count header is not set.",
  513. "schema": map[string]interface{}{
  514. "type": "array",
  515. "items": map[string]interface{}{
  516. "type": "array",
  517. "items": map[string]interface{}{
  518. "type": "object",
  519. },
  520. },
  521. },
  522. },
  523. "default": defaultError,
  524. },
  525. },
  526. }
  527. }
  528. // Comparator object to sort traversal results
  529. type traversalResultComparator struct {
  530. Data [][]map[string]interface{} // Data to sort
  531. }
  532. func (c traversalResultComparator) Len() int {
  533. return len(c.Data[0])
  534. }
  535. func (c traversalResultComparator) Less(i, j int) bool {
  536. c1 := c.Data[0][i]
  537. c2 := c.Data[0][j]
  538. return fmt.Sprintf("%v", c1[data.NodeKey]) < fmt.Sprintf("%v", c2[data.NodeKey])
  539. }
  540. func (c traversalResultComparator) Swap(i, j int) {
  541. c.Data[0][i], c.Data[0][j] = c.Data[0][j], c.Data[0][i]
  542. c.Data[1][i], c.Data[1][j] = c.Data[1][j], c.Data[1][i]
  543. }