├── .gitignore ├── ci └── before_script.sh ├── src ├── instance.rs ├── lib.rs ├── schema.rs ├── util.rs └── datastore.rs ├── test.sh ├── README.md ├── Cargo.toml ├── .travis.yml └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /ci/before_script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $TRAVIS_OS_NAME = osx ]; then 4 | rm -rf /usr/local/var/postgres 5 | initdb /usr/local/var/postgres 6 | pg_ctl -D /usr/local/var/postgres -w start 7 | createuser -s postgres 8 | fi 9 | 10 | dropdb --if-exists indradb_test 11 | createdb --owner=$PG_USER indradb_test 12 | 13 | source ~/.cargo/env || true 14 | -------------------------------------------------------------------------------- /src/instance.rs: -------------------------------------------------------------------------------- 1 | // Dead code detection is inaccurate because this module is only used in 2 | // conditionally compiled macros 3 | #![allow(dead_code)] 4 | 5 | use super::PostgresDatastore; 6 | use std::env; 7 | use std::sync::{Once, ONCE_INIT}; 8 | 9 | static START: Once = ONCE_INIT; 10 | 11 | /// Creates an instance of a pg datastore. Used for testing/benchmarking. 12 | pub fn datastore() -> PostgresDatastore { 13 | let connection_string = env::var("TEST_POSTGRES_URL").expect("Expected a TEST_POSTGRES_URL"); 14 | 15 | START.call_once(|| { 16 | PostgresDatastore::create_schema(connection_string.clone()).unwrap(); 17 | }); 18 | 19 | PostgresDatastore::new(Some(1), connection_string).unwrap() 20 | } 21 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | PG_USER='postgres' 6 | UNAME_STR=`uname` 7 | if [[ "$UNAME_STR" == 'Darwin' ]]; then 8 | PG_USER=`whoami` 9 | echo "Using user '${PG_USER}' for postgres" 10 | fi 11 | 12 | export RUST_BACKTRACE=1 13 | export TEST_POSTGRES_URL="postgres://${PG_USER}@localhost:5432/indradb_test" 14 | 15 | ACTION=test 16 | 17 | while true; do 18 | case "$1" in 19 | --bench) ACTION=bench; shift ;; 20 | * ) break ;; 21 | esac 22 | done 23 | 24 | dropdb --if-exists indradb_test 25 | createdb --owner=$PG_USER indradb_test 26 | cargo update 27 | 28 | if [ "$ACTION" == "test" ]; then 29 | cargo test --features=test-suite $TEST_NAME 30 | else 31 | cargo +nightly bench --features=bench-suite $TEST_NAME 32 | fi 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IndraDB Postgres Implementation [![Docs](https://docs.rs/indradb-postgres/badge.svg)](https://docs.rs/indradb-postgres) [![Build Status](https://travis-ci.org/indradb/postgres.svg?branch=master)](https://travis-ci.org/indradb/postgres) 2 | 3 | This is an implementation of the IndraDB datastore for postgres. It should 4 | generally be considered by far the slowest datastore implementation available, 5 | however it provides a few major benefits: 6 | 7 | * Transaction changes can be rolled back on error. 8 | * Multiple `IndraDB` server processes can run on the same datastore at the same 9 | time. 10 | * You can use all of the postgres tooling to poke around at the results. 11 | * Thanks to foreign keys et al., this is probably less buggy than other 12 | implementations. 13 | 14 | ## Running tests 15 | 16 | Run `./test.sh`. 17 | 18 | ## Running benchmarks 19 | 20 | Run `./test.sh --bench`. 21 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The postgres datastore implementation. 2 | //! 3 | //! This should generally be considered by far the slowest implementation, 4 | //! however it provides a few major benefits: 5 | //! 6 | //! * Transaction changes can be rolled back on error. 7 | //! * Multiple `IndraDB` server processes can run on the same datastore at the 8 | //! same time. 9 | //! * You can use all of the postgres tooling to poke around at the results. 10 | //! * Thanks to foreign keys et al., this is probably less buggy than other 11 | //! implementations. 12 | 13 | #![recursion_limit = "1024"] 14 | #![cfg_attr(feature = "bench-suite", feature(test))] 15 | 16 | extern crate chrono; 17 | #[macro_use] 18 | extern crate indradb; 19 | extern crate num_cpus; 20 | extern crate postgres; 21 | extern crate r2d2; 22 | extern crate r2d2_postgres; 23 | extern crate serde_json; 24 | extern crate uuid; 25 | 26 | mod datastore; 27 | mod instance; 28 | mod schema; 29 | mod util; 30 | 31 | pub use self::datastore::{PostgresDatastore, PostgresTransaction}; 32 | 33 | #[cfg(feature = "bench-suite")] 34 | full_bench_impl!(instance::datastore()); 35 | 36 | #[cfg(feature = "test-suite")] 37 | full_test_impl!(instance::datastore()); 38 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "indradb-postgres" 3 | version = "0.1.0" 4 | authors = ["Yusuf Simonson "] 5 | description = "A postgres-backed datastore for IndraDB" 6 | homepage = "https://indradb.github.io" 7 | repository = "https://github.com/indradb/postgres" 8 | keywords = ["graph", "database"] 9 | categories = ["database", "database-implementations"] 10 | license = "MPL-2.0" 11 | 12 | [badges] 13 | travis-ci = { repository = "indradb/postgres", branch = "master" } 14 | 15 | [lib] 16 | name = "indradb_postgres" 17 | path = "src/lib.rs" 18 | 19 | [features] 20 | default = [] 21 | test-suite = ["indradb-lib/test-suite"] 22 | bench-suite = ["indradb-lib/bench-suite"] 23 | 24 | [dependencies] 25 | error-chain = "~0.11.0" 26 | postgres = { version = "0.15", features = ["with-serde_json", "with-chrono", "with-uuid"] } 27 | r2d2 = "0.8" 28 | r2d2_postgres = "0.14" 29 | num_cpus = "^1.8.0" 30 | rust-crypto = "~0.2.36" 31 | serde = "^1.0.27" 32 | serde_json = "^1.0.9" 33 | serde_derive = "^1.0.27" 34 | libc = "0.2.26" 35 | rand = "~0.4.2" 36 | regex = "~0.2.5" 37 | lazy_static = "^1.0.0" 38 | byteorder = "^1.2.1" 39 | chrono = { version = "0.4.0", features = ["serde"] } 40 | uuid = { version = ">=0.5,<0.6", features = ["serde", "v1"] } 41 | indradb-lib = "^0.15.0" 42 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: rust 3 | services: docker 4 | sudo: required 5 | 6 | addons: 7 | postgresql: "9.5" 8 | 9 | matrix: 10 | include: 11 | # Rust stable 12 | - rust: stable 13 | env: 14 | - TARGET=x86_64-unknown-linux-gnu 15 | - PG_USER=postgres 16 | - TEST_POSTGRES_URL="postgres://$PG_USER@localhost:5432/indradb_test" 17 | - os: osx 18 | rust: stable 19 | env: 20 | - TARGET=x86_64-apple-darwin 21 | - PG_USER=travis 22 | - TEST_POSTGRES_URL="postgres://$PG_USER@localhost:5432/indradb_test" 23 | 24 | # Rust beta 25 | - rust: beta 26 | env: 27 | - TARGET=x86_64-unknown-linux-gnu 28 | - PG_USER=postgres 29 | - TEST_POSTGRES_URL="postgres://$PG_USER@localhost:5432/indradb_test" 30 | 31 | # Rust nightly 32 | - rust: nightly 33 | env: 34 | - TARGET=x86_64-unknown-linux-gnu 35 | - PG_USER=postgres 36 | - TEST_POSTGRES_URL="postgres://$PG_USER@localhost:5432/indradb_test" 37 | 38 | allow_failures: 39 | # Rust nightly 40 | - rust: nightly 41 | env: 42 | - TARGET=x86_64-unknown-linux-gnu 43 | - PG_USER=postgres 44 | - TEST_POSTGRES_URL="postgres://$PG_USER@localhost:5432/indradb_test" 45 | 46 | env: 47 | global: 48 | - RUST_BACKTRACE=1 49 | 50 | before_script: ./ci/before_script.sh 51 | script: cargo test --features=test-suite 52 | 53 | cache: 54 | directories: 55 | - $HOME/.cargo 56 | - $TRAVIS_BUILD_DIR/target 57 | before_cache: 58 | # Travis can't cache files that are not readable by "others" 59 | - chmod -R a+r $HOME/.cargo 60 | 61 | notifications: 62 | email: 63 | on_success: never -------------------------------------------------------------------------------- /src/schema.rs: -------------------------------------------------------------------------------- 1 | pub const SCHEMA: &'static str = " 2 | /* Vertices */ 3 | CREATE TABLE vertices ( 4 | id UUID NOT NULL, 5 | type VARCHAR(1000) NOT NULL 6 | ); 7 | 8 | ALTER TABLE vertices 9 | ADD CONSTRAINT vertices_pkey PRIMARY KEY (id); 10 | 11 | /* Edges */ 12 | CREATE TABLE edges ( 13 | id UUID NOT NULL, 14 | outbound_id UUID NOT NULL, 15 | type VARCHAR(1000) NOT NULL, 16 | inbound_id UUID NOT NULL, 17 | update_timestamp TIMESTAMP WITH TIME ZONE NOT NULL 18 | ); 19 | 20 | ALTER TABLE edges 21 | ADD CONSTRAINT edges_pkey PRIMARY KEY (id), 22 | ADD CONSTRAINT edges_outbound_id_type_inbound_id_ukey UNIQUE (outbound_id, type, inbound_id), 23 | ADD CONSTRAINT edges_outbound_id_fkey FOREIGN KEY (outbound_id) REFERENCES vertices (id) ON DELETE CASCADE, 24 | ADD CONSTRAINT edges_inbound_id_fkey FOREIGN KEY (inbound_id) REFERENCES vertices (id) ON DELETE CASCADE; 25 | 26 | CREATE INDEX ix_edges_update_timestamp ON edges USING btree (update_timestamp); 27 | CREATE INDEX ix_edges_inbound_id ON edges USING btree (inbound_id); 28 | 29 | /* Vertex metadata */ 30 | CREATE TABLE vertex_metadata ( 31 | owner_id UUID NOT NULL, 32 | name VARCHAR(1024) NOT NULL, 33 | value JSONB NOT NULL 34 | ); 35 | 36 | ALTER TABLE vertex_metadata 37 | ADD CONSTRAINT vertex_metadata_pkey PRIMARY KEY (owner_id, name), 38 | ADD CONSTRAINT vertex_metadata_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES vertices (id) ON DELETE CASCADE; 39 | 40 | /* Metadata */ 41 | CREATE TABLE edge_metadata ( 42 | owner_id UUID NOT NULL, 43 | name VARCHAR(1024) NOT NULL, 44 | value JSONB NOT NULL 45 | ); 46 | 47 | ALTER TABLE edge_metadata 48 | ADD CONSTRAINT edge_metadata_pkey PRIMARY KEY (owner_id, name), 49 | ADD CONSTRAINT edge_metadata_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES edges (id) ON DELETE CASCADE; 50 | 51 | "; 52 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use postgres::types::ToSql; 2 | 3 | fn get_from_table_name(root_table_name: &str, table_number: usize) -> String { 4 | match table_number { 5 | 0 => root_table_name.to_string(), 6 | _ => format!("pipe_{}", table_number), 7 | } 8 | } 9 | 10 | fn format_query(template: &str, from_table_name: &str, cur_params_length: usize, param_number: usize) -> String { 11 | // Turn the query template into an actual query. 12 | // TODO: This could be made much more efficient. 13 | let mut query = template.replace("%t", from_table_name); 14 | 15 | for i in 1..cur_params_length + 1 { 16 | query = query.replacen("%p", &format!("${}", param_number + i)[..], 1); 17 | } 18 | 19 | query 20 | } 21 | 22 | pub struct CTEQueryBuilder { 23 | queries: Vec, 24 | params: Vec>, 25 | } 26 | 27 | impl CTEQueryBuilder { 28 | pub fn new() -> CTEQueryBuilder { 29 | CTEQueryBuilder { 30 | queries: Vec::new(), 31 | params: Vec::new(), 32 | } 33 | } 34 | 35 | pub fn push(&mut self, query_template: &str, root_table_name: &str, params: Vec>) { 36 | // TODO: because we don't support query parameter numbers, there are a 37 | // couple of times where we have to pass the same parameter multiple 38 | // times. Fix this. 39 | 40 | let from_table_name = get_from_table_name(root_table_name, self.queries.len()); 41 | let query = format_query( 42 | query_template, 43 | &from_table_name[..], 44 | params.len(), 45 | self.params.len(), 46 | ); 47 | self.queries.push(query); 48 | self.params.extend(params); 49 | } 50 | 51 | pub fn into_query_payload(self, query_template: &str, params: Vec>) -> (String, Vec>) { 52 | if self.queries.is_empty() { 53 | panic!("No queries"); 54 | } 55 | 56 | let from_table_name = get_from_table_name("", self.queries.len()); 57 | let query = format_query( 58 | query_template, 59 | &from_table_name[..], 60 | params.len(), 61 | self.params.len(), 62 | ); 63 | 64 | let mut full_params = self.params; 65 | full_params.extend(params); 66 | 67 | let mut buffer: Vec = Vec::new(); 68 | buffer.push("WITH ".to_string()); 69 | 70 | for (i, query) in self.queries.into_iter().enumerate() { 71 | if i > 0 { 72 | buffer.push(", ".to_string()); 73 | } 74 | 75 | buffer.push(format!("pipe_{} AS (", i + 1)); 76 | buffer.push(query); 77 | buffer.push(")".to_string()); 78 | } 79 | 80 | buffer.push(" ".to_string()); 81 | 82 | buffer.push(query); 83 | let full_query = buffer.join(""); 84 | (full_query, full_params) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /src/datastore.rs: -------------------------------------------------------------------------------- 1 | use super::schema; 2 | use indradb::{Datastore, EdgeDirection, EdgeQuery, Transaction, VertexQuery, Error, Result, Vertex, Type, EdgeKey, Edge, VertexMetadata, EdgeMetadata}; 3 | use super::util::CTEQueryBuilder; 4 | use chrono::DateTime; 5 | use chrono::offset::Utc; 6 | use num_cpus; 7 | use postgres; 8 | use postgres::types::ToSql; 9 | use r2d2::{Pool, PooledConnection}; 10 | use r2d2_postgres::{PostgresConnectionManager, TlsMode}; 11 | use serde_json::Value as JsonValue; 12 | use std::cmp::min; 13 | use std::i64; 14 | use std::mem; 15 | use indradb::util::generate_uuid_v1; 16 | use uuid::Uuid; 17 | use indradb::ResultExt; 18 | 19 | /// A datastore that is backed by a postgres database. 20 | #[derive(Clone, Debug)] 21 | pub struct PostgresDatastore { 22 | pool: Pool, 23 | } 24 | 25 | impl PostgresDatastore { 26 | /// Creates a new postgres-backed datastore. 27 | /// 28 | /// # Arguments 29 | /// * `pool_size` - The maximum number of connections to maintain to 30 | /// postgres. If `None`, it defaults to twice the number of CPUs. 31 | /// * `connetion_string` - The postgres database connection string. 32 | pub fn new(pool_size: Option, connection_string: String) -> Result { 33 | let unwrapped_pool_size: u32 = match pool_size { 34 | Some(val) => val, 35 | None => min(num_cpus::get() as u32, 128u32), 36 | }; 37 | 38 | let manager = PostgresConnectionManager::new(&*connection_string, TlsMode::None) 39 | .chain_err(|| "Could not create connection manager")?; 40 | let pool = Pool::builder() 41 | .max_size(unwrapped_pool_size) 42 | .build(manager) 43 | .chain_err(|| "Could nto create connection pool")?; 44 | 45 | Ok(PostgresDatastore { pool: pool }) 46 | } 47 | 48 | /// Creates a new postgres-backed datastore. 49 | /// 50 | /// # Arguments 51 | /// * `connection_string` - The postgres database connection string. 52 | pub fn create_schema(connection_string: String) -> Result<()> { 53 | let conn = postgres::Connection::connect(connection_string, postgres::TlsMode::None) 54 | .chain_err(|| "Could not connect to the postgres database")?; 55 | 56 | for statement in schema::SCHEMA.split(";") { 57 | conn.execute(statement, &vec![]).chain_err(|| "Could not execute statement")?; 58 | } 59 | 60 | Ok(()) 61 | } 62 | } 63 | 64 | impl Datastore for PostgresDatastore { 65 | fn transaction(&self) -> Result { 66 | let conn = self.pool.get().chain_err(|| "Could not get connection from the connection pool")?; 67 | let trans = PostgresTransaction::new(conn)?; 68 | Ok(trans) 69 | } 70 | } 71 | 72 | /// A postgres-backed datastore transaction. 73 | #[derive(Debug)] 74 | pub struct PostgresTransaction { 75 | trans: postgres::transaction::Transaction<'static>, 76 | conn: Box>, 77 | } 78 | 79 | impl PostgresTransaction { 80 | fn new(conn: PooledConnection) -> Result { 81 | let conn = Box::new(conn); 82 | 83 | let trans: postgres::transaction::Transaction<'static> = unsafe { 84 | mem::transmute(conn.transaction() 85 | .map_err(|err| Error::with_chain(err, "Could not create transaction"))?) 86 | }; 87 | 88 | trans.set_commit(); 89 | 90 | Ok(PostgresTransaction { 91 | conn: conn, 92 | trans: trans, 93 | }) 94 | } 95 | 96 | fn vertex_query_to_sql(&self, q: &VertexQuery, sql_query_builder: &mut CTEQueryBuilder) { 97 | match q { 98 | &VertexQuery::All { 99 | ref start_id, 100 | ref limit, 101 | } => match start_id { 102 | &Some(start_id) => { 103 | let query_template = "SELECT id, type FROM %t WHERE id > %p ORDER BY id LIMIT %p"; 104 | let params: Vec> = vec![Box::new(start_id), Box::new(*limit as i64)]; 105 | sql_query_builder.push(query_template, "vertices", params); 106 | } 107 | &None => { 108 | let query_template = "SELECT id, type FROM %t ORDER BY id LIMIT %p"; 109 | let params: Vec> = vec![Box::new(*limit as i64)]; 110 | sql_query_builder.push(query_template, "vertices", params); 111 | } 112 | }, 113 | &VertexQuery::Vertices { ref ids } => { 114 | let mut params_template_builder = vec![]; 115 | let mut params: Vec> = vec![]; 116 | 117 | for id in ids { 118 | params_template_builder.push("%p"); 119 | params.push(Box::new(*id)); 120 | } 121 | 122 | let query_template = format!( 123 | "SELECT id, type FROM %t WHERE id IN ({}) ORDER BY id", 124 | params_template_builder.join(", ") 125 | ); 126 | sql_query_builder.push(&query_template[..], "vertices", params); 127 | } 128 | &VertexQuery::Pipe { 129 | ref edge_query, 130 | ref converter, 131 | ref limit, 132 | } => { 133 | self.edge_query_to_sql(edge_query, sql_query_builder); 134 | let params: Vec> = vec![Box::new(*limit as i64)]; 135 | 136 | let query_template = match converter { 137 | &EdgeDirection::Outbound => { 138 | "SELECT id, type FROM vertices WHERE id IN (SELECT outbound_id FROM %t) ORDER BY id LIMIT %p" 139 | } 140 | &EdgeDirection::Inbound => { 141 | "SELECT id, type FROM vertices WHERE id IN (SELECT inbound_id FROM %t) ORDER BY id LIMIT %p" 142 | } 143 | }; 144 | 145 | sql_query_builder.push(query_template, "", params); 146 | } 147 | } 148 | } 149 | 150 | fn edge_query_to_sql(&self, q: &EdgeQuery, sql_query_builder: &mut CTEQueryBuilder) { 151 | match q { 152 | &EdgeQuery::Edges { ref keys } => { 153 | let mut params_template_builder = vec![]; 154 | let mut params: Vec> = vec![]; 155 | 156 | for key in keys { 157 | params_template_builder.push("(%p, %p, %p)"); 158 | params.push(Box::new(key.outbound_id)); 159 | params.push(Box::new(key.t.0.to_string())); 160 | params.push(Box::new(key.inbound_id)); 161 | } 162 | 163 | let query_template = format!( 164 | "SELECT id, outbound_id, type, inbound_id, update_timestamp FROM %t WHERE (outbound_id, type, inbound_id) IN ({})", 165 | params_template_builder.join(", ") 166 | ); 167 | sql_query_builder.push(&query_template[..], "edges", params); 168 | } 169 | &EdgeQuery::Pipe { 170 | ref vertex_query, 171 | converter, 172 | ref type_filter, 173 | high_filter, 174 | low_filter, 175 | limit, 176 | } => { 177 | self.vertex_query_to_sql(&*vertex_query, sql_query_builder); 178 | 179 | let mut where_clause_template_builder = vec![]; 180 | let mut params: Vec> = vec![]; 181 | 182 | if let &Some(ref type_filter) = type_filter { 183 | where_clause_template_builder.push("type = %p"); 184 | params.push(Box::new(type_filter.0.to_string())); 185 | } 186 | 187 | if let Some(high_filter) = high_filter { 188 | where_clause_template_builder.push("update_timestamp <= %p"); 189 | params.push(Box::new(high_filter)); 190 | } 191 | 192 | if let Some(low_filter) = low_filter { 193 | where_clause_template_builder.push("update_timestamp >= %p"); 194 | params.push(Box::new(low_filter)); 195 | } 196 | 197 | params.push(Box::new(limit as i64)); 198 | let where_clause = where_clause_template_builder.join(" AND "); 199 | 200 | let query_template = match (converter, where_clause.len()) { 201 | (EdgeDirection::Outbound, 0) => { 202 | "SELECT id, outbound_id, type, inbound_id, update_timestamp FROM edges WHERE outbound_id IN (SELECT id FROM %t) ORDER BY update_timestamp DESC LIMIT %p".to_string() 203 | } 204 | (EdgeDirection::Outbound, _) => { 205 | format!( 206 | "SELECT id, outbound_id, type, inbound_id, update_timestamp FROM edges WHERE outbound_id IN (SELECT id FROM %t) AND {} ORDER BY update_timestamp DESC LIMIT %p", 207 | where_clause 208 | ) 209 | } 210 | (EdgeDirection::Inbound, 0) => { 211 | "SELECT id, outbound_id, type, inbound_id, update_timestamp FROM edges WHERE inbound_id IN (SELECT id FROM %t) ORDER BY update_timestamp DESC LIMIT %p".to_string() 212 | } 213 | (EdgeDirection::Inbound, _) => { 214 | format!( 215 | "SELECT id, outbound_id, type, inbound_id, update_timestamp FROM edges WHERE inbound_id IN (SELECT id FROM %t) AND {} ORDER BY update_timestamp DESC LIMIT %p", 216 | where_clause 217 | ) 218 | } 219 | }; 220 | 221 | sql_query_builder.push(&query_template[..], "", params); 222 | } 223 | } 224 | } 225 | } 226 | 227 | impl Transaction for PostgresTransaction { 228 | fn create_vertex(&self, vertex: &Vertex) -> Result { 229 | // Because this command could fail, we need to set a savepoint to roll 230 | // back to, rather than spoiling the entire transaction 231 | let trans = self.trans.savepoint("create_vertex").chain_err(|| "Could not set savepoint")?; 232 | 233 | let result = self.trans.execute( 234 | "INSERT INTO vertices (id, type) VALUES ($1, $2)", 235 | &[&vertex.id, &vertex.t.0], 236 | ); 237 | 238 | if result.is_err() { 239 | trans.set_rollback(); 240 | Ok(false) 241 | } else { 242 | trans.set_commit(); 243 | Ok(true) 244 | } 245 | } 246 | 247 | fn get_vertices(&self, q: &VertexQuery) -> Result> { 248 | let mut sql_query_builder = CTEQueryBuilder::new(); 249 | self.vertex_query_to_sql(q, &mut sql_query_builder); 250 | let (query, params) = sql_query_builder.into_query_payload("SELECT id, type FROM %t", vec![]); 251 | let params_refs: Vec<&ToSql> = params.iter().map(|x| &**x).collect(); 252 | 253 | let results = self.trans.query(&query[..], ¶ms_refs[..]).chain_err(|| "Could not execute query")?; 254 | let mut vertices: Vec = Vec::new(); 255 | 256 | for row in &results { 257 | let id: Uuid = row.get(0); 258 | let t_str: String = row.get(1); 259 | let v = Vertex::with_id(id, Type::new(t_str).unwrap()); 260 | vertices.push(v); 261 | } 262 | 263 | Ok(vertices) 264 | } 265 | 266 | fn delete_vertices(&self, q: &VertexQuery) -> Result<()> { 267 | let mut sql_query_builder = CTEQueryBuilder::new(); 268 | self.vertex_query_to_sql(q, &mut sql_query_builder); 269 | let (query, params) = sql_query_builder.into_query_payload( 270 | "DELETE FROM vertices WHERE id IN (SELECT id FROM %t)", 271 | vec![], 272 | ); 273 | let params_refs: Vec<&ToSql> = params.iter().map(|x| &**x).collect(); 274 | self.trans.execute(&query[..], ¶ms_refs[..]).chain_err(|| "Could not execute statement")?; 275 | Ok(()) 276 | } 277 | 278 | fn get_vertex_count(&self) -> Result { 279 | let results = self.trans.query("SELECT COUNT(*) FROM vertices", &[]).chain_err(|| "Could not execute query")?; 280 | 281 | for row in &results { 282 | let count: i64 = row.get(0); 283 | return Ok(count as u64); 284 | } 285 | 286 | unreachable!(); 287 | } 288 | 289 | fn create_edge(&self, key: &EdgeKey) -> Result { 290 | let id = generate_uuid_v1(); 291 | 292 | // Because this command could fail, we need to set a savepoint to roll 293 | // back to, rather than spoiling the entire transaction 294 | let trans = self.trans.savepoint("set_edge").chain_err(|| "Could not set savepoint")?; 295 | 296 | let results = trans.query( 297 | " 298 | INSERT INTO edges (id, outbound_id, type, inbound_id, update_timestamp) 299 | VALUES ($1, $2, $3, $4, CLOCK_TIMESTAMP()) 300 | ON CONFLICT ON CONSTRAINT edges_outbound_id_type_inbound_id_ukey 301 | DO UPDATE SET update_timestamp=CLOCK_TIMESTAMP() 302 | ", 303 | &[&id, &key.outbound_id, &key.t.0, &key.inbound_id], 304 | ); 305 | 306 | if results.is_err() { 307 | trans.set_rollback(); 308 | Ok(false) 309 | } else { 310 | trans.set_commit(); 311 | Ok(true) 312 | } 313 | } 314 | 315 | fn get_edges(&self, q: &EdgeQuery) -> Result> { 316 | let mut sql_query_builder = CTEQueryBuilder::new(); 317 | self.edge_query_to_sql(q, &mut sql_query_builder); 318 | let (query, params) = sql_query_builder.into_query_payload( 319 | "SELECT outbound_id, type, inbound_id, update_timestamp FROM %t", 320 | vec![], 321 | ); 322 | let params_refs: Vec<&ToSql> = params.iter().map(|x| &**x).collect(); 323 | 324 | let results = self.trans.query(&query[..], ¶ms_refs[..]).chain_err(|| "Could not execute query")?; 325 | let mut edges: Vec = Vec::new(); 326 | 327 | for row in &results { 328 | let outbound_id: Uuid = row.get(0); 329 | let t_str: String = row.get(1); 330 | let inbound_id: Uuid = row.get(2); 331 | let update_datetime: DateTime = row.get(3); 332 | let t = Type::new(t_str).unwrap(); 333 | let key = EdgeKey::new(outbound_id, t, inbound_id); 334 | let edge = Edge::new(key, update_datetime); 335 | edges.push(edge); 336 | } 337 | 338 | Ok(edges) 339 | } 340 | 341 | fn delete_edges(&self, q: &EdgeQuery) -> Result<()> { 342 | let mut sql_query_builder = CTEQueryBuilder::new(); 343 | self.edge_query_to_sql(q, &mut sql_query_builder); 344 | let (query, params) = 345 | sql_query_builder.into_query_payload("DELETE FROM edges WHERE id IN (SELECT id FROM %t)", vec![]); 346 | let params_refs: Vec<&ToSql> = params.iter().map(|x| &**x).collect(); 347 | self.trans.execute(&query[..], ¶ms_refs[..]).chain_err(|| "Could not execute statement")?; 348 | Ok(()) 349 | } 350 | 351 | fn get_edge_count( 352 | &self, 353 | id: Uuid, 354 | type_filter: Option<&Type>, 355 | direction: EdgeDirection, 356 | ) -> Result { 357 | let results = match (direction, type_filter) { 358 | (EdgeDirection::Outbound, Some(t)) => self.trans.query( 359 | "SELECT COUNT(*) FROM edges WHERE outbound_id=$1 AND type=$2", 360 | &[&id, &t.0], 361 | ), 362 | (EdgeDirection::Outbound, None) => self.trans 363 | .query("SELECT COUNT(*) FROM edges WHERE outbound_id=$1", &[&id]), 364 | (EdgeDirection::Inbound, Some(t)) => self.trans.query( 365 | "SELECT COUNT(*) FROM edges WHERE inbound_id=$1 AND type=$2", 366 | &[&id, &t.0], 367 | ), 368 | (EdgeDirection::Inbound, None) => self.trans 369 | .query("SELECT COUNT(*) FROM edges WHERE inbound_id=$1", &[&id]), 370 | }; 371 | 372 | let results = results.chain_err(|| "Could not execute query")?; 373 | 374 | for row in &results { 375 | let count: i64 = row.get(0); 376 | return Ok(count as u64); 377 | } 378 | 379 | unreachable!(); 380 | } 381 | 382 | fn get_vertex_metadata(&self, q: &VertexQuery, name: &str) -> Result> { 383 | let mut sql_query_builder = CTEQueryBuilder::new(); 384 | self.vertex_query_to_sql(q, &mut sql_query_builder); 385 | let (query, params) = sql_query_builder.into_query_payload( 386 | "SELECT owner_id, value FROM vertex_metadata WHERE owner_id IN (SELECT id FROM %t) AND name=%p", 387 | vec![Box::new(name.to_string())], 388 | ); 389 | let params_refs: Vec<&ToSql> = params.iter().map(|x| &**x).collect(); 390 | let results = self.trans.query(&query[..], ¶ms_refs[..]).chain_err(|| "Could not execute query")?; 391 | let mut metadata = Vec::new(); 392 | 393 | for row in &results { 394 | let id: Uuid = row.get(0); 395 | let value: JsonValue = row.get(1); 396 | metadata.push(VertexMetadata::new(id, value)); 397 | } 398 | 399 | Ok(metadata) 400 | } 401 | 402 | fn set_vertex_metadata(&self, q: &VertexQuery, name: &str, value: &JsonValue) -> Result<()> { 403 | let mut sql_query_builder = CTEQueryBuilder::new(); 404 | self.vertex_query_to_sql(q, &mut sql_query_builder); 405 | let (query, params) = sql_query_builder.into_query_payload( 406 | " 407 | INSERT INTO vertex_metadata (owner_id, name, value) 408 | SELECT id, %p, %p FROM %t 409 | ON CONFLICT ON CONSTRAINT vertex_metadata_pkey 410 | DO UPDATE SET value=%p 411 | ", 412 | vec![ 413 | Box::new(name.to_string()), 414 | Box::new(value.clone()), 415 | Box::new(value.clone()), 416 | ], 417 | ); 418 | let params_refs: Vec<&ToSql> = params.iter().map(|x| &**x).collect(); 419 | self.trans.execute(&query[..], ¶ms_refs[..]).chain_err(|| "Could not execute statement")?; 420 | Ok(()) 421 | } 422 | 423 | fn delete_vertex_metadata(&self, q: &VertexQuery, name: &str) -> Result<()> { 424 | let mut sql_query_builder = CTEQueryBuilder::new(); 425 | self.vertex_query_to_sql(q, &mut sql_query_builder); 426 | let (query, params) = sql_query_builder.into_query_payload( 427 | "DELETE FROM vertex_metadata WHERE owner_id IN (SELECT id FROM %t) AND name=%p", 428 | vec![Box::new(name.to_string())], 429 | ); 430 | let params_refs: Vec<&ToSql> = params.iter().map(|x| &**x).collect(); 431 | self.trans.execute(&query[..], ¶ms_refs[..]).chain_err(|| "Could not execute statement")?; 432 | Ok(()) 433 | } 434 | 435 | fn get_edge_metadata(&self, q: &EdgeQuery, name: &str) -> Result> { 436 | let mut sql_query_builder = CTEQueryBuilder::new(); 437 | self.edge_query_to_sql(q, &mut sql_query_builder); 438 | 439 | let (query, params) = sql_query_builder.into_query_payload( 440 | " 441 | SELECT edges.outbound_id, edges.type, edges.inbound_id, edge_metadata.value 442 | FROM edge_metadata JOIN edges ON edge_metadata.owner_id=edges.id 443 | WHERE owner_id IN (SELECT id FROM %t) AND name=%p 444 | ", 445 | vec![Box::new(name.to_string())], 446 | ); 447 | 448 | let params_refs: Vec<&ToSql> = params.iter().map(|x| &**x).collect(); 449 | let results = self.trans.query(&query[..], ¶ms_refs[..]).chain_err(|| "Could not execute query")?; 450 | let mut metadata = Vec::new(); 451 | 452 | for row in &results { 453 | let outbound_id: Uuid = row.get(0); 454 | let t_str: String = row.get(1); 455 | let inbound_id: Uuid = row.get(2); 456 | let value: JsonValue = row.get(3); 457 | let t = Type::new(t_str).unwrap(); 458 | let key = EdgeKey::new(outbound_id, t, inbound_id); 459 | metadata.push(EdgeMetadata::new(key, value)); 460 | } 461 | 462 | Ok(metadata) 463 | } 464 | 465 | fn set_edge_metadata(&self, q: &EdgeQuery, name: &str, value: &JsonValue) -> Result<()> { 466 | let mut sql_query_builder = CTEQueryBuilder::new(); 467 | self.edge_query_to_sql(q, &mut sql_query_builder); 468 | let (query, params) = sql_query_builder.into_query_payload( 469 | " 470 | INSERT INTO edge_metadata (owner_id, name, value) 471 | SELECT id, %p, %p FROM %t 472 | ON CONFLICT ON CONSTRAINT edge_metadata_pkey 473 | DO UPDATE SET value=%p 474 | ", 475 | vec![ 476 | Box::new(name.to_string()), 477 | Box::new(value.clone()), 478 | Box::new(value.clone()), 479 | ], 480 | ); 481 | let params_refs: Vec<&ToSql> = params.iter().map(|x| &**x).collect(); 482 | self.trans.execute(&query[..], ¶ms_refs[..]).chain_err(|| "Could not execute statement")?; 483 | Ok(()) 484 | } 485 | 486 | fn delete_edge_metadata(&self, q: &EdgeQuery, name: &str) -> Result<()> { 487 | let mut sql_query_builder = CTEQueryBuilder::new(); 488 | self.edge_query_to_sql(q, &mut sql_query_builder); 489 | let (query, params) = sql_query_builder.into_query_payload( 490 | "DELETE FROM edge_metadata WHERE owner_id IN (SELECT id FROM %t) AND name=%p", 491 | vec![Box::new(name.to_string())], 492 | ); 493 | let params_refs: Vec<&ToSql> = params.iter().map(|x| &**x).collect(); 494 | self.trans.execute(&query[..], ¶ms_refs[..]).chain_err(|| "Could not execute statement")?; 495 | Ok(()) 496 | } 497 | } 498 | --------------------------------------------------------------------------------