diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..059ba3d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +version: 2 +updates: + # This is mostly for actions/checkout + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + target-branch: "main" + + # This is actually for cargo crates + - package-ecosystem: cargo + directory: "/" + schedule: + interval: "weekly" + target-branch: "main" \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..211e97c --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,26 @@ +name: Linux Build + Tests + +on: + push: + branches: [ "main" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build-linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build + run: cargo build + - uses: taiki-e/install-action@nextest + - name: Populate test graph + run: pip install falkordb && ./resources/populate_graph.py + - name: Test + run: cargo nextest run --all + services: + falkordb: + image: falkordb/falkordb:edge + ports: + - 6379:6379 \ No newline at end of file diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml new file mode 100644 index 0000000..6c89c39 --- /dev/null +++ b/.github/workflows/codecov.yml @@ -0,0 +1,31 @@ +name: Code Coverage + +on: + push: + branches: [ "main" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + coverage: + runs-on: linux-latest + steps: + - uses: actions/checkout@v4 + - name: Populate test graph + run: pip install falkordb && ./resources/populate_graph.py + - uses: taiki-e/install-action@cargo-llvm-cov + - uses: taiki-e/install-action@nextest + - name: Generate Code Coverage + run: cargo llvm-cov nextest --all --codecov --output-path codecov.json + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: codecov.json + fail_ci_if_error: true + services: + falkordb: + image: falkordb/falkordb:edge + ports: + - 6379:6379 \ No newline at end of file diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml new file mode 100644 index 0000000..f3c21d1 --- /dev/null +++ b/.github/workflows/doc.yml @@ -0,0 +1,16 @@ +name: Documentation + +on: + push: + branches: [ "main" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + doc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Doc + run: cargo doc --all \ No newline at end of file diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml new file mode 100644 index 0000000..8520c82 --- /dev/null +++ b/.github/workflows/pr-checks.yml @@ -0,0 +1,53 @@ +name: Mandatory Pull Request Checks + +on: + pull_request: + branches: [ "main" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + check-clippy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check Clippy + run: cargo clippy --all + + check-deny: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: EmbarkStudios/cargo-deny-action@v1 + + check-doc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Doc + run: cargo doc --all + + check-fmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check Rustfmt + run: cargo fmt --all --check + + build-linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build + run: cargo build + - uses: taiki-e/install-action@nextest + - name: Populate test graph + run: pip install falkordb && ./resources/populate_graph.py + - name: Test + run: cargo nextest run --all + services: + falkordb: + image: falkordb/falkordb:edge + ports: + - 6379:6379 \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..03cdc36 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,83 @@ +name: Publish on crates.io + +on: + push: + tags: [ '*' ] + +env: + CARGO_TERM_COLOR: always + +jobs: + # Ensure formatting + fmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check Rustfmt + run: cargo fmt --all --check + + # Make sure no unwanted licenses or yanked crates have slipped in + deny: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: EmbarkStudios/cargo-deny-action@v1 + + # Make sure the release's build works + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build + run: cargo build + - uses: taiki-e/install-action@nextest + - name: Populate test graph + run: pip install falkordb && ./resources/populate_graph.py + - name: Test + run: cargo nextest run --all + services: + falkordb: + image: falkordb/falkordb:edge + ports: + - 6379:6379 + + # Ensure no clippy warnings + clippy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check Clippy + run: cargo clippy --all + + # Make sure the release's docs are full + doc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Doc + run: cargo doc --all + + # Actually publish to crates.io + crates-io: + needs: + - fmt + - deny + - build + - clippy + - doc + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Cargo Release + run: cargo install cargo-release + - name: Login + run: cargo login ${{ secrets.CRATES_IO_API_TOKEN }} + - name: Publish + run: |- + cargo release \ + publish \ + --all-features \ + --allow-branch HEAD \ + --no-confirm \ + --no-verify \ + --execute \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0408c77..75b6df3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ -target/ +/target .idea/ .vscode/ +.vs/ +/codecov.json +/dump.rdb \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f764631 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,943 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + +[[package]] +name = "cc" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "falkordb" +version = "0.1.0" +dependencies = [ + "log", + "parking_lot", + "redis", + "regex", + "strum", + "thiserror", + "tracing", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "backtrace", + "cfg-if", + "libc", + "petgraph", + "redox_syscall", + "smallvec", + "thread-id", + "windows-targets", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redis" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0d7a6955c7511f60f3ba9e86c6d02b3c3f144f8c24b288d1f4e18074ab8bbec" +dependencies = [ + "combine", + "itoa", + "native-tls", + "percent-encoding", + "rand", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "ryu", + "url", +] + +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + +[[package]] +name = "rustls-webpki" +version = "0.102.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "strum" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "2.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread-id" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0ec81c46e9eb50deaa257be2f148adf052d1fb7701cfd55ccfab2525280b70b" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5ec4d84 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "falkordb" +version = "0.1.0" +edition = "2021" +description = "A FalkorDB Rust client" +license = "SSPL-1.0" + + +[dependencies] +log = { version = "0.4.21", default-features = false, features = ["std"] } +parking_lot = { version = "0.12.3", default-features = false, features = ["deadlock_detection"] } +redis = { version = "0.25.4", default-features = false, optional = true, features = ["sentinel"] } +regex = { version = "1.10.4", default-features = false, features = ["std", "perf", "unicode-bool", "unicode-perl"] } +strum = { version = "0.26.2", default-features = false, features = ["std", "derive"] } +thiserror = "1.0.61" +tracing = { version = "0.1.40", default-features = false, features = ["std", "attributes"] } + +[features] +default = ["redis"] +native-tls = ["redis/tls-native-tls"] +rustls = ["redis/tls-rustls"] +redis = ["dep:redis"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5851192 --- /dev/null +++ b/LICENSE @@ -0,0 +1,655 @@ + Server Side Public License + VERSION 1, OCTOBER 16, 2018 + + Copyright © 2018 MongoDB, Inc. + + Everyone is permitted to copy and distribute verbatim copies of this + license document, but changing it is not allowed. + + TERMS AND CONDITIONS + + 0. Definitions. + + “This License” refers to Server Side Public License. + + “Copyright” also means copyright-like laws that apply to other kinds of + works, such as semiconductor masks. + + “The Program” refers to any copyrightable work licensed under this + License. Each licensee is addressed as “you”. “Licensees” and + “recipients” may be individuals or organizations. + + To “modify” a work means to copy from or adapt all or part of the work in + a fashion requiring copyright permission, other than the making of an + exact copy. The resulting work is called a “modified version” of the + earlier work or a work “based on” the earlier work. + + A “covered work” means either the unmodified Program or a work based on + the Program. + + To “propagate” a work means to do anything with it that, without + permission, would make you directly or secondarily liable for + infringement under applicable copyright law, except executing it on a + computer or modifying a private copy. Propagation includes copying, + distribution (with or without modification), making available to the + public, and in some countries other activities as well. + + To “convey” a work means any kind of propagation that enables other + parties to make or receive copies. Mere interaction with a user through a + computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays “Appropriate Legal Notices” to the + extent that it includes a convenient and prominently visible feature that + (1) displays an appropriate copyright notice, and (2) tells the user that + there is no warranty for the work (except to the extent that warranties + are provided), that licensees may convey the work under this License, and + how to view a copy of this License. If the interface presents a list of + user commands or options, such as a menu, a prominent item in the list + meets this criterion. + + 1. Source Code. + + The “source code” for a work means the preferred form of the work for + making modifications to it. “Object code” means any non-source form of a + work. + + A “Standard Interface” means an interface that either is an official + standard defined by a recognized standards body, or, in the case of + interfaces specified for a particular programming language, one that is + widely used among developers working in that language. The “System + Libraries” of an executable work include anything, other than the work as + a whole, that (a) is included in the normal form of packaging a Major + Component, but which is not part of that Major Component, and (b) serves + only to enable use of the work with that Major Component, or to implement + a Standard Interface for which an implementation is available to the + public in source code form. A “Major Component”, in this context, means a + major essential component (kernel, window system, and so on) of the + specific operating system (if any) on which the executable work runs, or + a compiler used to produce the work, or an object code interpreter used + to run it. + + The “Corresponding Source” for a work in object code form means all the + source code needed to generate, install, and (for an executable work) run + the object code and to modify the work, including scripts to control + those activities. However, it does not include the work's System + Libraries, or general-purpose tools or generally available free programs + which are used unmodified in performing those activities but which are + not part of the work. For example, Corresponding Source includes + interface definition files associated with source files for the work, and + the source code for shared libraries and dynamically linked subprograms + that the work is specifically designed to require, such as by intimate + data communication or control flow between those subprograms and other + parts of the work. + + The Corresponding Source need not include anything that users can + regenerate automatically from other parts of the Corresponding Source. + + The Corresponding Source for a work in source code form is that same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of + copyright on the Program, and are irrevocable provided the stated + conditions are met. This License explicitly affirms your unlimited + permission to run the unmodified Program, subject to section 13. The + output from running a covered work is covered by this License only if the + output, given its content, constitutes a covered work. This License + acknowledges your rights of fair use or other equivalent, as provided by + copyright law. Subject to section 13, you may make, run and propagate + covered works that you do not convey, without conditions so long as your + license otherwise remains in force. You may convey covered works to + others for the sole purpose of having them make modifications exclusively + for you, or provide you with facilities for running those works, provided + that you comply with the terms of this License in conveying all + material for which you do not control copyright. Those thus making or + running the covered works for you must do so exclusively on your + behalf, under your direction and control, on terms that prohibit them + from making any copies of your copyrighted material outside their + relationship with you. + + Conveying under any other circumstances is permitted solely under the + conditions stated below. Sublicensing is not allowed; section 10 makes it + unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological + measure under any applicable law fulfilling obligations under article 11 + of the WIPO copyright treaty adopted on 20 December 1996, or similar laws + prohibiting or restricting circumvention of such measures. + + When you convey a covered work, you waive any legal power to forbid + circumvention of technological measures to the extent such circumvention is + effected by exercising rights under this License with respect to the + covered work, and you disclaim any intention to limit operation or + modification of the work as a means of enforcing, against the work's users, + your or third parties' legal rights to forbid circumvention of + technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you + receive it, in any medium, provided that you conspicuously and + appropriately publish on each copy an appropriate copyright notice; keep + intact all notices stating that this License and any non-permissive terms + added in accord with section 7 apply to the code; keep intact all notices + of the absence of any warranty; and give all recipients a copy of this + License along with the Program. You may charge any price or no price for + each copy that you convey, and you may offer support or warranty + protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to + produce it from the Program, in the form of source code under the terms + of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified it, + and giving a relevant date. + + b) The work must carry prominent notices stating that it is released + under this License and any conditions added under section 7. This + requirement modifies the requirement in section 4 to “keep intact all + notices”. + + c) You must license the entire work, as a whole, under this License to + anyone who comes into possession of a copy. This License will therefore + apply, along with any applicable section 7 additional terms, to the + whole of the work, and all its parts, regardless of how they are + packaged. This License gives no permission to license the work in any + other way, but it does not invalidate such permission if you have + separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your work + need not make them do so. + + A compilation of a covered work with other separate and independent + works, which are not by their nature extensions of the covered work, and + which are not combined with it such as to form a larger program, in or on + a volume of a storage or distribution medium, is called an “aggregate” if + the compilation and its resulting copyright are not used to limit the + access or legal rights of the compilation's users beyond what the + individual works permit. Inclusion of a covered work in an aggregate does + not cause this License to apply to the other parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms of + sections 4 and 5, provided that you also convey the machine-readable + Corresponding Source under the terms of this License, in one of these + ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium customarily + used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a written + offer, valid for at least three years and valid for as long as you + offer spare parts or customer support for that product model, to give + anyone who possesses the object code either (1) a copy of the + Corresponding Source for all the software in the product that is + covered by this License, on a durable physical medium customarily used + for software interchange, for a price no more than your reasonable cost + of physically performing this conveying of source, or (2) access to + copy the Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This alternative is + allowed only occasionally and noncommercially, and only if you received + the object code with such an offer, in accord with subsection 6b. + + d) Convey the object code by offering access from a designated place + (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to copy + the object code is a network server, the Corresponding Source may be on + a different server (operated by you or a third party) that supports + equivalent copying facilities, provided you maintain clear directions + next to the object code saying where to find the Corresponding Source. + Regardless of what server hosts the Corresponding Source, you remain + obligated to ensure that it is available for as long as needed to + satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided you + inform other peers where the object code and Corresponding Source of + the work are being offered to the general public at no charge under + subsection 6d. + + A separable portion of the object code, whose source code is excluded + from the Corresponding Source as a System Library, need not be included + in conveying the object code work. + + A “User Product” is either (1) a “consumer product”, which means any + tangible personal property which is normally used for personal, family, + or household purposes, or (2) anything designed or sold for incorporation + into a dwelling. In determining whether a product is a consumer product, + doubtful cases shall be resolved in favor of coverage. For a particular + product received by a particular user, “normally used” refers to a + typical or common use of that class of product, regardless of the status + of the particular user or of the way in which the particular user + actually uses, or expects or is expected to use, the product. A product + is a consumer product regardless of whether the product has substantial + commercial, industrial or non-consumer uses, unless such uses represent + the only significant mode of use of the product. + + “Installation Information” for a User Product means any methods, + procedures, authorization keys, or other information required to install + and execute modified versions of a covered work in that User Product from + a modified version of its Corresponding Source. The information must + suffice to ensure that the continued functioning of the modified object + code is in no case prevented or interfered with solely because + modification has been made. + + If you convey an object code work under this section in, or with, or + specifically for use in, a User Product, and the conveying occurs as part + of a transaction in which the right of possession and use of the User + Product is transferred to the recipient in perpetuity or for a fixed term + (regardless of how the transaction is characterized), the Corresponding + Source conveyed under this section must be accompanied by the + Installation Information. But this requirement does not apply if neither + you nor any third party retains the ability to install modified object + code on the User Product (for example, the work has been installed in + ROM). + + The requirement to provide Installation Information does not include a + requirement to continue to provide support service, warranty, or updates + for a work that has been modified or installed by the recipient, or for + the User Product in which it has been modified or installed. Access + to a network may be denied when the modification itself materially + and adversely affects the operation of the network or violates the + rules and protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, in + accord with this section must be in a format that is publicly documented + (and with an implementation available to the public in source code form), + and must require no special password or key for unpacking, reading or + copying. + + 7. Additional Terms. + + “Additional permissions” are terms that supplement the terms of this + License by making exceptions from one or more of its conditions. + Additional permissions that are applicable to the entire Program shall be + treated as though they were included in this License, to the extent that + they are valid under applicable law. If additional permissions apply only + to part of the Program, that part may be used separately under those + permissions, but the entire Program remains governed by this License + without regard to the additional permissions. When you convey a copy of + a covered work, you may at your option remove any additional permissions + from that copy, or from any part of it. (Additional permissions may be + written to require their own removal in certain cases when you modify the + work.) You may place additional permissions on material, added by you to + a covered work, for which you have or can give appropriate copyright + permission. + + Notwithstanding any other provision of this License, for material you add + to a covered work, you may (if authorized by the copyright holders of + that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some trade + names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that material + by anyone who conveys the material (or modified versions of it) with + contractual assumptions of liability to the recipient, for any + liability that these contractual assumptions directly impose on those + licensors and authors. + + All other non-permissive additional terms are considered “further + restrictions” within the meaning of section 10. If the Program as you + received it, or any part of it, contains a notice stating that it is + governed by this License along with a term that is a further restriction, + you may remove that term. If a license document contains a further + restriction but permits relicensing or conveying under this License, you + may add to a covered work material governed by the terms of that license + document, provided that the further restriction does not survive such + relicensing or conveying. + + If you add terms to a covered work in accord with this section, you must + place, in the relevant source files, a statement of the additional terms + that apply to those files, or a notice indicating where to find the + applicable terms. Additional terms, permissive or non-permissive, may be + stated in the form of a separately written license, or stated as + exceptions; the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly + provided under this License. Any attempt otherwise to propagate or modify + it is void, and will automatically terminate your rights under this + License (including any patent licenses granted under the third paragraph + of section 11). + + However, if you cease all violation of this License, then your license + from a particular copyright holder is reinstated (a) provisionally, + unless and until the copyright holder explicitly and finally terminates + your license, and (b) permanently, if the copyright holder fails to + notify you of the violation by some reasonable means prior to 60 days + after the cessation. + + Moreover, your license from a particular copyright holder is reinstated + permanently if the copyright holder notifies you of the violation by some + reasonable means, this is the first time you have received notice of + violation of this License (for any work) from that copyright holder, and + you cure the violation prior to 30 days after your receipt of the notice. + + Termination of your rights under this section does not terminate the + licenses of parties who have received copies or rights from you under + this License. If your rights have been terminated and not permanently + reinstated, you do not qualify to receive new licenses for the same + material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or run a + copy of the Program. Ancillary propagation of a covered work occurring + solely as a consequence of using peer-to-peer transmission to receive a + copy likewise does not require acceptance. However, nothing other than + this License grants you permission to propagate or modify any covered + work. These actions infringe copyright if you do not accept this License. + Therefore, by modifying or propagating a covered work, you indicate your + acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically receives + a license from the original licensors, to run, modify and propagate that + work, subject to this License. You are not responsible for enforcing + compliance by third parties with this License. + + An “entity transaction” is a transaction transferring control of an + organization, or substantially all assets of one, or subdividing an + organization, or merging organizations. If propagation of a covered work + results from an entity transaction, each party to that transaction who + receives a copy of the work also receives whatever licenses to the work + the party's predecessor in interest had or could give under the previous + paragraph, plus a right to possession of the Corresponding Source of the + work from the predecessor in interest, if the predecessor has it or can + get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the rights + granted or affirmed under this License. For example, you may not impose a + license fee, royalty, or other charge for exercise of rights granted + under this License, and you may not initiate litigation (including a + cross-claim or counterclaim in a lawsuit) alleging that any patent claim + is infringed by making, using, selling, offering for sale, or importing + the Program or any portion of it. + + 11. Patents. + + A “contributor” is a copyright holder who authorizes use under this + License of the Program or a work on which the Program is based. The work + thus licensed is called the contributor's “contributor version”. + + A contributor's “essential patent claims” are all patent claims owned or + controlled by the contributor, whether already acquired or hereafter + acquired, that would be infringed by some manner, permitted by this + License, of making, using, or selling its contributor version, but do not + include claims that would be infringed only as a consequence of further + modification of the contributor version. For purposes of this definition, + “control” includes the right to grant patent sublicenses in a manner + consistent with the requirements of this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free + patent license under the contributor's essential patent claims, to make, + use, sell, offer for sale, import and otherwise run, modify and propagate + the contents of its contributor version. + + In the following three paragraphs, a “patent license” is any express + agreement or commitment, however denominated, not to enforce a patent + (such as an express permission to practice a patent or covenant not to + sue for patent infringement). To “grant” such a patent license to a party + means to make such an agreement or commitment not to enforce a patent + against the party. + + If you convey a covered work, knowingly relying on a patent license, and + the Corresponding Source of the work is not available for anyone to copy, + free of charge and under the terms of this License, through a publicly + available network server or other readily accessible means, then you must + either (1) cause the Corresponding Source to be so available, or (2) + arrange to deprive yourself of the benefit of the patent license for this + particular work, or (3) arrange, in a manner consistent with the + requirements of this License, to extend the patent license to downstream + recipients. “Knowingly relying” means you have actual knowledge that, but + for the patent license, your conveying the covered work in a country, or + your recipient's use of the covered work in a country, would infringe + one or more identifiable patents in that country that you have reason + to believe are valid. + + If, pursuant to or in connection with a single transaction or + arrangement, you convey, or propagate by procuring conveyance of, a + covered work, and grant a patent license to some of the parties receiving + the covered work authorizing them to use, propagate, modify or convey a + specific copy of the covered work, then the patent license you grant is + automatically extended to all recipients of the covered work and works + based on it. + + A patent license is “discriminatory” if it does not include within the + scope of its coverage, prohibits the exercise of, or is conditioned on + the non-exercise of one or more of the rights that are specifically + granted under this License. You may not convey a covered work if you are + a party to an arrangement with a third party that is in the business of + distributing software, under which you make payment to the third party + based on the extent of your activity of conveying the work, and under + which the third party grants, to any of the parties who would receive the + covered work from you, a discriminatory patent license (a) in connection + with copies of the covered work conveyed by you (or copies made from + those copies), or (b) primarily for and in connection with specific + products or compilations that contain the covered work, unless you + entered into that arrangement, or that patent license was granted, prior + to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting any + implied license or other defenses to infringement that may otherwise be + available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot use, + propagate or convey a covered work so as to satisfy simultaneously your + obligations under this License and any other pertinent obligations, then + as a consequence you may not use, propagate or convey it at all. For + example, if you agree to terms that obligate you to collect a royalty for + further conveying from those to whom you convey the Program, the only way + you could satisfy both those terms and this License would be to refrain + entirely from conveying the Program. + + 13. Offering the Program as a Service. + + If you make the functionality of the Program or a modified version + available to third parties as a service, you must make the Service Source + Code available via network download to everyone at no charge, under the + terms of this License. Making the functionality of the Program or + modified version available to third parties as a service includes, + without limitation, enabling third parties to interact with the + functionality of the Program or modified version remotely through a + computer network, offering a service the value of which entirely or + primarily derives from the value of the Program or modified version, or + offering a service that accomplishes for users the primary purpose of the + Program or modified version. + + “Service Source Code” means the Corresponding Source for the Program or + the modified version, and the Corresponding Source for all programs that + you use to make the Program or modified version available as a service, + including, without limitation, management software, user interfaces, + application program interfaces, automation software, monitoring software, + backup software, storage software and hosting software, all such that a + user could run an instance of the service using the Service Source Code + you make available. + + 14. Revised Versions of this License. + + MongoDB, Inc. may publish revised and/or new versions of the Server Side + Public License from time to time. Such new versions will be similar in + spirit to the present version, but may differ in detail to address new + problems or concerns. + + Each version is given a distinguishing version number. If the Program + specifies that a certain numbered version of the Server Side Public + License “or any later version” applies to it, you have the option of + following the terms and conditions either of that numbered version or of + any later version published by MongoDB, Inc. If the Program does not + specify a version number of the Server Side Public License, you may + choose any version ever published by MongoDB, Inc. + + If the Program specifies that a proxy can decide which future versions of + the Server Side Public License can be used, that proxy's public statement + of acceptance of a version permanently authorizes you to choose that + version for the Program. + + Later license versions may give you additional or different permissions. + However, no additional obligations are imposed on any author or copyright + holder as a result of your choosing to follow a later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY + APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT + HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY + OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM + IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF + ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING + WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS + THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING + ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF + THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO + LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU + OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER + PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE + POSSIBILITY OF SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided above + cannot be given local legal effect according to their terms, reviewing + courts shall apply local law that most closely approximates an absolute + waiver of all civil liability in connection with the Program, unless a + warranty or assumption of liability accompanies a copy of the Program in + return for a fee. + + END OF TERMS AND CONDITIONS + + + + + +Elastic License 2.0 + +URL: https://www.elastic.co/licensing/elastic-license + +## Acceptance + +By using the software, you agree to all of the terms and conditions below. + +## Copyright License + +The licensor grants you a non-exclusive, royalty-free, worldwide, +non-sublicensable, non-transferable license to use, copy, distribute, make +available, and prepare derivative works of the software, in each case subject to +the limitations and conditions below. + +## Limitations + +You may not provide the software to third parties as a hosted or managed +service, where the service provides users with access to any substantial set of +the features or functionality of the software. + +You may not move, change, disable, or circumvent the license key functionality +in the software, and you may not remove or obscure any functionality in the +software that is protected by the license key. + +You may not alter, remove, or obscure any licensing, copyright, or other notices +of the licensor in the software. Any use of the licensor’s trademarks is subject +to applicable law. + +## Patents + +The licensor grants you a license, under any patent claims the licensor can +license, or becomes able to license, to make, have made, use, sell, offer for +sale, import and have imported the software, in each case subject to the +limitations and conditions in this license. This license does not cover any +patent claims that you cause to be infringed by modifications or additions to +the software. If you or your company make any written claim that the software +infringes or contributes to infringement of any patent, your patent license for +the software granted under these terms ends immediately. If your company makes +such a claim, your patent license ends immediately for work on behalf of your +company. + +## Notices + +You must ensure that anyone who gets a copy of any part of the software from you +also gets a copy of these terms. + +If you modify the software, you must include in any modified copies of the +software prominent notices stating that you have modified the software. + +## No Other Rights + +These terms do not imply any licenses other than those expressly granted in +these terms. + +## Termination + +If you use the software in violation of these terms, such use is not licensed, +and your licenses will automatically terminate. If the licensor provides you +with a notice of your violation, and you cease all violation of this license no +later than 30 days after you receive that notice, your licenses will be +reinstated retroactively. However, if you violate these terms after such +reinstatement, any additional violation of these terms will cause your licenses +to terminate automatically and permanently. + +## No Liability + +*As far as the law allows, the software comes as is, without any warranty or +condition, and the licensor will not be liable to you for any damages arising +out of these terms or the use or nature of the software, under any kind of +legal claim.* + +## Definitions + +The **licensor** is the entity offering these terms, and the **software** is the +software the licensor makes available under these terms, including any portion +of it. + +**you** refers to the individual or entity agreeing to these terms. + +**your company** is any legal entity, sole proprietorship, or other kind of +organization that you work for, plus all organizations that have control over, +are under the control of, or are under common control with that +organization. **control** means ownership of substantially all the assets of an +entity, or the power to direct its management and policies by vote, contract, or +otherwise. Control can be direct or indirect. + +**your licenses** are all the licenses granted to you for the software under +these terms. + +**use** means anything you do with the software requiring one of your licenses. + +**trademark** means trademarks, service marks, and similar rights. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e8e5973 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +[![license](https://img.shields.io/github/license/falkordb/falkordb-rs.svg)](https://github.com/falkordb/falkordb-rs) +[![Release](https://img.shields.io/github/release/falkordb/falkordb-rs.svg)](https://github.com/falkordb/falkordb-rs/releases/latest) +[![Codecov](https://codecov.io/gh/falkordb/falkordb-rs/branch/main/graph/badge.svg)](https://codecov.io/gh/falkordb/falkordb-rs) +[![Docs](https://img.shields.io/docsrs/:crate)](https://docs.rs/crate/falkordb/0.1.0)\ +[![Forum](https://img.shields.io/badge/Forum-falkordb-blue)](https://github.com/orgs/FalkorDB/discussions) +[![Discord](https://img.shields.io/discord/1146782921294884966?style=flat-square)](https://discord.gg/ErBEqN9E) + +# falkordb-client-rs + +[![Try Free](https://img.shields.io/badge/Try%20Free-FalkorDB%20Cloud-FF8101?labelColor=FDE900&style=for-the-badge&link=https://app.falkordb.cloud)](https://app.falkordb.cloud) + +FalkorDB Rust client + +## Usage + +### Installation + +Just add it to your `Cargo.toml`, like so + +```toml +falkordb = { version = "0.1.0" } +``` + +### Run FalkorDB instance + +Docker: + +```sh +docker run --rm -p 6379:6379 falkordb/falkordb +``` + +### Code Example + +```rust +use falkordb::FalkorClientBuilder; + +// Connect to FalkorDB +let client = FalkorClientBuilder::new().with_connection_info("falkor://127.0.0.1:6379".try_into().expect("Failed constructing connection info")).build().expect("Failed to build client"); + +// Select the social graph +let mut graph = client.select_graph("social"); + +// Create 100 nodes and return a handful +let nodes = graph.query("UNWIND range(0, 100) AS i CREATE (n { v:1 }) RETURN n LIMIT 10").with_timeout(5000).execute().expect("Failed performing query").data; +for n in nodes { +println!("{:?}", n[0]); +} +``` diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..2e966cc --- /dev/null +++ b/deny.toml @@ -0,0 +1,40 @@ +[advisories] + +[bans] +multiple-versions = "deny" +skip = ["windows_x86_64_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", + "windows_i686_msvc", "windows_i686_gnu", "windows_i686_gnullvm", + "windows_aarch64_msvc", "windows_aarch64_gnullvm", + "windows-targets"] # Windows crates are all locked in `mio`, but should be fine + +[sources] +unknown-registry = "deny" +unknown-git = "deny" + +[licenses] +exceptions = [ + { name = "unicode-ident", allow = ["Unicode-DFS-2016"] }, # unique license + { name = "ring", allow = ["LicenseRef-ring"] } # ring uses a specific BoringSSL license that does not match the standard text so requires allowing the specific hash +] +unused-allowed-license = "allow" +confidence-threshold = 0.93 +allow = [ + "SSPL-1.0", + "Apache-2.0 WITH LLVM-exception", + "Apache-2.0", + "BSD-2-Clause", + "BSD-3-Clause", + "MIT", + "GPL-3.0", + "LGPL-3.0", + "AGPL-3.0", + "ISC" +] + + +[[licenses.clarify]] +name = "ring" +expression = "LicenseRef-ring" +license-files = [ + { path = "LICENSE", hash = 0xbd0eed23 }, +] diff --git a/resources/actors.csv b/resources/actors.csv new file mode 100644 index 0000000..56a42ef --- /dev/null +++ b/resources/actors.csv @@ -0,0 +1,1862 @@ +Chris Pratt,1979,Guardians of the Galaxy +Zoe Saldana,1978,Guardians of the Galaxy +Dave Bautista,1969,Guardians of the Galaxy +Vin Diesel,1967,Guardians of the Galaxy +Bradley Cooper,1975,Guardians of the Galaxy +Lee Pace,1979,Guardians of the Galaxy +Michael Rooker,1955,Guardians of the Galaxy +Karen Gillan,1987,Guardians of the Galaxy +Djimon Hounsou,1964,Guardians of the Galaxy +Ellen Burstyn,1932,Interstellar +Matthew McConaughey,1969,Interstellar +Mackenzie Foy,2000,Interstellar +John Lithgow,1945,Interstellar +David Oyelowo,1976,Interstellar +Collette Wolfe,1980,Interstellar +Francis X. McCarthy,1942,Interstellar +Bill Irwin,1950,Interstellar +Ralph Fiennes,1962,The Grand Budapest Hotel +F. Murray Abraham,1939,The Grand Budapest Hotel +Mathieu Amalric,1965,The Grand Budapest Hotel +Adrien Brody,1973,The Grand Budapest Hotel +Willem Dafoe,1955,The Grand Budapest Hotel +Jeff Goldblum,1952,The Grand Budapest Hotel +Harvey Keitel,1939,The Grand Budapest Hotel +Jude Law,1972,The Grand Budapest Hotel +Bill Murray,1950,The Grand Budapest Hotel +Ben Affleck,1972,Gone Girl +Rosamund Pike,1979,Gone Girl +Neil Patrick Harris,1973,Gone Girl +Tyler Perry,1969,Gone Girl +Carrie Coon,1981,Gone Girl +Kim Dickens,1965,Gone Girl +Patrick Fugit,1982,Gone Girl +David Clennon,1943,Gone Girl +Lisa Banes,1955,Gone Girl +Benedict Cumberbatch,1976,The Imitation Game +Keira Knightley,1985,The Imitation Game +Matthew Goode,1978,The Imitation Game +Rory Kinnear,1978,The Imitation Game +Allen Leech,1981,The Imitation Game +Matthew Beard,1989,The Imitation Game +Charles Dance,1946,The Imitation Game +Mark Strong,1963,The Imitation Game +Keanu Reeves,1964,John Wick +Michael Nyqvist,1960,John Wick +Alfie Allen,1986,John Wick +Willem Dafoe,1955,John Wick +Dean Winters,1964,John Wick +Adrianne Palicki,1983,John Wick +Omer Barnea,1980,John Wick +Daniel Bernhardt,1965,John Wick +Hugh Jackman,1968,X-Men: Days of Future Past +James McAvoy,1979,X-Men: Days of Future Past +Michael Fassbender,1977,X-Men: Days of Future Past +Jennifer Lawrence,1990,X-Men: Days of Future Past +Halle Berry,1966,X-Men: Days of Future Past +Nicholas Hoult,1989,X-Men: Days of Future Past +Anna Paquin,1982,X-Men: Days of Future Past +Ellen Page,1987,X-Men: Days of Future Past +Peter Dinklage,1969,X-Men: Days of Future Past +Elizabeth Reaser,1975,Ouija: Origin of Evil +Henry Thomas,1971,Ouija: Origin of Evil +Doug Jones,1960,Ouija: Origin of Evil +Kate Siegel,1982,Ouija: Origin of Evil +Denzel Washington,1954,The Equalizer +Marton Csokas,1966,The Equalizer +David Harbour,1975,The Equalizer +Haley Bennett,1988,The Equalizer +Bill Pullman,1953,The Equalizer +Melissa Leo,1960,The Equalizer +David Meunier,1973,The Equalizer +Angelina Jolie,1975,Maleficent +Elle Fanning,1998,Maleficent +Sharlto Copley,1973,Maleficent +Lesley Manville,1956,Maleficent +Imelda Staunton,1956,Maleficent +Juno Temple,1989,Maleficent +Sam Riley,1980,Maleficent +Brenton Thwaites,1989,Maleficent +Kenneth Cranham,1944,Maleficent +Miles Teller,1987,Whiplash +J.K. Simmons,1955,Whiplash +Paul Reiser,1957,Whiplash +Melissa Benoist,1988,Whiplash +Chris Mulkey,1948,Whiplash +Brad Pitt,1963,Fury +Shia LaBeouf,1986,Fury +Logan Lerman,1992,Fury +Jon Bernthal,1976,Fury +Jim Parrack,1981,Fury +Brad William Henke,1966,Fury +Xavier Samuel,1983,Fury +Colin Firth,1960,Kingsman: The Secret Service +Mark Strong,1963,Kingsman: The Secret Service +Jonno Davies,1992,Kingsman: The Secret Service +Jack Davenport,1973,Kingsman: The Secret Service +Samantha Womack,1972,Kingsman: The Secret Service +Mark Hamill,1951,Kingsman: The Secret Service +Velibor Topic,1970,Kingsman: The Secret Service +Keir Gilchrist,1992,It Follows +Maika Monroe,1993,It Follows +Lili Sepe,1997,It Follows +Olivia Luccardi,1989,It Follows +Tom Cruise,1962,Edge of Tomorrow +Emily Blunt,1983,Edge of Tomorrow +Brendan Gleeson,1955,Edge of Tomorrow +Bill Paxton,1955,Edge of Tomorrow +Jonas Armstrong,1981,Edge of Tomorrow +Tony Way,1978,Edge of Tomorrow +Kick Gurry,1978,Edge of Tomorrow +Franz Drameh,1993,Edge of Tomorrow +Eddie Redmayne,1982,The Theory of Everything +Felicity Jones,1983,The Theory of Everything +Harry Lloyd,1983,The Theory of Everything +Alice Orr-Ewing,1989,The Theory of Everything +David Thewlis,1963,The Theory of Everything +Thomas Morrison,1983,The Theory of Everything +Essie Davis,1970,The Babadook +Hayley McElhinney,1974,The Babadook +Daniel Henshall,1982,The Babadook +Shailene Woodley,1991,Divergent +Theo James,1984,Divergent +Ashley Judd,1968,Divergent +Jai Courtney,1986,Divergent +Ray Stevenson,1964,Divergent +Miles Teller,1987,Divergent +Tony Goldwyn,1960,Divergent +Ansel Elgort,1994,Divergent +Mickey Rourke,1952,Sin City: A Dame to Kill For +Jessica Alba,1981,Sin City: A Dame to Kill For +Josh Brolin,1968,Sin City: A Dame to Kill For +Joseph Gordon-Levitt,1981,Sin City: A Dame to Kill For +Rosario Dawson,1979,Sin City: A Dame to Kill For +Bruce Willis,1955,Sin City: A Dame to Kill For +Eva Green,1980,Sin City: A Dame to Kill For +Powers Boothe,1948,Sin City: A Dame to Kill For +Dennis Haysbert,1954,Sin City: A Dame to Kill For +Aaron Paul,1979,Need for Speed +Dominic Cooper,1978,Need for Speed +Imogen Poots,1989,Need for Speed +Scott Mescudi,1984,Need for Speed +Rami Malek,1981,Need for Speed +Ramon Rodriguez,1979,Need for Speed +Dakota Johnson,1989,Need for Speed +Stevie Ray Dallimore,1967,Need for Speed +Andrew Garfield,1983,The Amazing Spider-Man 2 +Emma Stone,1988,The Amazing Spider-Man 2 +Jamie Foxx,1967,The Amazing Spider-Man 2 +Dane DeHaan,1986,The Amazing Spider-Man 2 +Colm Feore,1958,The Amazing Spider-Man 2 +Felicity Jones,1983,The Amazing Spider-Man 2 +Paul Giamatti,1967,The Amazing Spider-Man 2 +Sally Field,1946,The Amazing Spider-Man 2 +Embeth Davidtz,1965,The Amazing Spider-Man 2 +Luke Evans,1979,Dracula Untold +Sarah Gadon,1987,Dracula Untold +Dominic Cooper,1978,Dracula Untold +Art Parkinson,2001,Dracula Untold +Charles Dance,1946,Dracula Untold +Paul Kaye,1965,Dracula Untold +William Houston,1968,Dracula Untold +Noah Huntley,1974,Dracula Untold +Diego Luna,1979,The Book of Life +Zoe Saldana,1978,The Book of Life +Channing Tatum,1980,The Book of Life +Ron Perlman,1950,The Book of Life +Christina Applegate,1971,The Book of Life +Ice Cube,1969,The Book of Life +Kate del Castillo,1972,The Book of Life +Hector Elizondo,1936,The Book of Life +Danny Trejo,1944,The Book of Life +Frank Grillo,1965,The Purge: Anarchy +Carmen Ejogo,1973,The Purge: Anarchy +Zach Gilford,1982,The Purge: Anarchy +Kiele Sanchez,1977,The Purge: Anarchy +Justina Machado,1972,The Purge: Anarchy +John Beasley,1943,The Purge: Anarchy +Noel Gugliemi,1970,The Purge: Anarchy +Chris Evans,1981,Captain America: The Winter Soldier +Samuel L. Jackson,1948,Captain America: The Winter Soldier +Scarlett Johansson,1984,Captain America: The Winter Soldier +Robert Redford,1936,Captain America: The Winter Soldier +Sebastian Stan,1982,Captain America: The Winter Soldier +Anthony Mackie,1978,Captain America: The Winter Soldier +Cobie Smulders,1982,Captain America: The Winter Soldier +Frank Grillo,1965,Captain America: The Winter Soldier +Mark Wahlberg,1971,Transformers: Age of Extinction +Stanley Tucci,1960,Transformers: Age of Extinction +Kelsey Grammer,1955,Transformers: Age of Extinction +Nicola Peltz,1995,Transformers: Age of Extinction +Jack Reynor,1992,Transformers: Age of Extinction +Titus Welliver,1961,Transformers: Age of Extinction +Sophia Myles,1980,Transformers: Age of Extinction +Bingbing Li,1973,Transformers: Age of Extinction +T.J. Miller,1981,Transformers: Age of Extinction +Megan Fox,1986,Teenage Mutant Ninja Turtles +Will Arnett,1970,Teenage Mutant Ninja Turtles +William Fichtner,1956,Teenage Mutant Ninja Turtles +Alan Ritchson,1984,Teenage Mutant Ninja Turtles +Noel Fisher,1984,Teenage Mutant Ninja Turtles +Pete Ploszek,1987,Teenage Mutant Ninja Turtles +Johnny Knoxville,1971,Teenage Mutant Ninja Turtles +Jeremy Howard,1981,Teenage Mutant Ninja Turtles +Danny Woodburn,1964,Teenage Mutant Ninja Turtles +Tom Ellis,1978,Lucifer +Lauren German,1978,Lucifer +Kevin Alejandro,1976,Lucifer +D.B. Woodside,1969,Lucifer +Rachael Harris,1968,Lucifer +Tricia Helfer,1974,Lucifer +Aimee Garcia,1978,Lucifer +Ian McKellen,1939,The Hobbit: The Battle of the Five Armies +Martin Freeman,1971,The Hobbit: The Battle of the Five Armies +Richard Armitage,1971,The Hobbit: The Battle of the Five Armies +Ken Stott,1955,The Hobbit: The Battle of the Five Armies +Graham McTavish,1961,The Hobbit: The Battle of the Five Armies +James Nesbitt,1965,The Hobbit: The Battle of the Five Armies +Dean O'Gorman,1976,The Hobbit: The Battle of the Five Armies +Laura Allen,1974,Clown +Peter Stormare,1953,Clown +Christian Distefano,2005,Clown +Elizabeth Whitmere,1981,Clown +Michael Keaton,1951,Birdman or (The Unexpected Virtue of Ignorance) +Emma Stone,1988,Birdman or (The Unexpected Virtue of Ignorance) +Zach Galifianakis,1969,Birdman or (The Unexpected Virtue of Ignorance) +Naomi Watts,1968,Birdman or (The Unexpected Virtue of Ignorance) +Andrea Riseborough,1981,Birdman or (The Unexpected Virtue of Ignorance) +Bruce Campbell,1958,The Evil Dead +Ellen Sandweiss,1958,The Evil Dead +Betsy Baker,1955,The Evil Dead +Theresa Tilly,1953,The Evil Dead +Jeff Bridges,1949,The Giver +Meryl Streep,1949,The Giver +Brenton Thwaites,1989,The Giver +Katie Holmes,1978,The Giver +Odeya Rush,1997,The Giver +Cameron Monaghan,1993,The Giver +Taylor Swift,1989,The Giver +Jemaine Clement,1974,What We Do in the Shadows +Taika Waititi,1975,What We Do in the Shadows +Hiroki Hasegawa,1977,Shin Gojira +Yutaka Takenouchi,1971,Shin Gojira +Satomi Ishihara,1986,Shin Gojira +Ren Ohsugi,1951,Shin Gojira +Akira Emoto,1948,Shin Gojira +Mikako Ichikawa,1978,Shin Gojira +Jun Kunimura,1955,Shin Gojira +Pierre Taki,1967,Shin Gojira +Dylan O'Brien,1991,The Maze Runner +Aml Ameen,1985,The Maze Runner +Thomas Brodie-Sangster,1990,The Maze Runner +Will Poulter,1993,The Maze Runner +Kaya Scodelario,1992,The Maze Runner +Chris Sheffield,1988,The Maze Runner +Jake Gyllenhaal,1980,Nightcrawler +Michael Papajohn,1964,Nightcrawler +Bill Paxton,1955,Nightcrawler +James Huang,1977,Nightcrawler +Kent Shocknek,1956,Nightcrawler +Sharon Tay,1966,Nightcrawler +Shailene Woodley,1991,The Fault in Our Stars +Ansel Elgort,1994,The Fault in Our Stars +Nat Wolff,1994,The Fault in Our Stars +Laura Dern,1967,The Fault in Our Stars +Sam Trammell,1969,The Fault in Our Stars +Willem Dafoe,1955,The Fault in Our Stars +Lotte Verbeek,1982,The Fault in Our Stars +Randy Kovitz,1955,The Fault in Our Stars +Dwayne Johnson,1972,Hercules +Ian McShane,1942,Hercules +John Hurt,1940,Hercules +Rufus Sewell,1967,Hercules +Aksel Hennie,1975,Hercules +Reece Ritchie,1986,Hercules +Joseph Fiennes,1970,Hercules +Tobias Santelmann,1980,Hercules +Seth Rogen,1982,Neighbors +Rose Byrne,1979,Neighbors +Brian Huskey,1968,Neighbors +Ike Barinholtz,1977,Neighbors +Carla Gallo,1975,Neighbors +Zac Efron,1987,Neighbors +Dave Franco,1985,Neighbors +Sylvester Stallone,1946,The Expendables 3 +Jason Statham,1967,The Expendables 3 +Harrison Ford,1942,The Expendables 3 +Arnold Schwarzenegger,1947,The Expendables 3 +Mel Gibson,1956,The Expendables 3 +Wesley Snipes,1962,The Expendables 3 +Dolph Lundgren,1957,The Expendables 3 +Randy Couture,1963,The Expendables 3 +Terry Crews,1968,The Expendables 3 +Will Arnett,1970,The Lego Movie +Elizabeth Banks,1974,The Lego Movie +Alison Brie,1982,The Lego Movie +Anthony Daniels,1946,The Lego Movie +Charlie Day,1976,The Lego Movie +Keith Ferguson,1972,The Lego Movie +Lily Collins,1989,"Love, Rosie" +Sam Claflin,1986,"Love, Rosie" +Christian Cooke,1987,"Love, Rosie" +Jaime Winstone,1985,"Love, Rosie" +Suki Waterhouse,1992,"Love, Rosie" +Tamsin Egerton,1988,"Love, Rosie" +Jamie Beamish,1976,"Love, Rosie" +Lorcan Cranitch,1959,"Love, Rosie" +Ellar Coltrane,1994,Boyhood +Patricia Arquette,1968,Boyhood +Lorelei Linklater,1994,Boyhood +Ethan Hawke,1970,Boyhood +Bradley Cooper,1975,American Sniper +Kyle Gallner,1986,American Sniper +Ben Reed,1965,American Sniper +Keir O'Donnell,1978,American Sniper +Scott Adsit,1965,Big Hero 6 +Daniel Henney,1979,Big Hero 6 +T.J. Miller,1981,Big Hero 6 +Jamie Chung,1983,Big Hero 6 +Damon Wayans Jr.,1982,Big Hero 6 +Genesis Rodriguez,1987,Big Hero 6 +James Cromwell,1940,Big Hero 6 +Alan Tudyk,1971,Big Hero 6 +Jennifer Lawrence,1990,The Hunger Games: Mockingjay - Part 1 +Josh Hutcherson,1992,The Hunger Games: Mockingjay - Part 1 +Liam Hemsworth,1990,The Hunger Games: Mockingjay - Part 1 +Woody Harrelson,1961,The Hunger Games: Mockingjay - Part 1 +Donald Sutherland,1935,The Hunger Games: Mockingjay - Part 1 +Philip Seymour Hoffman,1967,The Hunger Games: Mockingjay - Part 1 +Julianne Moore,1960,The Hunger Games: Mockingjay - Part 1 +Willow Shields,2000,The Hunger Games: Mockingjay - Part 1 +Sam Claflin,1986,The Hunger Games: Mockingjay - Part 1 +Russell Crowe,1964,Noah +Jennifer Connelly,1970,Noah +Ray Winstone,1957,Noah +Anthony Hopkins,1937,Noah +Emma Watson,1990,Noah +Logan Lerman,1992,Noah +Douglas Booth,1992,Noah +Nick Nolte,1941,Noah +Mark Margolis,1939,Noah +Ethan Hawke,1970,Predestination +Jack O'Connell,1990,Unbroken +Domhnall Gleeson,1983,Unbroken +Garrett Hedlund,1984,Unbroken +Finn Wittrock,1984,Unbroken +Jai Courtney,1986,Unbroken +Vincenzo Amato,1966,Unbroken +Amy Adams,1974,Big Eyes +Christoph Waltz,1956,Big Eyes +Danny Huston,1962,Big Eyes +Krysten Ritter,1981,Big Eyes +Jason Schwartzman,1980,Big Eyes +Terence Stamp,1938,Big Eyes +Jon Polito,1950,Big Eyes +Madeleine Arthur,1997,Big Eyes +Michael Pitt,1981,I Origins +Steven Yeun,1983,I Origins +Brit Marling,1982,I Origins +Charles Woods Gray,1949,I Origins +Joanna Newsom,1982,Inherent Vice +Katherine Waterston,1980,Inherent Vice +Joaquin Phoenix,1974,Inherent Vice +Jeannie Berlin,1949,Inherent Vice +Josh Brolin,1968,Inherent Vice +Eric Roberts,1956,Inherent Vice +Serena Scott Thomas,1961,Inherent Vice +Rita Cortese,1949,Relatos salvajes +Julieta Zylberberg,1983,Relatos salvajes +Leonardo Sbaraglia,1970,Relatos salvajes +Jill Larson,1947,The Taking +Anne Ramsay,1960,The Taking +Michelle Ang,1983,The Taking +Brett Gentile,1973,The Taking +Ryan Cutrona,1949,The Taking +Anne Bedian,1972,The Taking +Sullivan Stapleton,1977,300: Rise of an Empire +Eva Green,1980,300: Rise of an Empire +Lena Headey,1973,300: Rise of an Empire +Hans Matheson,1975,300: Rise of an Empire +Callan Mulvey,1975,300: Rise of an Empire +David Wenham,1965,300: Rise of an Empire +Rodrigo Santoro,1975,300: Rise of an Empire +Jack O'Connell,1990,300: Rise of an Empire +Andrew Tiernan,1965,300: Rise of an Empire +Jonah Hill,1983,22 Jump Street +Channing Tatum,1980,22 Jump Street +Peter Stormare,1953,22 Jump Street +Wyatt Russell,1986,22 Jump Street +Amber Stevens West,1986,22 Jump Street +Ice Cube,1969,22 Jump Street +Michelle Monaghan,1976,The Best of Me +James Marsden,1973,The Best of Me +Liana Liberato,1995,The Best of Me +Gerald McRaney,1947,The Best of Me +Caroline Goodall,1959,The Best of Me +Clarke Peters,1952,The Best of Me +Sebastian Arcelus,1976,The Best of Me +Jon Tenney,1961,The Best of Me +Zoey Deutch,1994,Vampire Academy +Danila Kozlovsky,1985,Vampire Academy +Gabriel Byrne,1950,Vampire Academy +Olga Kurylenko,1979,Vampire Academy +Sarah Hyland,1990,Vampire Academy +Cameron Monaghan,1993,Vampire Academy +Sami Gayle,1996,Vampire Academy +Jeff Bridges,1949,Seventh Son +Ben Barnes,1981,Seventh Son +Julianne Moore,1960,Seventh Son +Alicia Vikander,1988,Seventh Son +Antje Traue,1981,Seventh Son +Olivia Williams,1968,Seventh Son +Kit Harington,1986,Seventh Son +Djimon Hounsou,1964,Seventh Son +Eric Bana,1968,Deliver Us from Evil +Olivia Munn,1980,Deliver Us from Evil +Chris Coy,1986,Deliver Us from Evil +Dorian Missick,1976,Deliver Us from Evil +Sean Harris,1966,Deliver Us from Evil +Joel McHale,1971,Deliver Us from Evil +Mike Houston,1976,Deliver Us from Evil +Andy Serkis,1964,Dawn of the Planet of the Apes +Jason Clarke,1969,Dawn of the Planet of the Apes +Gary Oldman,1958,Dawn of the Planet of the Apes +Keri Russell,1976,Dawn of the Planet of the Apes +Toby Kebbell,1982,Dawn of the Planet of the Apes +Kodi Smit-McPhee,1996,Dawn of the Planet of the Apes +Kirk Acevedo,1971,Dawn of the Planet of the Apes +Christian Bale,1974,Exodus: Gods and Kings +Joel Edgerton,1974,Exodus: Gods and Kings +John Turturro,1957,Exodus: Gods and Kings +Aaron Paul,1979,Exodus: Gods and Kings +Ben Mendelsohn,1969,Exodus: Gods and Kings +Sigourney Weaver,1949,Exodus: Gods and Kings +Ben Kingsley,1943,Exodus: Gods and Kings +Annabelle Wallis,1984,Annabelle +Ward Horton,1976,Annabelle +Tony Amendola,1951,Annabelle +Alfre Woodard,1952,Annabelle +Eric Ladin,1978,Annabelle +Ivar Brogger,1947,Annabelle +Karl Urban,1972,The Loft +James Marsden,1973,The Loft +Wentworth Miller,1972,The Loft +Eric Stonestreet,1971,The Loft +Matthias Schoenaerts,1977,The Loft +Isabel Lucas,1985,The Loft +Rachael Taylor,1984,The Loft +Rhona Mitra,1976,The Loft +Valerie Cruz,1976,The Loft +Michael Parks,1940,Tusk +Justin Long,1978,Tusk +Genesis Rodriguez,1987,Tusk +Haley Joel Osment,1988,Tusk +Johnny Depp,1963,Tusk +Ralph Garman,1964,Tusk +Jennifer Schwalbach Smith,1971,Tusk +Harley Quinn Smith,1999,Tusk +James Franco,1978,The Interview +Seth Rogen,1982,The Interview +Lizzy Caplan,1982,The Interview +Timothy Simons,1978,The Interview +Ryan Reynolds,1976,The Voices +Gemma Arterton,1986,The Voices +Anna Kendrick,1985,The Voices +Jacki Weaver,1947,The Voices +Ella Smith,1983,The Voices +Sam Spruell,1977,The Voices +Aaron Eckhart,1968,"I, Frankenstein" +Yvonne Strahovski,1982,"I, Frankenstein" +Miranda Otto,1967,"I, Frankenstein" +Bill Nighy,1949,"I, Frankenstein" +Jai Courtney,1986,"I, Frankenstein" +Aden Young,1972,"I, Frankenstein" +Caitlin Stasey,1990,"I, Frankenstein" +Mahesh Jadu,1982,"I, Frankenstein" +Robert Downey Jr.,1965,The Judge +Robert Duvall,1931,The Judge +Vera Farmiga,1973,The Judge +Billy Bob Thornton,1955,The Judge +Vincent D'Onofrio,1959,The Judge +Dax Shepard,1975,The Judge +Leighton Meester,1986,The Judge +Ken Howard,1944,The Judge +Bradley Cooper,1975,Burnt +Sienna Miller,1981,Burnt +Riccardo Scamarcio,1979,Burnt +Omar Sy,1978,Burnt +Henry Goodman,1950,Burnt +Matthew Rhys,1974,Burnt +Stephen Campbell Moore,1979,Burnt +Mireille Enos,1975,If I Stay +Jamie Blackley,1991,If I Stay +Joshua Leonard,1975,If I Stay +Liana Liberato,1995,If I Stay +Stacy Keach,1941,If I Stay +Gabrielle Rose,1954,If I Stay +Jakob Davies,2003,If I Stay +Perdita Weeks,1985,"As Above, So Below" +Ben Feldman,1980,"As Above, So Below" +Edwin Hodge,1985,"As Above, So Below" +Kevin Costner,1955,3 Days to Kill +Amber Heard,1986,3 Days to Kill +Hailee Steinfeld,1996,3 Days to Kill +Connie Nielsen,1965,3 Days to Kill +Richard Sammel,1960,3 Days to Kill +Chris Evans,1981,Before We Go +Alice Eve,1982,Before We Go +Emma Fitzpatrick,1985,Before We Go +John Cullum,1930,Before We Go +Mark Kassen,1971,Before We Go +Daniel Spink,1979,Before We Go +Liam Neeson,1952,Non-Stop +Julianne Moore,1960,Non-Stop +Scoot McNairy,1977,Non-Stop +Michelle Dockery,1981,Non-Stop +Nate Parker,1979,Non-Stop +Corey Stoll,1976,Non-Stop +Lupita Nyong'o,1983,Non-Stop +Omar Metwally,1974,Non-Stop +Jason Butler Harner,1970,Non-Stop +Zac Efron,1987,That Awkward Moment +Miles Teller,1987,That Awkward Moment +Michael B. Jordan,1987,That Awkward Moment +Imogen Poots,1989,That Awkward Moment +Mackenzie Davis,1987,That Awkward Moment +Jessica Lucas,1985,That Awkward Moment +Addison Timlin,1991,That Awkward Moment +Josh Pais,1964,That Awkward Moment +Vincent Price,1911,Vincent +Adam Sandler,1966,Blended +Drew Barrymore,1975,Blended +Kevin Nealon,1953,Blended +Terry Crews,1968,Blended +Wendi McLendon-Covey,1969,Blended +Bella Thorne,1997,Blended +Dan Stevens,1982,The Guest +Maika Monroe,1993,The Guest +Brendan Meyer,1994,The Guest +Sheila Kelley,1961,The Guest +Leland Orser,1960,The Guest +Lance Reddick,1962,The Guest +Chase Williamson,1988,The Guest +Joel David Moore,1977,The Guest +Cameron Diaz,1972,The Other Woman +Leslie Mann,1972,The Other Woman +Nikolaj Coster-Waldau,1970,The Other Woman +Don Johnson,1949,The Other Woman +Kate Upton,1992,The Other Woman +Taylor Kinney,1981,The Other Woman +Nicki Minaj,1982,The Other Woman +Nicolas Cage,1964,Left Behind +Chad Michael Murray,1981,Left Behind +Cassi Thomson,1993,Left Behind +Jordin Sparks,1989,Left Behind +Lea Thompson,1961,Left Behind +Gary Grubbs,1949,Left Behind +Quinton Aaron,1984,Left Behind +Martin Klebba,1969,Left Behind +Jamie Foxx,1967,Annie +Rose Byrne,1979,Annie +Bobby Cannavale,1970,Annie +Adewale Akinnuoye-Agbaje,1967,Annie +David Zayas,1962,Annie +Cameron Diaz,1972,Annie +Nicolette Pierini,2003,Annie +Heather Sossaman,1987,Cybernatural +Courtney Halverson,1989,Cybernatural +Shelley Hennig,1987,Cybernatural +Will Peltz,1986,Cybernatural +Renee Olstead,1989,Cybernatural +Mark Wahlberg,1971,The Gambler +George Kennedy,1925,The Gambler +Jessica Lange,1949,The Gambler +Brie Larson,1989,The Gambler +Michael Kenneth Williams,1966,The Gambler +Mads Mikkelsen,1965,The Salvation +Eva Green,1980,The Salvation +Jeffrey Dean Morgan,1966,The Salvation +Eric Cantona,1966,The Salvation +Mikael Persbrandt,1963,The Salvation +Douglas Henshall,1965,The Salvation +Michael Raymond-James,1977,The Salvation +Jonathan Pryce,1947,The Salvation +Seth MacFarlane,1973,A Million Ways to Die in the West +Charlize Theron,1975,A Million Ways to Die in the West +Amanda Seyfried,1985,A Million Ways to Die in the West +Liam Neeson,1952,A Million Ways to Die in the West +Giovanni Ribisi,1974,A Million Ways to Die in the West +Neil Patrick Harris,1973,A Million Ways to Die in the West +Sarah Silverman,1970,A Million Ways to Die in the West +Wes Studi,1947,A Million Ways to Die in the West +Alicia Vikander,1988,Testament of Youth +Taron Egerton,1989,Testament of Youth +Colin Morgan,1986,Testament of Youth +Dominic West,1969,Testament of Youth +Emily Watson,1967,Testament of Youth +Kit Harington,1986,Testament of Youth +Joanna Scanlan,1961,Testament of Youth +Miranda Richardson,1958,Testament of Youth +Sam Neill,1947,Hunt for the Wilderpeople +Rima Te Wiata,1963,Hunt for the Wilderpeople +Johnny Depp,1963,Transcendence +Rebecca Hall,1982,Transcendence +Paul Bettany,1971,Transcendence +Cillian Murphy,1976,Transcendence +Kate Mara,1983,Transcendence +Cole Hauser,1975,Transcendence +Morgan Freeman,1937,Transcendence +Clifton Collins Jr.,1970,Transcendence +Cory Hardrict,1979,Transcendence +Cameron Diaz,1972,Sex Tape +Jason Segel,1980,Sex Tape +Rob Corddry,1971,Sex Tape +Ellie Kemper,1980,Sex Tape +Rob Lowe,1964,Sex Tape +Nancy Lenehan,1953,Sex Tape +Jessica Sula,1994,Honeytrap +Lucien Laviscount,1992,Honeytrap +Naomi Ryan,1977,Honeytrap +Asa Butterfield,1997,X+Y +Rafe Spall,1983,X+Y +Sally Hawkins,1976,X+Y +Eddie Marsan,1968,X+Y +Martin McCann,1983,X+Y +Jake Davies,1993,X+Y +Mark Duplass,1976,Creep +Tom Hardy,1977,The Drop +Noomi Rapace,1979,The Drop +James Gandolfini,1961,The Drop +Matthias Schoenaerts,1977,The Drop +John Ortiz,1968,The Drop +Elizabeth Rodriguez,1980,The Drop +Michael Esper,1976,The Drop +Susanne Wuest,1979,Ich seh ich seh +Kate Beckinsale,1973,Eliza Graves +Jim Sturgess,1978,Eliza Graves +David Thewlis,1963,Eliza Graves +Brendan Gleeson,1955,Eliza Graves +Ben Kingsley,1943,Eliza Graves +Michael Caine,1933,Eliza Graves +Jason Flemyng,1966,Eliza Graves +Jason Bateman,1969,Horrible Bosses 2 +Jason Sudeikis,1975,Horrible Bosses 2 +Charlie Day,1976,Horrible Bosses 2 +Jennifer Aniston,1969,Horrible Bosses 2 +Kevin Spacey,1959,Horrible Bosses 2 +Jamie Foxx,1967,Horrible Bosses 2 +Chris Pine,1980,Horrible Bosses 2 +Christoph Waltz,1956,Horrible Bosses 2 +Jonathan Banks,1947,Horrible Bosses 2 +Chris Pine,1980,Jack Ryan: Shadow Recruit +Keira Knightley,1985,Jack Ryan: Shadow Recruit +Kevin Costner,1955,Jack Ryan: Shadow Recruit +Kenneth Branagh,1960,Jack Ryan: Shadow Recruit +Lenn Kudrjawizki,1975,Jack Ryan: Shadow Recruit +Peter Andersson,1953,Jack Ryan: Shadow Recruit +Elena Velikanova,1984,Jack Ryan: Shadow Recruit +Nonso Anozie,1979,Jack Ryan: Shadow Recruit +Kate Mara,1983,Man Down +Shia LaBeouf,1986,Man Down +Jai Courtney,1986,Man Down +Clifton Collins Jr.,1970,Man Down +Gary Oldman,1958,Man Down +Jose Pablo Cantillo,1979,Man Down +Harrison Ford,1942,Star Wars: Episode VII - The Force Awakens +Mark Hamill,1951,Star Wars: Episode VII - The Force Awakens +Carrie Fisher,1956,Star Wars: Episode VII - The Force Awakens +Adam Driver,1983,Star Wars: Episode VII - The Force Awakens +Daisy Ridley,1992,Star Wars: Episode VII - The Force Awakens +John Boyega,1992,Star Wars: Episode VII - The Force Awakens +Oscar Isaac,1979,Star Wars: Episode VII - The Force Awakens +Lupita Nyong'o,1983,Star Wars: Episode VII - The Force Awakens +Andy Serkis,1964,Star Wars: Episode VII - The Force Awakens +Leonardo DiCaprio,1974,The Revenant +Tom Hardy,1977,The Revenant +Domhnall Gleeson,1983,The Revenant +Will Poulter,1993,The Revenant +Forrest Goodluck,1998,The Revenant +Kristoffer Joner,1972,The Revenant +Jennifer Lawrence,1990,Joy +Robert De Niro,1943,Joy +Bradley Cooper,1975,Joy +Diane Ladd,1935,Joy +Virginia Madsen,1961,Joy +Isabella Rossellini,1952,Joy +Daniel Craig,1968,Spectre +Christoph Waltz,1956,Spectre +Ralph Fiennes,1962,Spectre +Monica Bellucci,1964,Spectre +Ben Whishaw,1980,Spectre +Naomie Harris,1976,Spectre +Dave Bautista,1969,Spectre +Andrew Scott,1976,Spectre +Anya Taylor-Joy,1996,The VVitch: A New-England Folktale +Ralph Ineson,1969,The VVitch: A New-England Folktale +Kate Dickie,1971,The VVitch: A New-England Folktale +Julian Richings,1955,The VVitch: A New-England Folktale +Sarah Stephens,1990,The VVitch: A New-England Folktale +Ryan Gosling,1980,The Big Short +Charlie Talbert,1978,The Big Short +Christian Bale,1974,The Big Short +Chris Pratt,1979,Jurassic World +Bryce Dallas Howard,1981,Jurassic World +Irrfan Khan,1967,Jurassic World +Vincent D'Onofrio,1959,Jurassic World +Nick Robinson,1995,Jurassic World +Jake Johnson,1978,Jurassic World +Omar Sy,1978,Jurassic World +BD Wong,1960,Jurassic World +Tom Hardy,1977,Mad Max: Fury Road +Charlize Theron,1975,Mad Max: Fury Road +Nicholas Hoult,1989,Mad Max: Fury Road +Hugh Keays-Byrne,1947,Mad Max: Fury Road +Josh Helman,1986,Mad Max: Fury Road +Nathan Jones,1969,Mad Max: Fury Road +Rosie Huntington-Whiteley,1987,Mad Max: Fury Road +Riley Keough,1989,Mad Max: Fury Road +Matt Damon,1970,The Martian +Jessica Chastain,1977,The Martian +Kristen Wiig,1973,The Martian +Jeff Daniels,1955,The Martian +Sean Bean,1959,The Martian +Kate Mara,1983,The Martian +Sebastian Stan,1982,The Martian +Aksel Hennie,1975,The Martian +Samuel L. Jackson,1948,The Hateful Eight +Kurt Russell,1951,The Hateful Eight +Jennifer Jason Leigh,1962,The Hateful Eight +Walton Goggins,1971,The Hateful Eight +Tim Roth,1961,The Hateful Eight +Michael Madsen,1958,The Hateful Eight +Bruce Dern,1936,The Hateful Eight +James Parks,1968,The Hateful Eight +Alicia Vikander,1988,The Danish Girl +Eddie Redmayne,1982,The Danish Girl +Amber Heard,1986,The Danish Girl +Emerald Fennell,1985,The Danish Girl +Claus Bue,1947,The Danish Girl +Emily Blunt,1983,Sicario +Benicio Del Toro,1967,Sicario +Josh Brolin,1968,Sicario +Victor Garber,1949,Sicario +Jon Bernthal,1976,Sicario +Daniel Kaluuya,1989,Sicario +Jeffrey Donovan,1968,Sicario +Raoul Max Trujillo,1955,Sicario +Robert Downey Jr.,1965,Avengers: Age of Ultron +Chris Hemsworth,1983,Avengers: Age of Ultron +Mark Ruffalo,1967,Avengers: Age of Ultron +Chris Evans,1981,Avengers: Age of Ultron +Scarlett Johansson,1984,Avengers: Age of Ultron +Jeremy Renner,1971,Avengers: Age of Ultron +James Spader,1960,Avengers: Age of Ultron +Samuel L. Jackson,1948,Avengers: Age of Ultron +Don Cheadle,1964,Avengers: Age of Ultron +Mark Ruffalo,1967,Spotlight +Michael Keaton,1951,Spotlight +Rachel McAdams,1978,Spotlight +Liev Schreiber,1967,Spotlight +John Slattery,1962,Spotlight +Brian d'Arcy James,1968,Spotlight +Stanley Tucci,1960,Spotlight +Elena Wohl,1963,Spotlight +Brie Larson,1989,Room +Jacob Tremblay,2006,Room +Sean Bridgers,1968,Room +Wendy Crewson,1956,Room +Amanda Brugel,1978,Room +Joan Allen,1956,Room +Dakota Johnson,1989,Fifty Shades of Grey +Jamie Dornan,1982,Fifty Shades of Grey +Jennifer Ehle,1969,Fifty Shades of Grey +Eloise Mumford,1986,Fifty Shades of Grey +Victor Rasuk,1984,Fifty Shades of Grey +Luke Grimes,1984,Fifty Shades of Grey +Marcia Gay Harden,1959,Fifty Shades of Grey +Rita Ora,1990,Fifty Shades of Grey +Max Martini,1969,Fifty Shades of Grey +Domhnall Gleeson,1983,Ex Machina +Corey Johnson,1961,Ex Machina +Oscar Isaac,1979,Ex Machina +Alicia Vikander,1988,Ex Machina +Roger Ashton-Griffiths,1957,The Lobster +Jessica Barden,1992,The Lobster +Olivia Colman,1974,The Lobster +Colin Farrell,1976,The Lobster +Sam Rockwell,1968,Mr. Right +Anna Kendrick,1985,Mr. Right +Tim Roth,1961,Mr. Right +James Ransone,1979,Mr. Right +Anson Mount,1973,Mr. Right +RZA,1969,Mr. Right +Tom Cruise,1962,Mission: Impossible - Rogue Nation +Jeremy Renner,1971,Mission: Impossible - Rogue Nation +Simon Pegg,1970,Mission: Impossible - Rogue Nation +Rebecca Ferguson,1983,Mission: Impossible - Rogue Nation +Ving Rhames,1959,Mission: Impossible - Rogue Nation +Sean Harris,1966,Mission: Impossible - Rogue Nation +Simon McBurney,1957,Mission: Impossible - Rogue Nation +Jingchu Zhang,1980,Mission: Impossible - Rogue Nation +Tom Hollander,1967,Mission: Impossible - Rogue Nation +Jennifer Lawrence,1990,The Hunger Games: Mockingjay - Part 2 +Josh Hutcherson,1992,The Hunger Games: Mockingjay - Part 2 +Liam Hemsworth,1990,The Hunger Games: Mockingjay - Part 2 +Woody Harrelson,1961,The Hunger Games: Mockingjay - Part 2 +Donald Sutherland,1935,The Hunger Games: Mockingjay - Part 2 +Philip Seymour Hoffman,1967,The Hunger Games: Mockingjay - Part 2 +Julianne Moore,1960,The Hunger Games: Mockingjay - Part 2 +Willow Shields,2000,The Hunger Games: Mockingjay - Part 2 +Sam Claflin,1986,The Hunger Games: Mockingjay - Part 2 +Hilary Swank,1974,P.S. I Love You +Gerard Butler,1969,P.S. I Love You +Lisa Kudrow,1963,P.S. I Love You +Gina Gershon,1962,P.S. I Love You +James Marsters,1962,P.S. I Love You +Kathy Bates,1948,P.S. I Love You +Harry Connick Jr.,1967,P.S. I Love You +Nellie McKay,1982,P.S. I Love You +Jeffrey Dean Morgan,1966,P.S. I Love You +Johnny Depp,1963,Black Mass +Joel Edgerton,1974,Black Mass +Benedict Cumberbatch,1976,Black Mass +Dakota Johnson,1989,Black Mass +Kevin Bacon,1958,Black Mass +Peter Sarsgaard,1971,Black Mass +Jesse Plemons,1988,Black Mass +Rory Cochrane,1972,Black Mass +David Harbour,1975,Black Mass +Chris Hemsworth,1983,In the Heart of the Sea +Benjamin Walker,1982,In the Heart of the Sea +Cillian Murphy,1976,In the Heart of the Sea +Brendan Gleeson,1955,In the Heart of the Sea +Ben Whishaw,1980,In the Heart of the Sea +Tom Holland,1996,In the Heart of the Sea +Frank Dillane,1991,In the Heart of the Sea +Brad Pitt,1963,By the Sea +Angelina Jolie,1975,By the Sea +Melvil Poupaud,1973,By the Sea +Niels Arestrup,1949,By the Sea +Richard Bohringer,1942,By the Sea +Marika Green,1943,By the Sea +Keanu Reeves,1964,Knock Knock +Lorenza Izzo,1989,Knock Knock +Ana de Armas,1988,Knock Knock +Aaron Burns,1985,Knock Knock +Ignacia Allamand,1981,Knock Knock +Colleen Camp,1953,Knock Knock +Amy Poehler,1971,Inside Out +Phyllis Smith,1951,Inside Out +Richard Kind,1956,Inside Out +Bill Hader,1978,Inside Out +Lewis Black,1948,Inside Out +Mindy Kaling,1979,Inside Out +Diane Lane,1965,Inside Out +Kyle MacLachlan,1959,Inside Out +O'Shea Jackson Jr.,1991,Straight Outta Compton +Corey Hawkins,1988,Straight Outta Compton +Neil Brown Jr.,1980,Straight Outta Compton +Aldis Hodge,1986,Straight Outta Compton +Paul Rudd,1969,Ant-Man +Michael Douglas,1944,Ant-Man +Evangeline Lilly,1979,Ant-Man +Corey Stoll,1976,Ant-Man +Bobby Cannavale,1970,Ant-Man +Anthony Mackie,1978,Ant-Man +Judy Greer,1975,Ant-Man +Logan Marshall-Green,1976,The Invitation +Emayatzy Corinealdi,1980,The Invitation +Michelle Krusiec,1974,The Invitation +Jordi Vilasuso,1981,The Invitation +Marieh Delfino,1977,The Invitation +Tammy Blanchard,1976,The Invitation +Saoirse Ronan,1994,Brooklyn +Brid Brennan,1955,Brooklyn +Fiona Glascott,1982,Brooklyn +Mark Rylance,1960,Bridge of Spies +Domenick Lombardozzi,1976,Bridge of Spies +Tom Hanks,1956,Bridge of Spies +Joshua Harto,1979,Bridge of Spies +Cate Blanchett,1969,Cinderella +Lily James,1989,Cinderella +Richard Madden,1986,Cinderella +Helena Bonham Carter,1966,Cinderella +Nonso Anozie,1979,Cinderella +Sophie McShera,1985,Cinderella +Holliday Grainger,1988,Cinderella +Derek Jacobi,1938,Cinderella +Anton Yelchin,1989,Green Room +Joe Cole,1988,Green Room +Alia Shawkat,1989,Green Room +Mark Webber,1980,Green Room +Eric Edelstein,1977,Green Room +Michael Fassbender,1977,Steve Jobs +Kate Winslet,1975,Steve Jobs +Seth Rogen,1982,Steve Jobs +Jeff Daniels,1955,Steve Jobs +Michael Stuhlbarg,1968,Steve Jobs +Katherine Waterston,1980,Steve Jobs +Perla Haney-Jardine,1997,Steve Jobs +Emjay Anthony,2003,Krampus +Adam Scott,1973,Krampus +Toni Collette,1972,Krampus +Stefania LaVie Owen,1997,Krampus +Krista Stadler,1942,Krampus +Conchata Ferrell,1943,Krampus +Allison Tolman,1981,Krampus +David Koechner,1962,Krampus +Vin Diesel,1967,Furious Seven +Paul Walker,1973,Furious Seven +Jason Statham,1967,Furious Seven +Michelle Rodriguez,1978,Furious Seven +Jordana Brewster,1980,Furious Seven +Tyrese Gibson,1978,Furious Seven +Ludacris,1977,Furious Seven +Dwayne Johnson,1972,Furious Seven +Lucas Black,1982,Furious Seven +Mia Wasikowska,1989,Crimson Peak +Jessica Chastain,1977,Crimson Peak +Tom Hiddleston,1981,Crimson Peak +Charlie Hunnam,1980,Crimson Peak +Jim Beaver,1950,Crimson Peak +Burn Gorman,1974,Crimson Peak +Leslie Hope,1965,Crimson Peak +Doug Jones,1960,Crimson Peak +Jonathan Hyde,1948,Crimson Peak +Russell Crowe,1964,Fathers & Daughters +Amanda Seyfried,1985,Fathers & Daughters +Aaron Paul,1979,Fathers & Daughters +Diane Kruger,1976,Fathers & Daughters +Bruce Greenwood,1956,Fathers & Daughters +Janet McTeer,1961,Fathers & Daughters +Kylie Rogers,2004,Fathers & Daughters +Jane Fonda,1937,Fathers & Daughters +Arnold Schwarzenegger,1947,Terminator Genisys +Jason Clarke,1969,Terminator Genisys +Emilia Clarke,1986,Terminator Genisys +Jai Courtney,1986,Terminator Genisys +J.K. Simmons,1955,Terminator Genisys +Matt Smith,1982,Terminator Genisys +Courtney B. Vance,1960,Terminator Genisys +Byung-hun Lee,1970,Terminator Genisys +Victor Garber,1949,Legends of Tomorrow +Brandon Routh,1979,Legends of Tomorrow +Caity Lotz,1986,Legends of Tomorrow +Franz Drameh,1993,Legends of Tomorrow +Dominic Purcell,1970,Legends of Tomorrow +Arthur Darvill,1982,Legends of Tomorrow +Wentworth Miller,1972,Legends of Tomorrow +Jude Law,1972,Spy +Melissa McCarthy,1970,Spy +Miranda Hart,1972,Spy +Helen Mirren,1945,Eye in the Sky +Aaron Paul,1979,Eye in the Sky +Jason Clarke,1969,Everest +Thomas M. Wright,1983,Everest +Martin Henderson,1974,Everest +Tom Goodman-Hill,1968,Everest +Jeremy Irons,1948,The Man Who Knew Infinity +Dev Patel,1990,The Man Who Knew Infinity +Malcolm Sinclair,1950,The Man Who Knew Infinity +Dhritiman Chatterjee,1945,The Man Who Knew Infinity +Stephen Fry,1957,The Man Who Knew Infinity +Olivia DeJonge,1998,The Visit +Ed Oxenbould,2001,The Visit +Deanna Dunagan,1940,The Visit +Peter McRobbie,1943,The Visit +Kathryn Hahn,1973,The Visit +Cate Blanchett,1969,Carol +Rooney Mara,1985,Carol +Kyle Chandler,1965,Carol +Sarah Paulson,1974,Carol +Kevin Crowley,1958,Carol +Tia Mowry-Hardrict,1978,"Sister, Sister" +Tamera Mowry-Housley,1978,"Sister, Sister" +Tim Reid,1944,"Sister, Sister" +Marques Houston,1981,"Sister, Sister" +Robert De Niro,1943,The Intern +Anne Hathaway,1982,The Intern +Rene Russo,1954,The Intern +Andrew Rannells,1978,The Intern +Adam Devine,1983,The Intern +Jake Gyllenhaal,1980,Demolition +Naomi Watts,1968,Demolition +Chris Cooper,1951,Demolition +Polly Draper,1955,Demolition +Debra Monk,1949,Demolition +Heather Lind,1983,Demolition +Jack Black,1969,Goosebumps +Dylan Minnette,1996,Goosebumps +Odeya Rush,1997,Goosebumps +Ryan Lee,1996,Goosebumps +Amy Ryan,1968,Goosebumps +Halston Sage,1993,Goosebumps +Steven Krueger,1989,Goosebumps +Sharlto Copley,1973,Hardcore Henry +Danila Kozlovsky,1985,Hardcore Henry +Haley Bennett,1988,Hardcore Henry +Tim Roth,1961,Hardcore Henry +Svetlana Ustinova,1982,Hardcore Henry +Darya Charusha,1980,Hardcore Henry +Jeffrey Dean Morgan,1966,Desierto +Yuri Zackoqv,1993,Desierto +Jodelle Ferland,1994,The Unspoken +Pascale Hutton,1979,The Unspoken +Jonathan Whitesell,1991,The Unspoken +Chanelle Peloso,1994,The Unspoken +Rukiya Bernard,1983,The Unspoken +Lochlyn Munro,1966,The Unspoken +Colin Quinn,1959,Trainwreck +Amy Schumer,1981,Trainwreck +Josh Segarra,1986,Trainwreck +Ryan Farrell,1981,Trainwreck +Jim Florentine,1964,Trainwreck +Robert Kelly,1970,Trainwreck +Michael B. Jordan,1987,Creed +Sylvester Stallone,1946,Creed +Tessa Thompson,1983,Creed +Phylicia Rashad,1948,Creed +Andre Ward,1984,Creed +Tony Bellew,1982,Creed +Ritchie Coster,1967,Creed +Graham McTavish,1961,Creed +Jake Gyllenhaal,1980,Southpaw +Rachel McAdams,1978,Southpaw +Forest Whitaker,1961,Southpaw +Oona Laurence,2002,Southpaw +50 Cent,1975,Southpaw +Naomie Harris,1976,Southpaw +Daniel Radcliffe,1989,Victor Frankenstein +Jessica Brown Findlay,1989,Victor Frankenstein +Bronson Webb,1983,Victor Frankenstein +James McAvoy,1979,Victor Frankenstein +Daniel Mays,1978,Victor Frankenstein +Spencer Wilding,1972,Victor Frankenstein +Andrew Scott,1976,Victor Frankenstein +Anna Kendrick,1985,Pitch Perfect 2 +Rebel Wilson,1980,Pitch Perfect 2 +Hailee Steinfeld,1996,Pitch Perfect 2 +Brittany Snow,1986,Pitch Perfect 2 +Skylar Astin,1987,Pitch Perfect 2 +Adam Devine,1983,Pitch Perfect 2 +Katey Sagal,1954,Pitch Perfect 2 +Anna Camp,1982,Pitch Perfect 2 +Ben Platt,1993,Pitch Perfect 2 +Emma Watson,1990,Colonia +Michael Nyqvist,1960,Colonia +Richenda Carey,1948,Colonia +Vicky Krieps,1983,Colonia +Julian Ovenden,1976,Colonia +August Zirner,1956,Colonia +Martin Wuttke,1962,Colonia +Dwayne Johnson,1972,San Andreas +Carla Gugino,1971,San Andreas +Alexandra Daddario,1986,San Andreas +Ioan Gruffudd,1973,San Andreas +Archie Panjabi,1972,San Andreas +Paul Giamatti,1967,San Andreas +Art Parkinson,2001,San Andreas +Will Yun Lee,1971,San Andreas +Blake Lively,1987,The Age of Adaline +Michiel Huisman,1981,The Age of Adaline +Harrison Ford,1942,The Age of Adaline +Ellen Burstyn,1932,The Age of Adaline +Kathy Baker,1950,The Age of Adaline +Amanda Crew,1986,The Age of Adaline +Lynda Boyd,1965,The Age of Adaline +Richard Harmon,1991,The Age of Adaline +Ray Winstone,1957,Point Break +Teresa Palmer,1986,Point Break +Matias Varela,1980,Point Break +Clemens Schick,1972,Point Break +Tobias Santelmann,1980,Point Break +Max Thieriot,1988,Point Break +Delroy Lindo,1952,Point Break +Ellen Page,1987,Into the Forest +Evan Rachel Wood,1987,Into the Forest +Max Minghella,1985,Into the Forest +Callum Keith Rennie,1960,Into the Forest +Wendy Crewson,1956,Into the Forest +Adam Sandler,1966,Hotel Transylvania 2 +Andy Samberg,1978,Hotel Transylvania 2 +Selena Gomez,1992,Hotel Transylvania 2 +Kevin James,1965,Hotel Transylvania 2 +Steve Buscemi,1957,Hotel Transylvania 2 +David Spade,1964,Hotel Transylvania 2 +Keegan-Michael Key,1971,Hotel Transylvania 2 +Fran Drescher,1957,Hotel Transylvania 2 +Hugh Jackman,1968,Pan +Levi Miller,2002,Pan +Garrett Hedlund,1984,Pan +Rooney Mara,1985,Pan +Nonso Anozie,1979,Pan +Amanda Seyfried,1985,Pan +Kathy Burke,1964,Pan +Kate Winslet,1975,The Dressmaker +Liam Hemsworth,1990,The Dressmaker +Hugo Weaving,1960,The Dressmaker +Judy Davis,1955,The Dressmaker +Caroline Goodall,1959,The Dressmaker +Kerry Fox,1966,The Dressmaker +Rebecca Gibney,1964,The Dressmaker +Dylan O'Brien,1991,Maze Runner: The Scorch Trials +Kaya Scodelario,1992,Maze Runner: The Scorch Trials +Thomas Brodie-Sangster,1990,Maze Runner: The Scorch Trials +Alexander Flores,1990,Maze Runner: The Scorch Trials +Jacob Lofland,1996,Maze Runner: The Scorch Trials +Rosa Salazar,1985,Maze Runner: The Scorch Trials +Giancarlo Esposito,1958,Maze Runner: The Scorch Trials +Michael Caine,1933,Youth +Harvey Keitel,1939,Youth +Rachel Weisz,1970,Youth +Paul Dano,1984,Youth +Jane Fonda,1937,Youth +Ed Stoppard,1974,Youth +Alex Macqueen,1974,Youth +Madalina Diana Ghenea,1988,Youth +Ed Helms,1974,Vacation +Christina Applegate,1971,Vacation +Skyler Gisondo,1996,Vacation +Chris Hemsworth,1983,Vacation +Leslie Mann,1972,Vacation +Chevy Chase,1943,Vacation +Beverly D'Angelo,1951,Vacation +Charlie Day,1976,Vacation +Kurt Russell,1951,Bone Tomahawk +Patrick Wilson,1973,Bone Tomahawk +Matthew Fox,1966,Bone Tomahawk +Richard Jenkins,1947,Bone Tomahawk +Lili Simmons,1993,Bone Tomahawk +David Arquette,1971,Bone Tomahawk +Fred Melamed,1956,Bone Tomahawk +Sid Haig,1939,Bone Tomahawk +Tye Sheridan,1996,Scouts Guide to the Zombie Apocalypse +Logan Miller,1992,Scouts Guide to the Zombie Apocalypse +David Koechner,1962,Scouts Guide to the Zombie Apocalypse +Halston Sage,1993,Scouts Guide to the Zombie Apocalypse +Cloris Leachman,1926,Scouts Guide to the Zombie Apocalypse +Niki Koss,1994,Scouts Guide to the Zombie Apocalypse +Hiram A. Murray,1981,Scouts Guide to the Zombie Apocalypse +George Clooney,1961,Tomorrowland +Hugh Laurie,1959,Tomorrowland +Britt Robertson,1990,Tomorrowland +Tim McGraw,1967,Tomorrowland +Kathryn Hahn,1973,Tomorrowland +Keegan-Michael Key,1971,Tomorrowland +Chris Bauer,1966,Tomorrowland +Jason Bateman,1969,The Gift +Rebecca Hall,1982,The Gift +Joel Edgerton,1974,The Gift +Allison Tolman,1981,The Gift +Tim Griffin,1969,The Gift +Busy Philipps,1979,The Gift +Adam Lazarre-White,1969,The Gift +Wendell Pierce,1963,The Gift +Vin Diesel,1967,The Last Witch Hunter +Rose Leslie,1987,The Last Witch Hunter +Elijah Wood,1981,The Last Witch Hunter +Rena Owen,1962,The Last Witch Hunter +Julie Engelbrecht,1984,The Last Witch Hunter +Michael Caine,1933,The Last Witch Hunter +Joseph Gilgun,1984,The Last Witch Hunter +Kate Winslet,1975,Insurgent +Jai Courtney,1986,Insurgent +Mekhi Phifer,1974,Insurgent +Shailene Woodley,1991,Insurgent +Theo James,1984,Insurgent +Ansel Elgort,1994,Insurgent +Miles Teller,1987,Insurgent +Justice Leak,1979,Insurgent +Mae Whitman,1988,The DUFF +Robbie Amell,1988,The DUFF +Bella Thorne,1997,The DUFF +Skyler Samuels,1994,The DUFF +Romany Malco,1968,The DUFF +Nick Eversman,1986,The DUFF +Chris Wylde,1976,The DUFF +Ken Jeong,1969,The DUFF +Jeffrey Wright,1965,The Good Dinosaur +Frances McDormand,1957,The Good Dinosaur +Raymond Ochoa,2001,The Good Dinosaur +Will Smith,1968,Focus +Margot Robbie,1990,Focus +Gerald McRaney,1947,Focus +Rodrigo Santoro,1975,Focus +BD Wong,1960,Focus +Brennan Brown,1968,Focus +Robert Taylor,1963,Focus +Mila Kunis,1983,Jupiter Ascending +Channing Tatum,1980,Jupiter Ascending +Sean Bean,1959,Jupiter Ascending +Eddie Redmayne,1982,Jupiter Ascending +Douglas Booth,1992,Jupiter Ascending +Tuppence Middleton,1987,Jupiter Ascending +Nikki Amuka-Bird,1976,Jupiter Ascending +Christina Cole,1982,Jupiter Ascending +Miles Teller,1987,Fantastic Four +Michael B. Jordan,1987,Fantastic Four +Kate Mara,1983,Fantastic Four +Jamie Bell,1986,Fantastic Four +Toby Kebbell,1982,Fantastic Four +Reg E. Cathey,1958,Fantastic Four +Tim Blake Nelson,1964,Fantastic Four +Dan Castellaneta,1957,Fantastic Four +Will Smith,1968,Concussion +Alec Baldwin,1958,Concussion +Albert Brooks,1947,Concussion +Gugu Mbatha-Raw,1983,Concussion +David Morse,1953,Concussion +Arliss Howard,1954,Concussion +Mike O'Malley,1966,Concussion +Eddie Marsan,1968,Concussion +Hill Harper,1966,Concussion +Sareh Bayat,1979,Muhammad: The Messenger of God +Siamak Adib,1974,Muhammad: The Messenger of God +Nicholas Hoult,1989,Equals +Scott Lawrence,1963,Equals +Kristen Stewart,1990,Equals +Bel Powley,1992,Equals +David Selby,1941,Equals +Channing Tatum,1980,Magic Mike XXL +Juan Piedrahita,1985,Magic Mike XXL +Joe Manganiello,1976,Magic Mike XXL +Kevin Nash,1959,Magic Mike XXL +Gabriel Iglesias,1976,Magic Mike XXL +Matt Bomer,1977,Magic Mike XXL +Britt Robertson,1990,The Longest Ride +Scott Eastwood,1986,The Longest Ride +Alan Alda,1936,The Longest Ride +Jack Huston,1982,The Longest Ride +Oona Chaplin,1986,The Longest Ride +Melissa Benoist,1988,The Longest Ride +Lolita Davidovich,1961,The Longest Ride +Will Ferrell,1967,Daddy's Home +Mark Wahlberg,1971,Daddy's Home +Linda Cardellini,1975,Daddy's Home +Thomas Haden Church,1960,Daddy's Home +Bobby Cannavale,1970,Daddy's Home +Hannibal Buress,1983,Daddy's Home +Bill Burr,1968,Daddy's Home +Christian Bale,1974,Knight of Cups +Cate Blanchett,1969,Knight of Cups +Natalie Portman,1981,Knight of Cups +Brian Dennehy,1938,Knight of Cups +Antonio Banderas,1960,Knight of Cups +Freida Pinto,1984,Knight of Cups +Wes Bentley,1978,Knight of Cups +Isabel Lucas,1985,Knight of Cups +Teresa Palmer,1986,Knight of Cups +Adam Sandler,1966,The Ridiculous 6 +Terry Crews,1968,The Ridiculous 6 +Jorge Garcia,1973,The Ridiculous 6 +Taylor Lautner,1992,The Ridiculous 6 +Rob Schneider,1963,The Ridiculous 6 +Luke Wilson,1971,The Ridiculous 6 +Will Forte,1970,The Ridiculous 6 +Steve Zahn,1967,The Ridiculous 6 +Harvey Keitel,1939,The Ridiculous 6 +Mark Wahlberg,1971,Ted 2 +Seth MacFarlane,1973,Ted 2 +Amanda Seyfried,1985,Ted 2 +Giovanni Ribisi,1974,Ted 2 +Morgan Freeman,1937,Ted 2 +Sam J. Jones,1954,Ted 2 +Patrick Warburton,1964,Ted 2 +Michael Dorn,1952,Ted 2 +Sandra Bullock,1964,Minions +Jon Hamm,1971,Minions +Michael Keaton,1951,Minions +Allison Janney,1959,Minions +Steve Coogan,1965,Minions +Jennifer Saunders,1958,Minions +Geoffrey Rush,1951,Minions +Steve Carell,1962,Minions +Michael Fassbender,1977,Macbeth +Marion Cotillard,1975,Macbeth +Paddy Considine,1974,Macbeth +Taissa Farmiga,1994,The Final Girls +Malin Akerman,1978,The Final Girls +Alexander Ludwig,1992,The Final Girls +Nina Dobrev,1989,The Final Girls +Alia Shawkat,1989,The Final Girls +Thomas Middleditch,1982,The Final Girls +Adam Devine,1983,The Final Girls +Angela Trimbur,1981,The Final Girls +Chloe Bridges,1991,The Final Girls +Tilda Swinton,1960,A Bigger Splash +Matthias Schoenaerts,1977,A Bigger Splash +Ralph Fiennes,1962,A Bigger Splash +Dakota Johnson,1989,A Bigger Splash +Corrado Guzzanti,1965,A Bigger Splash +Sharlto Copley,1973,Chappie +Dev Patel,1990,Chappie +Ninja,1974,Chappie +Yo-Landi Visser,1984,Chappie +Jose Pablo Cantillo,1979,Chappie +Hugh Jackman,1968,Chappie +Sigourney Weaver,1949,Chappie +Bahar Pars,1979,En man som heter Ove +Filip Berg,1986,En man som heter Ove +Anna-Lena Brundin,1959,En man som heter Ove +Karin de Frumerie,1978,En man som heter Ove +Benedict Cumberbatch,1976,Doctor Strange +Chiwetel Ejiofor,1977,Doctor Strange +Rachel McAdams,1978,Doctor Strange +Benedict Wong,1970,Doctor Strange +Mads Mikkelsen,1965,Doctor Strange +Tilda Swinton,1960,Doctor Strange +Michael Stuhlbarg,1968,Doctor Strange +Benjamin Bratt,1963,Doctor Strange +Scott Adkins,1976,Doctor Strange +Tom Cruise,1962,Jack Reacher: Never Go Back +Cobie Smulders,1982,Jack Reacher: Never Go Back +Aldis Hodge,1986,Jack Reacher: Never Go Back +Danika Yarosh,1998,Jack Reacher: Never Go Back +Holt McCallany,1963,Jack Reacher: Never Go Back +Tom Hanks,1956,Inferno +Felicity Jones,1983,Inferno +Omar Sy,1978,Inferno +Irrfan Khan,1967,Inferno +Sidse Babett Knudsen,1968,Inferno +Ben Foster,1980,Inferno +Ana Ularu,1985,Inferno +Sugar Lyn Beard,1981,Sausage Party +Michael Cera,1988,Sausage Party +Ian James Corlett,1962,Sausage Party +Michael Daingerfield,1970,Sausage Party +Brian Dobson,1973,Sausage Party +Michael Dobson,1966,Sausage Party +James Franco,1978,Sausage Party +Ben Affleck,1972,The Accountant +Anna Kendrick,1985,The Accountant +J.K. Simmons,1955,The Accountant +Jon Bernthal,1976,The Accountant +Jeffrey Tambor,1944,The Accountant +Cynthia Addai-Robinson,1985,The Accountant +John Lithgow,1945,The Accountant +Jean Smart,1951,The Accountant +Amy Adams,1974,Nocturnal Animals +Jake Gyllenhaal,1980,Nocturnal Animals +Michael Shannon,1974,Nocturnal Animals +Aaron Taylor-Johnson,1990,Nocturnal Animals +Isla Fisher,1976,Nocturnal Animals +Ellie Bamber,1997,Nocturnal Animals +Armie Hammer,1986,Nocturnal Animals +Karl Glusman,1988,Nocturnal Animals +Emily Blunt,1983,The Girl on the Train +Haley Bennett,1988,The Girl on the Train +Rebecca Ferguson,1983,The Girl on the Train +Justin Theroux,1971,The Girl on the Train +Luke Evans,1979,The Girl on the Train +Laura Prepon,1980,The Girl on the Train +Allison Janney,1959,The Girl on the Train +Darren Goldstein,1974,The Girl on the Train +Will Smith,1968,Suicide Squad +Ike Barinholtz,1977,Suicide Squad +Margot Robbie,1990,Suicide Squad +Christopher Dyson,1978,Suicide Squad +Bambadjan Bamba,1982,Suicide Squad +Viola Davis,1965,Suicide Squad +David Harbour,1975,Suicide Squad +Tyler Perry,1969,Boo! A Madea Halloween +Cassi Davis,1964,Boo! A Madea Halloween +Bella Thorne,1997,Boo! A Madea Halloween +Yousef Erakat,1990,Boo! A Madea Halloween +James McAvoy,1979,Split +Haley Lu Richardson,1995,Split +Anya Taylor-Joy,1996,Split +Kim Director,1974,Split +Maria Breyman,1987,Split +Jessica Sula,1994,Split +Betty Buckley,1947,Split +Brad William Henke,1966,Split +Lyne Renee,1979,Split +Eva Green,1980,Miss Peregrine's Home for Peculiar Children +Asa Butterfield,1997,Miss Peregrine's Home for Peculiar Children +Samuel L. Jackson,1948,Miss Peregrine's Home for Peculiar Children +Judi Dench,1934,Miss Peregrine's Home for Peculiar Children +Rupert Everett,1959,Miss Peregrine's Home for Peculiar Children +Allison Janney,1959,Miss Peregrine's Home for Peculiar Children +Chris O'Dowd,1979,Miss Peregrine's Home for Peculiar Children +Terence Stamp,1938,Miss Peregrine's Home for Peculiar Children +Ella Purnell,1996,Miss Peregrine's Home for Peculiar Children +Zach Galifianakis,1969,Keeping Up with the Joneses +Isla Fisher,1976,Keeping Up with the Joneses +Jon Hamm,1971,Keeping Up with the Joneses +Gal Gadot,1985,Keeping Up with the Joneses +Patton Oswalt,1969,Keeping Up with the Joneses +Matt Walsh,1964,Keeping Up with the Joneses +Andrew Garfield,1983,Hacksaw Ridge +Hugo Weaving,1960,Hacksaw Ridge +Anna Kendrick,1985,Trolls +Justin Timberlake,1981,Trolls +Zooey Deschanel,1980,Trolls +Christopher Mintz-Plasse,1989,Trolls +Christine Baranski,1952,Trolls +Russell Brand,1975,Trolls +Gwen Stefani,1969,Trolls +John Cleese,1939,Trolls +James Corden,1978,Trolls +Viggo Mortensen,1958,Captain Fantastic +George MacKay,1992,Captain Fantastic +Samantha Isler,1998,Captain Fantastic +Nicholas Hamilton,2000,Captain Fantastic +Kathryn Hahn,1973,Captain Fantastic +Chris Pine,1980,Star Trek Beyond +Zachary Quinto,1977,Star Trek Beyond +Karl Urban,1972,Star Trek Beyond +Zoe Saldana,1978,Star Trek Beyond +Simon Pegg,1970,Star Trek Beyond +John Cho,1972,Star Trek Beyond +Anton Yelchin,1989,Star Trek Beyond +Idris Elba,1972,Star Trek Beyond +Sofia Boutella,1982,Star Trek Beyond +Mila Kunis,1983,Bad Moms +Kathryn Hahn,1973,Bad Moms +Kristen Bell,1980,Bad Moms +Christina Applegate,1971,Bad Moms +Jada Pinkett Smith,1971,Bad Moms +Annie Mumolo,1973,Bad Moms +Oona Laurence,2002,Bad Moms +Emjay Anthony,2003,Bad Moms +David Walton,1978,Bad Moms +Zach Woods,1984,Ghostbusters +Kristen Wiig,1973,Ghostbusters +Ed Begley Jr.,1949,Ghostbusters +Charles Dance,1946,Ghostbusters +Melissa McCarthy,1970,Ghostbusters +Kate McKinnon,1984,Ghostbusters +Amy Adams,1974,Arrival +Jeremy Renner,1971,Arrival +Michael Stuhlbarg,1968,Arrival +Forest Whitaker,1961,Arrival +Mark O'Brien,1984,Arrival +Stephen Lang,1952,Don't Breathe +Jane Levy,1989,Don't Breathe +Dylan Minnette,1996,Don't Breathe +Katia Bokor,1975,Don't Breathe +Sergej Onopko,1985,Don't Breathe +Emma Roberts,1991,Nerve +Dave Franco,1985,Nerve +Emily Meade,1989,Nerve +Miles Heizer,1994,Nerve +Kimiko Glenn,1989,Nerve +Marc John Jefferies,1990,Nerve +Machine Gun Kelly,1990,Nerve +Ellen DeGeneres,1958,Finding Dory +Albert Brooks,1947,Finding Dory +Ed O'Neill,1946,Finding Dory +Kaitlin Olson,1975,Finding Dory +Ty Burrell,1967,Finding Dory +Diane Keaton,1946,Finding Dory +Eugene Levy,1946,Finding Dory +Brenton Thwaites,1989,Gods of Egypt +Courtney Eaton,1996,Gods of Egypt +Nikolaj Coster-Waldau,1970,Gods of Egypt +Emily Wheaton,1989,Gods of Egypt +Elodie Yung,1981,Gods of Egypt +Rachael Blake,1971,Gods of Egypt +James McAvoy,1979,X-Men: Apocalypse +Michael Fassbender,1977,X-Men: Apocalypse +Jennifer Lawrence,1990,X-Men: Apocalypse +Nicholas Hoult,1989,X-Men: Apocalypse +Oscar Isaac,1979,X-Men: Apocalypse +Rose Byrne,1979,X-Men: Apocalypse +Evan Peters,1987,X-Men: Apocalypse +Josh Helman,1986,X-Men: Apocalypse +Sophie Turner,1996,X-Men: Apocalypse +Ryan Reynolds,1976,Deadpool +Ed Skrein,1983,Deadpool +Michael Benyaer,1970,Deadpool +Stefan Kapicic,1978,Deadpool +Brianna Hildebrand,1996,Deadpool +Kyle Cassie,1976,Deadpool +Taylor Hickson,1997,Deadpool +Liam Hemsworth,1990,Independence Day: Resurgence +Jeff Goldblum,1952,Independence Day: Resurgence +Jessie T. Usher,1992,Independence Day: Resurgence +Bill Pullman,1953,Independence Day: Resurgence +Maika Monroe,1993,Independence Day: Resurgence +Sela Ward,1956,Independence Day: Resurgence +William Fichtner,1956,Independence Day: Resurgence +Judd Hirsch,1935,Independence Day: Resurgence +Brent Spiner,1949,Independence Day: Resurgence +Mark Rylance,1960,The BFG +Ruby Barnhill,2004,The BFG +Penelope Wilton,1946,The BFG +Jemaine Clement,1974,The BFG +Rebecca Hall,1982,The BFG +Rafe Spall,1983,The BFG +Bill Hader,1978,The BFG +Adam Godley,1964,The BFG +Jamie Dornan,1982,Anthropoid +Cillian Murphy,1976,Anthropoid +Charlotte Le Bon,1986,Anthropoid +Harry Lloyd,1983,Anthropoid +Marcin Dorocinski,1973,Anthropoid +Denzel Washington,1954,The Magnificent Seven +Chris Pratt,1979,The Magnificent Seven +Ethan Hawke,1970,The Magnificent Seven +Vincent D'Onofrio,1959,The Magnificent Seven +Byung-hun Lee,1970,The Magnificent Seven +Manuel Garcia-Rulfo,1981,The Magnificent Seven +Haley Bennett,1988,The Magnificent Seven +Peter Sarsgaard,1971,The Magnificent Seven +Mark Wahlberg,1971,Deepwater Horizon +Kurt Russell,1951,Deepwater Horizon +Douglas M. Griffin,1966,Deepwater Horizon +James DuMont,1965,Deepwater Horizon +Gina Rodriguez,1984,Deepwater Horizon +Brad Leland,1954,Deepwater Horizon +John Malkovich,1953,Deepwater Horizon +Bryan Cranston,1956,The Infiltrator +Daniel Mays,1978,The Infiltrator +Tom Vaughan-Lawlor,1977,The Infiltrator +Juliet Aubrey,1966,The Infiltrator +Olympia Dukakis,1931,The Infiltrator +Amy Ryan,1968,The Infiltrator +Teresa Palmer,1986,Lights Out +Billy Burke,1966,Lights Out +Maria Bello,1967,Lights Out +Alicia Vela-Bailey,1982,Lights Out +Andi Osho,1973,Lights Out +Ethan Hawke,1970,In a Valley of Violence +John Travolta,1954,In a Valley of Violence +Taissa Farmiga,1994,In a Valley of Violence +James Ransone,1979,In a Valley of Violence +Karen Gillan,1987,In a Valley of Violence +Toby Huss,1966,In a Valley of Violence +Larry Fessenden,1963,In a Valley of Violence +Ginnifer Goodwin,1978,Zootopia +Jason Bateman,1969,Zootopia +Idris Elba,1972,Zootopia +Jenny Slate,1982,Zootopia +Nate Torrence,1977,Zootopia +Bonnie Hunt,1961,Zootopia +Don Lake,1956,Zootopia +Tommy Chong,1938,Zootopia +J.K. Simmons,1955,Zootopia +Christoph Waltz,1956,The Legend of Tarzan +Samuel L. Jackson,1948,The Legend of Tarzan +Margot Robbie,1990,The Legend of Tarzan +Travis Fimmel,1979,Warcraft +Paula Patton,1975,Warcraft +Ben Foster,1980,Warcraft +Dominic Cooper,1978,Warcraft +Toby Kebbell,1982,Warcraft +Robert Kazinsky,1983,Warcraft +Clancy Brown,1959,Warcraft +Daniel Wu,1974,Warcraft +Chris Evans,1981,Captain America: Civil War +Robert Downey Jr.,1965,Captain America: Civil War +Scarlett Johansson,1984,Captain America: Civil War +Sebastian Stan,1982,Captain America: Civil War +Anthony Mackie,1978,Captain America: Civil War +Don Cheadle,1964,Captain America: Civil War +Jeremy Renner,1971,Captain America: Civil War +Chadwick Boseman,1976,Captain America: Civil War +Paul Bettany,1971,Captain America: Civil War +Kristen Stewart,1990,Billy Lynn's Long Halftime Walk +Vin Diesel,1967,Billy Lynn's Long Halftime Walk +Garrett Hedlund,1984,Billy Lynn's Long Halftime Walk +Steve Martin,1945,Billy Lynn's Long Halftime Walk +Chris Tucker,1972,Billy Lynn's Long Halftime Walk +Deirdre Lovejoy,1962,Billy Lynn's Long Halftime Walk +Joe Alwyn,1991,Billy Lynn's Long Halftime Walk +Mahershala Ali,1974,Moonlight +Naomie Harris,1976,Moonlight +Ewan McGregor,1971,American Pastoral +Jennifer Connelly,1970,American Pastoral +Dakota Fanning,1994,American Pastoral +Peter Riegert,1947,American Pastoral +Rupert Evans,1976,American Pastoral +Uzo Aduba,1981,American Pastoral +Molly Parker,1972,American Pastoral +Valorie Curry,1986,American Pastoral +Sam Claflin,1986,Me Before You +Emilia Clarke,1986,Me Before You +Samantha Spiro,1968,Me Before You +Brendan Coyle,1963,Me Before You +Ben Affleck,1972,Batman v Superman: Dawn of Justice +Henry Cavill,1983,Batman v Superman: Dawn of Justice +Amy Adams,1974,Batman v Superman: Dawn of Justice +Jesse Eisenberg,1983,Batman v Superman: Dawn of Justice +Diane Lane,1965,Batman v Superman: Dawn of Justice +Laurence Fishburne,1961,Batman v Superman: Dawn of Justice +Jeremy Irons,1948,Batman v Superman: Dawn of Justice +Holly Hunter,1958,Batman v Superman: Dawn of Justice +Gal Gadot,1985,Batman v Superman: Dawn of Justice +Johnny Depp,1963,Alice Through the Looking Glass +Mia Wasikowska,1989,Alice Through the Looking Glass +Helena Bonham Carter,1966,Alice Through the Looking Glass +Anne Hathaway,1982,Alice Through the Looking Glass +Sacha Baron Cohen,1971,Alice Through the Looking Glass +Rhys Ifans,1967,Alice Through the Looking Glass +Matt Lucas,1974,Alice Through the Looking Glass +Lindsay Duncan,1950,Alice Through the Looking Glass +Leo Bill,1980,Alice Through the Looking Glass +Ajay Devgn,1969,Shivaay +Girish Karnad,1938,Shivaay +Saurabh Shukla,1963,Shivaay +James Franco,1978,King Cobra +Alicia Silverstone,1976,King Cobra +Molly Ringwald,1968,King Cobra +Christian Slater,1969,King Cobra +Keegan Allen,1989,King Cobra +Sean Grandillo,1992,King Cobra +Patrick Wilson,1973,The Conjuring 2 +Vera Farmiga,1973,The Conjuring 2 +Madison Wolfe,2002,The Conjuring 2 +Frances O'Connor,1967,The Conjuring 2 +Simon McBurney,1957,The Conjuring 2 +Maria Doyle Kennedy,1964,The Conjuring 2 +Tom Hanks,1956,Sully +Aaron Eckhart,1968,Sully +Valerie Mahaffey,1953,Sully +Delphi Harrington,1937,Sully +Mike O'Malley,1966,Sully +Jamey Sheridan,1951,Sully +Anna Gunn,1968,Sully +Holt McCallany,1963,Sully +Zac Efron,1987,Mike and Dave Need Wedding Dates +Adam Devine,1983,Mike and Dave Need Wedding Dates +Anna Kendrick,1985,Mike and Dave Need Wedding Dates +Aubrey Plaza,1984,Mike and Dave Need Wedding Dates +Stephen Root,1951,Mike and Dave Need Wedding Dates +Stephanie Faracy,1952,Mike and Dave Need Wedding Dates +Sugar Lyn Beard,1981,Mike and Dave Need Wedding Dates +Keanu Reeves,1964,The Whole Truth +Gugu Mbatha-Raw,1983,The Whole Truth +Jim Belushi,1954,The Whole Truth +Lara Grice,1971,The Whole Truth +Daniel Radcliffe,1989,Imperium +Toni Collette,1972,Imperium +Tracy Letts,1965,Imperium +Sam Trammell,1969,Imperium +Nestor Carbonell,1967,Imperium +Pawel Szajda,1982,Imperium +Luis Gnecco,1962,Neruda +Alfredo Castro,1955,Neruda +Pablo Derqui,1976,Neruda +Alejandro Goic,1969,Neruda +Kate McKinnon,1984,Masterminds +Kristen Wiig,1973,Masterminds +Zach Galifianakis,1969,Masterminds +Jason Sudeikis,1975,Masterminds +Leslie Jones,1967,Masterminds +Owen Wilson,1968,Masterminds +Mary Elizabeth Ellis,1979,Masterminds +Ken Marino,1968,Masterminds +Devin Ratray,1977,Masterminds +Kevin Spacey,1959,Nine Lives +Jennifer Garner,1972,Nine Lives +Robbie Amell,1988,Nine Lives +Cheryl Hines,1965,Nine Lives +Mark Consuelos,1971,Nine Lives +Christopher Walken,1943,Nine Lives +Teddy Sears,1977,Nine Lives +Russell Crowe,1964,The Nice Guys +Ryan Gosling,1980,The Nice Guys +Matt Bomer,1977,The Nice Guys +Yaya DaCosta,1982,The Nice Guys +Keith David,1956,The Nice Guys +Lois Smith,1930,The Nice Guys +Paul Dano,1984,Swiss Army Man +Daniel Radcliffe,1989,Swiss Army Man +Mary Elizabeth Winstead,1984,Swiss Army Man +Dwayne Johnson,1972,Central Intelligence +Kevin Hart,1979,Central Intelligence +Amy Ryan,1968,Central Intelligence +Danielle Nicolet,1973,Central Intelligence +Jason Bateman,1969,Central Intelligence +Aaron Paul,1979,Central Intelligence +Ryan Hansen,1981,Central Intelligence +Tim Griffin,1969,Central Intelligence +Jamie Dornan,1982,The Siege of Jadotville +Mark Strong,1963,The Siege of Jadotville +Jason O'Mara,1972,The Siege of Jadotville +Emmanuelle Seigner,1966,The Siege of Jadotville +Mikael Persbrandt,1963,The Siege of Jadotville +Guillaume Canet,1973,The Siege of Jadotville +Fiona Glascott,1982,The Siege of Jadotville +Michael McElhatton,1963,The Siege of Jadotville +Blake Lively,1987,The Shallows +Brett Cullen,1956,The Shallows +Neel Sethi,2003,The Jungle Book +Bill Murray,1950,The Jungle Book +Ben Kingsley,1943,The Jungle Book +Idris Elba,1972,The Jungle Book +Lupita Nyong'o,1983,The Jungle Book +Scarlett Johansson,1984,The Jungle Book +Giancarlo Esposito,1958,The Jungle Book +Christopher Walken,1943,The Jungle Book +Garry Shandling,1949,The Jungle Book +Mel Gibson,1956,Blood Father +Erin Moriarty,1994,Blood Father +Diego Luna,1979,Blood Father +Michael Parks,1940,Blood Father +William H. Macy,1950,Blood Father +Miguel Sandoval,1951,Blood Father +Dale Dickey,1961,Blood Father +Louis C.K.,1967,The Secret Life of Pets +Eric Stonestreet,1971,The Secret Life of Pets +Kevin Hart,1979,The Secret Life of Pets +Jenny Slate,1982,The Secret Life of Pets +Ellie Kemper,1980,The Secret Life of Pets +Albert Brooks,1947,The Secret Life of Pets +Lake Bell,1979,The Secret Life of Pets +Dana Carvey,1955,The Secret Life of Pets +Hannibal Buress,1983,The Secret Life of Pets +Andy Samberg,1978,Storks +Kelsey Grammer,1955,Storks +Jennifer Aniston,1969,Storks +Ty Burrell,1967,Storks +Keegan-Michael Key,1971,Storks +Jordan Peele,1979,Storks +Danny Trejo,1944,Storks +Elle Fanning,1998,The Neon Demon +Karl Glusman,1988,The Neon Demon +Jena Malone,1984,The Neon Demon +Bella Heathcote,1987,The Neon Demon +Abbey Lee,1987,The Neon Demon +Desmond Harrington,1976,The Neon Demon +Christina Hendricks,1975,The Neon Demon +Keanu Reeves,1964,The Neon Demon +Charles Baker,1971,The Neon Demon +Kelly Chen,1972,Tokyo Zance +Melissa Leo,1960,Snowden +Zachary Quinto,1977,Snowden +Joseph Gordon-Levitt,1981,Snowden +Jaymes Butler,1959,Snowden +Rhys Ifans,1967,Snowden +Frank Grillo,1965,The Purge: Election Year +Elizabeth Mitchell,1970,The Purge: Election Year +Mykelti Williamson,1957,The Purge: Election Year +Edwin Hodge,1985,The Purge: Election Year +Kyle Secor,1957,The Purge: Election Year +Barry Nolan,1947,The Purge: Election Year +Miles Teller,1987,Bleed for This +Katey Sagal,1954,Bleed for This +Aaron Eckhart,1968,Bleed for This +Ted Levine,1957,Bleed for This +Tina Casciani,1982,Bleed for This +Jesse Eisenberg,1983,Now You See Me 2 +Mark Ruffalo,1967,Now You See Me 2 +Woody Harrelson,1961,Now You See Me 2 +Dave Franco,1985,Now You See Me 2 +Daniel Radcliffe,1989,Now You See Me 2 +Lizzy Caplan,1982,Now You See Me 2 +Jay Chou,1979,Now You See Me 2 +Sanaa Lathan,1971,Now You See Me 2 +Michael Caine,1933,Now You See Me 2 +Matthew McConaughey,1969,Free State of Jones +Gugu Mbatha-Raw,1983,Free State of Jones +Mahershala Ali,1974,Free State of Jones +Keri Russell,1976,Free State of Jones +Sean Bridgers,1968,Free State of Jones +Jacob Lofland,1996,Free State of Jones +Gemma Jones,1942,Bridget Jones's Baby +Jim Broadbent,1949,Bridget Jones's Baby +Sally Phillips,1970,Bridget Jones's Baby +Julian Rhind-Tutt,1968,Bridget Jones's Baby +Shirley Henderson,1965,Bridget Jones's Baby +Ben Willbond,1973,Bridget Jones's Baby +Paul Bentall,1948,Bridget Jones's Baby +Colin Firth,1960,Bridget Jones's Baby +Josh Brener,1984,Max Steel +Maria Bello,1967,Max Steel +Andy Garcia,1956,Max Steel +Phillip DeVona,1970,Max Steel +Billy Slaughter,1980,Max Steel +Megan Fox,1986,Teenage Mutant Ninja Turtles: Out of the Shadows +Will Arnett,1970,Teenage Mutant Ninja Turtles: Out of the Shadows +Laura Linney,1964,Teenage Mutant Ninja Turtles: Out of the Shadows +Stephen Amell,1981,Teenage Mutant Ninja Turtles: Out of the Shadows +Noel Fisher,1984,Teenage Mutant Ninja Turtles: Out of the Shadows +Jeremy Howard,1981,Teenage Mutant Ninja Turtles: Out of the Shadows +Pete Ploszek,1987,Teenage Mutant Ninja Turtles: Out of the Shadows +Alan Ritchson,1984,Teenage Mutant Ninja Turtles: Out of the Shadows +Tyler Perry,1969,Teenage Mutant Ninja Turtles: Out of the Shadows +Zach Woods,1984,Mascots +Wayne Wilderson,1966,Mascots +Michael Hitchcock,1958,Mascots +Parker Posey,1968,Mascots +Chris O'Dowd,1979,Mascots +Min-hee Kim,1982,Ah-ga-ssi +Kim Tae-ri,1990,Ah-ga-ssi +Jung-woo Ha,1978,Ah-ga-ssi +Jin-woong Jo,1976,Ah-ga-ssi +So-ri Moon,1974,Ah-ga-ssi +Hae-suk Kim,1955,Ah-ga-ssi +Neil deGrasse Tyson,1958,Ice Age: Collision Course +Adam Devine,1983,Ice Age: Collision Course +Jesse Tyler Ferguson,1975,Ice Age: Collision Course +Max Greenfield,1980,Ice Age: Collision Course +Jessie J,1988,Ice Age: Collision Course +Queen Latifah,1970,Ice Age: Collision Course +Denis Leary,1957,Ice Age: Collision Course +John Krasinski,1979,13 Hours +James Badge Dale,1978,13 Hours +Pablo Schreiber,1978,13 Hours +David Denman,1973,13 Hours +Dominic Fumusa,1969,13 Hours +Max Martini,1969,13 Hours +Peyman Moaadi,1970,13 Hours +Ryan Gosling,1980,La La Land +Emma Stone,1988,La La Land +Cameron McKendry,1996,I'm Not Ashamed +Victoria Staley,1996,I'm Not Ashamed +Taylor Kalupa,1990,I'm Not Ashamed +Hailee Steinfeld,1996,The Edge of Seventeen +Haley Lu Richardson,1995,The Edge of Seventeen +Blake Jenner,1992,The Edge of Seventeen +Kyra Sedgwick,1965,The Edge of Seventeen +Woody Harrelson,1961,The Edge of Seventeen +Hayden Szeto,1985,The Edge of Seventeen +Eric Keenleyside,1957,The Edge of Seventeen +Matt Damon,1970,Jason Bourne +Tommy Lee Jones,1946,Jason Bourne +Alicia Vikander,1988,Jason Bourne +Vincent Cassel,1966,Jason Bourne +Julia Stiles,1981,Jason Bourne +Riz Ahmed,1982,Jason Bourne +Ato Essandoh,1972,Jason Bourne +Sasha Lane,1995,American Honey +Shia LaBeouf,1986,American Honey +Riley Keough,1989,American Honey +Jason Statham,1967,Mechanic: Resurrection +Jessica Alba,1981,Mechanic: Resurrection +Tommy Lee Jones,1946,Mechanic: Resurrection +Michelle Yeoh,1962,Mechanic: Resurrection +Sam Hazeldine,1972,Mechanic: Resurrection +John Cenatiempo,1963,Mechanic: Resurrection +Yoo Gong,1979,Busanhaeng +Yu-mi Jeong,1983,Busanhaeng +Dong-seok Ma,1971,Busanhaeng +Woo-sik Choi,1990,Busanhaeng +Sohee,1992,Busanhaeng +Sacha Baron Cohen,1971,Grimsby +Rebel Wilson,1980,Grimsby +Mark Strong,1963,Grimsby +Lex Shrapnel,1979,Grimsby +Isla Fisher,1976,Grimsby +Dale Dickey,1961,Hell or High Water +Ben Foster,1980,Hell or High Water +Chris Pine,1980,Hell or High Water +Buck Taylor,1938,Hell or High Water +Jeff Bridges,1949,Hell or High Water +Gil Birmingham,1953,Hell or High Water +John Goodman,1952,10 Cloverfield Lane +Mary Elizabeth Winstead,1984,10 Cloverfield Lane +John Gallagher Jr.,1984,10 Cloverfield Lane +Douglas M. Griffin,1966,10 Cloverfield Lane +Suzanne Cryer,1967,10 Cloverfield Lane +Bradley Cooper,1975,10 Cloverfield Lane +Chris Pine,1980,The Finest Hours +Casey Affleck,1975,The Finest Hours +Ben Foster,1980,The Finest Hours +Eric Bana,1968,The Finest Hours +Holliday Grainger,1988,The Finest Hours +John Ortiz,1968,The Finest Hours +Kyle Gallner,1986,The Finest Hours +Graham McTavish,1961,The Finest Hours diff --git a/resources/movies.csv b/resources/movies.csv new file mode 100644 index 0000000..2003009 --- /dev/null +++ b/resources/movies.csv @@ -0,0 +1,285 @@ +Guardians of the Galaxy,Action,704613,8.1,2014 +Interstellar,Adventure,961763,8.6,2014 +The Grand Budapest Hotel,Adventure,492158,8.1,2014 +Gone Girl,Crime,589116,8.1,2014 +The Imitation Game,Biography,489175,8.1,2014 +John Wick,Action,263235,7.2,2014 +X-Men: Days of Future Past,Action,524078,8.0,2014 +Ouija: Origin of Evil,Horror,6144,6.6,2016 +The Equalizer,Action,235859,7.2,2014 +Maleficent,Action,257707,7.0,2014 +Whiplash,Drama,420586,8.5,2014 +Fury,Action,312068,7.6,2014 +Kingsman: The Secret Service,Action,410076,7.7,2014 +It Follows,Horror,120440,6.9,2014 +Edge of Tomorrow,Action,443643,7.9,2014 +The Theory of Everything,Biography,275057,7.7,2014 +The Babadook,Drama,120497,6.8,2014 +Divergent,Adventure,347499,6.7,2014 +Sin City: A Dame to Kill For,Action,114899,6.5,2014 +Need for Speed,Action,139153,6.5,2014 +The Amazing Spider-Man 2,Action,326998,6.7,2014 +Dracula Untold,Action,141759,6.3,2014 +The Book of Life,Animation,47048,7.3,2014 +The Purge: Anarchy,Action,99115,6.5,2014 +Captain America: The Winter Soldier,Action,510608,7.8,2014 +Transformers: Age of Extinction,Action,246318,5.7,2014 +Teenage Mutant Ninja Turtles,Action,172213,5.9,2014 +Lucifer,Crime,58703,8.3,2015 +The Hobbit: The Battle of the Five Armies,Adventure,363557,7.4,2014 +Clown,Horror,12690,5.7,2014 +Birdman or (The Unexpected Virtue of Ignorance),Comedy,408054,7.8,2014 +The Evil Dead,Horror,144850,7.6,1981 +The Giver,Drama,88897,6.5,2014 +What We Do in the Shadows,Comedy,73230,7.6,2014 +Shin Gojira,Action,2915,7.6,2016 +The Maze Runner,Action,318953,6.8,2014 +Nightcrawler,Crime,304445,7.9,2014 +The Fault in Our Stars,Drama,256899,7.8,2014 +Hercules,Action,117904,6.0,2014 +Neighbors,Comedy,224378,6.4,2014 +The Expendables 3,Action,130394,6.1,2014 +The Lego Movie,Animation,251297,7.8,2014 +"Love Rosie",Comedy,72052,7.2,2014 +Boyhood,Drama,272604,7.9,2014 +American Sniper,Action,333228,7.3,2014 +Big Hero 6,Animation,288143,7.9,2014 +The Hunger Games: Mockingjay - Part 1,Adventure,313261,6.7,2014 +Noah,Action,203170,5.8,2014 +Predestination,Drama,172521,7.4,2014 +Unbroken,Biography,106707,7.2,2014 +Big Eyes,Biography,58303,7.0,2014 +I Origins,Drama,78760,7.3,2014 +Inherent Vice,Comedy,64396,6.7,2014 +Relatos salvajes,Comedy,96058,8.1,2014 +The Taking,Horror,16365,6.0,2014 +300: Rise of an Empire,Action,229109,6.2,2014 +22 Jump Street,Action,265037,7.1,2014 +The Best of Me,Drama,45116,6.7,2014 +Vampire Academy,Action,42267,5.6,2014 +Seventh Son,Action,56323,5.5,2014 +Deliver Us from Evil,Horror,55582,6.2,2014 +Dawn of the Planet of the Apes,Action,323652,7.6,2014 +Exodus: Gods and Kings,Action,131331,6.1,2014 +Annabelle,Horror,85055,5.4,2014 +The Loft,Mystery,35542,6.3,2014 +Tusk,Comedy,32399,5.4,2014 +The Interview,Comedy,251910,6.6,2014 +The Voices,Comedy,36321,6.3,2014 +"I Frankenstein",Action,67009,5.1,2014 +The Judge,Crime,140247,7.4,2014 +Burnt,Comedy,66999,6.6,2015 +If I Stay,Drama,86751,6.8,2014 +"As Above So Below",Horror,52008,6.2,2014 +3 Days to Kill,Action,70604,6.2,2014 +Before We Go,Comedy,27217,6.9,2014 +Non-Stop,Action,204451,7.0,2014 +That Awkward Moment,Comedy,78562,6.2,2014 +Vincent,Short,18284,8.4,1982 +Blended,Comedy,89403,6.5,2014 +The Guest,Action,65590,6.7,2014 +The Other Woman,Comedy,106752,6.0,2014 +Left Behind,Action,25659,3.1,2014 +Annie,Comedy,25486,5.3,2014 +Cybernatural,Horror,46643,5.7,2014 +The Gambler,Crime,49667,6.0,2014 +The Salvation,Drama,24481,6.8,2014 +A Million Ways to Die in the West,Comedy,138651,6.1,2014 +Testament of Youth,Biography,15500,7.3,2014 +Hunt for the Wilderpeople,Adventure,22696,8.0,2016 +Transcendence,Drama,176429,6.3,2014 +Sex Tape,Comedy,86018,5.1,2014 +Honeytrap,Crime,325,5.6,2014 +X+Y,Drama,19609,7.2,2014 +Creep,Horror,14696,6.2,2014 +The Drop,Crime,109038,7.1,2014 +Ich seh ich seh,Drama,22916,6.7,2014 +Eliza Graves,Drama,36059,6.8,2014 +Horrible Bosses 2,Comedy,117742,6.3,2014 +Jack Ryan: Shadow Recruit,Action,100550,6.2,2014 +Man Down,Drama,414,6.5,2015 +Star Wars: Episode VII - The Force Awakens,Action,594763,8.2,2015 +The Revenant,Adventure,441653,8.1,2015 +Joy,Biography,83047,6.6,2015 +Spectre,Action,287494,6.8,2015 +The VVitch: A New-England Folktale,Horror,79688,6.7,2015 +The Big Short,Biography,206783,7.8,2015 +Jurassic World,Action,429565,7.0,2015 +Mad Max: Fury Road,Action,578046,8.1,2015 +The Martian,Adventure,506641,8.0,2015 +The Hateful Eight,Crime,294908,7.9,2015 +The Danish Girl,Biography,92054,7.0,2015 +Sicario,Action,212880,7.6,2015 +Avengers: Age of Ultron,Action,479512,7.5,2015 +Spotlight,Biography,220052,8.1,2015 +Room,Drama,182004,8.2,2015 +Fifty Shades of Grey,Drama,224710,4.1,2015 +Ex Machina,Drama,304335,7.7,2015 +The Lobster,Comedy,90934,7.1,2015 +Mr. Right,Action,22752,6.3,2015 +Mission: Impossible - Rogue Nation,Action,241599,7.4,2015 +The Hunger Games: Mockingjay - Part 2,Adventure,179028,6.6,2015 +P.S. I Love You,Drama,170878,7.1,2007 +Black Mass,Biography,123475,7.0,2015 +In the Heart of the Sea,Action,79623,6.9,2015 +By the Sea,Drama,9662,5.3,2015 +Knock Knock,Drama,47192,4.9,2015 +Inside Out,Animation,367848,8.2,2015 +Straight Outta Compton,Biography,127258,7.9,2015 +Ant-Man,Action,331498,7.4,2015 +The Invitation,Thriller,28593,6.7,2015 +Brooklyn,Drama,82468,7.5,2015 +Bridge of Spies,Drama,192125,7.6,2015 +Cinderella,Drama,107389,7.0,2015 +Green Room,Crime,41386,7.1,2015 +Steve Jobs,Biography,102485,7.2,2015 +Krampus,Comedy,33237,6.2,2015 +Furious Seven,Action,284316,7.2,2015 +Crimson Peak,Drama,88501,6.6,2015 +Fathers & Daughters,Drama,12242,7.1,2015 +Terminator Genisys,Action,194182,6.5,2015 +Legends of Tomorrow,Action,42082,7.1,2016 +Spy,Action,176403,7.1,2015 +Eye in the Sky,Drama,44086,7.3,2015 +Everest,Adventure,142142,7.1,2015 +The Man Who Knew Infinity,Biography,18444,7.2,2015 +The Visit,Horror,70878,6.2,2015 +Carol,Drama,66848,7.2,2015 +"Sister Sister",Comedy,8021,6.1,1994 +The Intern,Comedy,142646,7.2,2015 +Demolition,Drama,42400,7.0,2015 +Goosebumps,Adventure,51870,6.3,2015 +Hardcore Henry,Action,48460,6.8,2015 +Desierto,Drama,2618,5.8,2015 +The Unspoken,Thriller,423,4.9,2015 +Trainwreck,Comedy,98763,6.3,2015 +Creed,Drama,157387,7.7,2015 +Southpaw,Drama,154514,7.4,2015 +Victor Frankenstein,Drama,32516,6.0,2015 +Pitch Perfect 2,Comedy,101190,6.5,2015 +Colonia,Drama,22234,7.1,2015 +San Andreas,Action,152249,6.1,2015 +The Age of Adaline,Drama,99943,7.2,2015 +Point Break,Action,38527,5.3,2015 +Into the Forest,Drama,5638,6.0,2015 +Hotel Transylvania 2,Animation,61666,6.7,2015 +Pan,Adventure,43442,5.8,2015 +The Dressmaker,Drama,25597,7.1,2015 +Maze Runner: The Scorch Trials,Action,146153,6.4,2015 +Youth,Comedy,45576,7.4,2015 +Vacation,Adventure,69598,6.1,2015 +Bone Tomahawk,Adventure,38814,7.1,2015 +Scouts Guide to the Zombie Apocalypse,Action,28124,6.3,2015 +Tomorrowland,Action,132793,6.5,2015 +The Gift,Mystery,86698,7.1,2015 +The Last Witch Hunter,Action,63434,6.0,2015 +Insurgent,Adventure,160386,6.3,2015 +The DUFF,Comedy,53584,6.5,2015 +The Good Dinosaur,Animation,68749,6.8,2015 +Focus,Comedy,155186,6.6,2015 +Jupiter Ascending,Action,142878,5.4,2015 +Fantastic Four,Action,114717,4.3,2015 +Concussion,Biography,51021,7.1,2015 +Muhammad: The Messenger of God,Biography,4266,8.1,2015 +Equals,Drama,10357,6.1,2015 +Magic Mike XXL,Comedy,39910,5.7,2015 +The Longest Ride,Drama,53050,7.1,2015 +Daddy's Home,Comedy,59706,6.1,2015 +Knight of Cups,Drama,14456,5.7,2015 +The Ridiculous 6,Comedy,28505,4.9,2015 +Ted 2,Comedy,123758,6.4,2015 +Minions,Animation,148448,6.4,2015 +Macbeth,Drama,37044,6.7,2015 +Burnt,Comedy,67059,6.6,2015 +The Final Girls,Comedy,20599,6.6,2015 +A Bigger Splash,Crime,10315,6.4,2015 +Chappie,Action,177519,6.9,2015 +En man som heter Ove,Comedy,7806,7.6,2015 +Doctor Strange,Action,71532,8.0,2016 +Jack Reacher: Never Go Back,Action,15821,6.4,2016 +Inferno,Action,28671,6.4,2016 +Sausage Party,Animation,61333,6.5,2016 +The Accountant,Action,27335,7.7,2016 +Nocturnal Animals,Drama,2060,8.1,2016 +The Girl on the Train,Drama,25170,6.7,2016 +Suicide Squad,Action,249658,6.6,2016 +Ouija: Origin of Evil,Horror,6973,6.5,2016 +Boo! A Madea Halloween,Comedy,1851,4.7,2016 +Split,Horror,331,8.1,2016 +Miss Peregrine's Home for Peculiar Children,Adventure,31522,7.0,2016 +Keeping Up with the Joneses,Action,1964,5.8,2016 +Hacksaw Ridge,Biography,6904,8.8,2016 +Trolls,Animation,3307,6.7,2016 +Captain Fantastic,Comedy,30203,8.0,2016 +Star Trek Beyond,Action,111125,7.2,2016 +Bad Moms,Comedy,30510,6.3,2016 +Ghostbusters,Action,111399,5.4,2016 +Arrival,Drama,2396,8.5,2016 +Don't Breathe,Crime,55841,7.3,2016 +Nerve,Adventure,37601,6.7,2016 +Finding Dory,Animation,87660,7.6,2016 +Gods of Egypt,Action,60592,5.5,2016 +X-Men: Apocalypse,Action,210509,7.2,2016 +Deadpool,Action,533503,8.1,2016 +Independence Day: Resurgence,Action,93353,5.4,2016 +The BFG,Adventure,21997,6.6,2016 +Anthropoid,Biography,8307,7.3,2016 +The Magnificent Seven,Action,35670,7.1,2016 +Deepwater Horizon,Action,19531,7.5,2016 +The Infiltrator,Biography,20436,7.1,2016 +Lights Out,Horror,43426,6.5,2016 +In a Valley of Violence,Western,1942,6.0,2016 +Zootopia,Animation,216939,8.1,2016 +The Legend of Tarzan,Action,80332,6.4,2016 +Warcraft,Action,154327,7.1,2016 +Captain America: Civil War,Action,340867,8.0,2016 +Billy Lynn's Long Halftime Walk,Drama,348,6.4,2016 +Moonlight,Drama,2184,8.7,2016 +American Pastoral,Crime,742,6.2,2016 +Me Before You,Drama,82868,7.5,2016 +Batman v Superman: Dawn of Justice,Action,415255,6.8,2016 +Alice Through the Looking Glass,Adventure,37184,6.3,2016 +Shivaay,Action,4948,7.2,2016 +King Cobra,Crime,928,5.8,2016 +The Conjuring 2,Horror,108550,7.5,2016 +Sully,Biography,34289,7.9,2016 +Mike and Dave Need Wedding Dates,Adventure,30530,6.1,2016 +The Whole Truth,Drama,3888,6.0,2016 +Imperium,Crime,14516,6.5,2016 +Neruda,Biography,812,7.3,2016 +Masterminds,Action,3254,5.8,2016 +Nine Lives,Comedy,5010,5.0,2016 +The Nice Guys,Action,116011,7.4,2016 +Swiss Army Man,Adventure,32284,7.2,2016 +Central Intelligence,Action,66143,6.4,2016 +The Siege of Jadotville,Action,6428,7.4,2016 +The Shallows,Drama,54247,6.4,2016 +The Jungle Book,Adventure,151342,7.6,2016 +Blood Father,Action,25837,6.5,2016 +The Secret Life of Pets,Animation,63437,6.7,2016 +Storks,Animation,5798,7.0,2016 +The Neon Demon,Horror,29895,6.4,2016 +Tokyo Zance,Comedy,5,5.4,2001 +Snowden,Biography,12257,7.4,2016 +The Purge: Election Year,Action,38837,6.0,2016 +Bleed for This,Biography,382,6.5,2016 +Now You See Me 2,Action,112664,6.5,2016 +Free State of Jones,Action,16935,6.9,2016 +Bridget Jones's Baby,Comedy,16892,7.3,2016 +Max Steel,Action,897,4.9,2016 +Teenage Mutant Ninja Turtles: Out of the Shadows,Action,45213,6.1,2016 +Mascots,Comedy,3147,5.9,2016 +Ah-ga-ssi,Drama,7135,8.1,2016 +Ice Age: Collision Course,Animation,21966,5.7,2016 +13 Hours,Action,59515,7.3,2016 +La La Land,Comedy,1473,8.6,2016 +I'm Not Ashamed,Biography,345,6.1,2016 +The Edge of Seventeen,Comedy,305,7.7,2016 +Jason Bourne,Action,87772,6.8,2016 +American Honey,Drama,2493,7.5,2016 +Mechanic: Resurrection,Action,18817,5.7,2016 +Busanhaeng,Action,22742,7.6,2016 +Grimsby,Action,50897,6.2,2016 +Hell or High Water,Crime,15385,8.2,2016 +10 Cloverfield Lane,Drama,149415,7.3,2016 +The Finest Hours,Action,34666,6.8,2016 \ No newline at end of file diff --git a/resources/populate_graph.py b/resources/populate_graph.py new file mode 100755 index 0000000..2c06f01 --- /dev/null +++ b/resources/populate_graph.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 + +from falkordb import FalkorDB, Node, Edge +import os +import csv + +db = FalkorDB() + +graph = db.select_graph("imdb") + +if graph.name in db.list_graphs(): + exit(0) + +movies = {} +node_count = 0 + +with open(os.path.dirname(os.path.abspath(__file__)) + '/movies.csv', 'r') as f: + reader = csv.reader(f, delimiter=',') + for row in reader: + title = row[0] + genre = row[1] + votes = int(row[2]) + rating = float(row[3]) + year = int(row[4]) + + node = Node(alias=f"n_{node_count}", labels="movie", properties={'title': title, + 'genre': genre, + 'votes': votes, + 'rating': rating, + 'year': year}) + movies[title] = node + node_count += 1 + +# Load actors entities +actors = {} + +edges = [] +with open(os.path.dirname(os.path.abspath(__file__)) + '/actors.csv', 'r') as f: + reader = csv.reader(f, delimiter=',') + for row in reader: + name = row[0] + yearOfBirth = int(row[1]) + movie = row[2] + # All age calculations are done where 2019 is the the current year. + age = 2019 - yearOfBirth + + if name not in actors: + node = Node(alias=f"n_{node_count}", labels="actor", properties={'name': name, 'age': age}) + actors[name] = node + node_count += 1 + + if movie in movies: + edges.append(Edge(actors[name], "act", movies[movie])) + +edges_str = [str(e) for e in edges] +nodes_str = [str(n) for n in actors.values()] + [str(n) for n in movies.values()] +graph.query(f"CREATE {','.join(nodes_str + edges_str)}") + +graph.create_node_fulltext_index("actor", "name") diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..99c6ded --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +fn_params_layout = "Vertical" \ No newline at end of file diff --git a/src/client/blocking.rs b/src/client/blocking.rs new file mode 100644 index 0000000..70c0ad2 --- /dev/null +++ b/src/client/blocking.rs @@ -0,0 +1,451 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +use crate::{ + client::FalkorClientProvider, + connection::blocking::{BorrowedSyncConnection, FalkorSyncConnection}, + parser::utils::string_vec_from_val, + ConfigValue, FalkorConnectionInfo, FalkorDBError, FalkorResult, FalkorValue, SyncGraph, +}; +use parking_lot::{Mutex, RwLock}; +use std::{ + collections::HashMap, + sync::{mpsc, Arc}, +}; + +pub(crate) struct FalkorSyncClientInner { + _inner: Mutex, + + connection_pool_size: u8, + connection_pool_tx: RwLock>, + connection_pool_rx: Mutex>, +} + +impl FalkorSyncClientInner { + pub(crate) fn borrow_connection( + &self, + pool_owner: Arc, + ) -> FalkorResult { + Ok(BorrowedSyncConnection::new( + self.connection_pool_rx + .lock() + .recv() + .map_err(|_| FalkorDBError::EmptyConnection)?, + self.connection_pool_tx.read().clone(), + pool_owner, + )) + } + + pub(crate) fn get_connection(&self) -> FalkorResult { + self._inner.lock().get_connection() + } +} + +#[cfg(feature = "redis")] +fn is_sentinel(conn: &mut FalkorSyncConnection) -> FalkorResult { + let info_map = conn.get_redis_info(Some("server"))?; + Ok(info_map + .get("redis_mode") + .map(|redis_mode| redis_mode == "sentinel") + .unwrap_or_default()) +} + +#[cfg(feature = "redis")] +pub(crate) fn get_sentinel_client( + client: &mut FalkorClientProvider, + connection_info: &redis::ConnectionInfo, +) -> FalkorResult> { + let mut conn = client.get_connection()?; + if !is_sentinel(&mut conn)? { + return Ok(None); + } + + // This could have been so simple using the Sentinel API, but it requires a service name + // Perhaps in the future we can use it if we only support the master instance to be called 'master'? + let sentinel_masters = conn + .execute_command(None, "SENTINEL", Some("MASTERS"), None)? + .into_vec()?; + + if sentinel_masters.len() != 1 { + return Err(FalkorDBError::SentinelMastersCount); + } + + let sentinel_master: HashMap<_, _> = sentinel_masters + .into_iter() + .next() + .ok_or(FalkorDBError::SentinelMastersCount)? + .into_vec()? + .chunks_exact(2) + .flat_map(|chunk| TryInto::<[FalkorValue; 2]>::try_into(chunk.to_vec())) + .flat_map(|[key, val]| { + Result::<_, FalkorDBError>::Ok((key.into_string()?, val.into_string()?)) + }) + .collect(); + + let name = sentinel_master + .get("name") + .ok_or(FalkorDBError::SentinelMastersCount)?; + + Ok(Some( + redis::sentinel::SentinelClient::build( + vec![connection_info.to_owned()], + name.to_string(), + Some(redis::sentinel::SentinelNodeConnectionInfo { + tls_mode: match connection_info.addr { + redis::ConnectionAddr::TcpTls { insecure: true, .. } => { + Some(redis::TlsMode::Insecure) + } + redis::ConnectionAddr::TcpTls { + insecure: false, .. + } => Some(redis::TlsMode::Secure), + _ => None, + }, + redis_connection_info: Some(connection_info.redis.clone()), + }), + redis::sentinel::SentinelServerType::Master, + ) + .map_err(|err| FalkorDBError::SentinelConnection(err.to_string()))?, + )) +} + +/// This is the publicly exposed API of the sync Falkor Client +/// It makes no assumptions in regard to which database the Falkor module is running on, +/// and will select it based on enabled features and url connection +/// +/// # Thread Safety +/// This struct is fully thread safe, it can be cloned and passed between threads without constraints, +/// Its API uses only immutable references +#[derive(Clone)] +pub struct FalkorSyncClient { + inner: Arc, + pub(crate) _connection_info: FalkorConnectionInfo, +} + +impl FalkorSyncClient { + pub(crate) fn create( + mut client: FalkorClientProvider, + connection_info: FalkorConnectionInfo, + num_connections: u8, + ) -> FalkorResult { + let (connection_pool_tx, connection_pool_rx) = mpsc::sync_channel(num_connections as usize); + + // One already exists + for _ in 0..num_connections { + let new_conn = client + .get_connection() + .map_err(|err| FalkorDBError::RedisError(err.to_string()))?; + + connection_pool_tx + .send(new_conn) + .map_err(|_| FalkorDBError::EmptyConnection)?; + } + + Ok(Self { + inner: Arc::new(FalkorSyncClientInner { + _inner: client.into(), + connection_pool_size: num_connections, + connection_pool_tx: RwLock::new(connection_pool_tx), + connection_pool_rx: Mutex::new(connection_pool_rx), + }), + _connection_info: connection_info, + }) + } + + /// Get the max number of connections in the client's connection pool + pub fn connection_pool_size(&self) -> u8 { + self.inner.connection_pool_size + } + + pub(crate) fn borrow_connection(&self) -> FalkorResult { + self.inner.borrow_connection(self.inner.clone()) + } + + /// Return a list of graphs currently residing in the database + /// + /// # Returns + /// A [`Vec`] of [`String`]s, containing the names of available graphs + pub fn list_graphs(&self) -> FalkorResult> { + let mut conn = self.borrow_connection()?; + conn.execute_command(None, "GRAPH.LIST", None, None) + .and_then(string_vec_from_val) + } + + /// Return the current value of a configuration option in the database. + /// + /// # Arguments + /// * `config_Key`: A [`String`] representation of a configuration's key. + /// The config key can also be "*", which will return ALL the configuration options. + /// + /// # Returns + /// A [`HashMap`] comprised of [`String`] keys, and [`ConfigValue`] values. + pub fn config_get( + &self, + config_key: &str, + ) -> FalkorResult> { + let mut conn = self.borrow_connection()?; + let config = conn + .execute_command(None, "GRAPH.CONFIG", Some("GET"), Some(&[config_key]))? + .into_vec()?; + + if config.len() == 2 { + let [key, val]: [FalkorValue; 2] = config.try_into().map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 2 elements for configuration option".to_string(), + ) + })?; + + return Ok(HashMap::from([( + key.into_string()?, + ConfigValue::try_from(val)?, + )])); + } + + Ok(config + .into_iter() + .flat_map(|config| { + let [key, val]: [FalkorValue; 2] = config.into_vec()?.try_into().map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 2 elements for configuration option".to_string(), + ) + })?; + + Result::<_, FalkorDBError>::Ok((key.into_string()?, ConfigValue::try_from(val)?)) + }) + .collect::>()) + } + + /// Return the current value of a configuration option in the database. + /// + /// # Arguments + /// * `config_Key`: A [`String`] representation of a configuration's key. + /// The config key can also be "*", which will return ALL the configuration options. + /// * `value`: The new value to set, which is anything that can be converted into a [`ConfigValue`], namely string types and i64. + pub fn config_set>( + &self, + config_key: &str, + value: C, + ) -> FalkorResult { + self.borrow_connection()?.execute_command( + None, + "GRAPH.CONFIG", + Some("SET"), + Some(&[config_key, value.into().to_string().as_str()]), + ) + } + + /// Opens a graph context for queries and operations + /// + /// # Arguments + /// * `graph_name`: A string identifier of the graph to open. + /// + /// # Returns + /// a [`SyncGraph`] object, allowing various graph operations. + pub fn select_graph( + &self, + graph_name: T, + ) -> SyncGraph { + SyncGraph::new(self.inner.clone(), graph_name) + } + + /// Copies an entire graph and returns the [`SyncGraph`] for the new copied graph. + /// + /// # Arguments + /// * `graph_to_clone`: A string identifier of the graph to copy. + /// * `new_graph_name`: The name to give the new graph. + /// + /// # Returns + /// If successful, will return the new [`SyncGraph`] object. + pub fn copy_graph( + &self, + graph_to_clone: &str, + new_graph_name: &str, + ) -> FalkorResult { + self.borrow_connection()?.execute_command( + Some(graph_to_clone), + "GRAPH.COPY", + None, + Some(&[new_graph_name]), + )?; + Ok(self.select_graph(new_graph_name)) + } + + #[cfg(feature = "redis")] + /// Retrieves redis information + pub fn redis_info( + &self, + section: Option<&str>, + ) -> FalkorResult> { + self.borrow_connection()? + .as_inner()? + .get_redis_info(section) + } +} + +#[cfg(test)] +pub(crate) fn create_empty_inner_client() -> Arc { + let (tx, rx) = mpsc::sync_channel(1); + tx.send(FalkorSyncConnection::None).ok(); + Arc::new(FalkorSyncClientInner { + _inner: Mutex::new(FalkorClientProvider::None), + connection_pool_size: 0, + connection_pool_tx: RwLock::new(tx), + connection_pool_rx: Mutex::new(rx), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + test_utils::{create_test_client, TestSyncGraphHandle}, + FalkorClientBuilder, + }; + use std::{mem, num::NonZeroU8, sync::mpsc::TryRecvError, thread}; + + #[test] + fn test_borrow_connection() { + let client = FalkorClientBuilder::new() + .with_num_connections(NonZeroU8::new(6).expect("Could not create a perfectly valid u8")) + .build() + .expect("Could not create client for this test"); + + // Client was created with 6 connections + let _conn_vec: Vec> = (0..6) + .map(|_| { + let conn = client.borrow_connection(); + assert!(conn.is_ok()); + conn + }) + .collect(); + + let non_existing_conn = client.inner.connection_pool_rx.lock().try_recv(); + assert!(non_existing_conn.is_err()); + + let Err(TryRecvError::Empty) = non_existing_conn else { + panic!("Got error, but not a TryRecvError::Empty, as expected"); + }; + } + + #[test] + fn test_list_graphs() { + let client = create_test_client(); + let res = client.list_graphs(); + assert!(res.is_ok()); + + let graphs = res.unwrap(); + assert_eq!(graphs[0], "imdb"); + } + + #[test] + fn test_select_graph_and_query() { + let client = create_test_client(); + + let mut graph = client.select_graph("imdb"); + assert_eq!(graph.graph_name(), "imdb".to_string()); + + let res = graph + .query("MATCH (a:actor) return a") + .execute() + .expect("Could not get actors from unmodified graph"); + + assert_eq!(res.data.len(), 1317); + } + + #[test] + fn test_copy_graph() { + let client = create_test_client(); + + client.select_graph("imdb_ro_copy").delete().ok(); + + let graph = client.copy_graph("imdb", "imdb_ro_copy"); + assert!(graph.is_ok()); + + let mut graph = TestSyncGraphHandle { + inner: graph.unwrap(), + }; + + let mut original_graph = client.select_graph("imdb"); + + assert_eq!( + graph + .inner + .query("MATCH (a:actor) RETURN a") + .execute() + .expect("Could not get actors from unmodified graph") + .data, + original_graph + .query("MATCH (a:actor) RETURN a") + .execute() + .expect("Could not get actors from unmodified graph") + .data + ) + } + + #[test] + fn test_get_config() { + let client = create_test_client(); + + let config = client + .config_get("QUERY_MEM_CAPACITY") + .expect("Could not get configuration"); + + assert_eq!(config.len(), 1); + assert!(config.contains_key("QUERY_MEM_CAPACITY")); + assert_eq!( + mem::discriminant(config.get("QUERY_MEM_CAPACITY").unwrap()), + mem::discriminant(&ConfigValue::Int64(0)) + ); + } + + #[test] + fn test_get_config_all() { + let client = create_test_client(); + let configuration = client.config_get("*").expect("Could not get configuration"); + assert_eq!( + configuration.get("THREAD_COUNT").cloned().unwrap(), + ConfigValue::Int64(thread::available_parallelism().unwrap().get() as i64) + ); + } + + #[test] + fn test_set_config() { + let client = create_test_client(); + + let config = client + .config_get("DELTA_MAX_PENDING_CHANGES") + .expect("Could not get configuration"); + + let current_val = config + .get("DELTA_MAX_PENDING_CHANGES") + .cloned() + .unwrap() + .as_i64() + .unwrap(); + + let desired_val = if current_val == 10000 { 50000 } else { 10000 }; + + client + .config_set("DELTA_MAX_PENDING_CHANGES", desired_val) + .expect("Could not set config value"); + + let new_config = client + .config_get("DELTA_MAX_PENDING_CHANGES") + .expect("Could not get configuration"); + + assert_eq!( + new_config + .get("DELTA_MAX_PENDING_CHANGES") + .cloned() + .unwrap() + .as_i64() + .unwrap(), + desired_val + ); + + client + .config_set("DELTA_MAX_PENDING_CHANGES", current_val) + .ok(); + } +} diff --git a/src/client/builder.rs b/src/client/builder.rs new file mode 100644 index 0000000..279f274 --- /dev/null +++ b/src/client/builder.rs @@ -0,0 +1,142 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +use crate::{ + client::FalkorClientProvider, FalkorConnectionInfo, FalkorDBError, FalkorResult, + FalkorSyncClient, +}; +use std::num::NonZeroU8; + +/// A Builder-pattern implementation struct for creating a new Falkor client. +pub struct FalkorClientBuilder { + connection_info: Option, + num_connections: NonZeroU8, +} + +impl FalkorClientBuilder { + /// Provide a connection info for the database connection + /// Will otherwise use the default connection details. + /// + /// # Arguments + /// * `falkor_connection_info`: the [`FalkorConnectionInfo`] to provide + /// + /// # Returns + /// The consumed and modified self. + pub fn with_connection_info( + self, + falkor_connection_info: FalkorConnectionInfo, + ) -> Self { + Self { + connection_info: Some(falkor_connection_info), + ..self + } + } + + /// Specify how large a connection pool to maintain, for concurrent operations. + /// + /// # Arguments + /// * `num_connections`: the numer of connections, a non-negative integer, between 1 and 32 + /// + /// # Returns + /// The consumed and modified self. + pub fn with_num_connections( + self, + num_connections: NonZeroU8, + ) -> Self { + Self { + num_connections, + ..self + } + } + + fn get_client>( + connection_info: T + ) -> FalkorResult { + let connection_info = connection_info + .try_into() + .map_err(|err| FalkorDBError::InvalidConnectionInfo(err.to_string()))?; + Ok(match connection_info { + #[cfg(feature = "redis")] + FalkorConnectionInfo::Redis(connection_info) => FalkorClientProvider::Redis { + client: redis::Client::open(connection_info.clone()) + .map_err(|err| FalkorDBError::RedisError(err.to_string()))?, + sentinel: None, + }, + }) + } +} + +impl FalkorClientBuilder<'S'> { + /// Creates a new [`FalkorClientBuilder`] for a sync client. + /// + /// # Returns + /// The new [`FalkorClientBuilder`] + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + FalkorClientBuilder { + connection_info: None, + num_connections: NonZeroU8::new(8).expect("Error creating perfectly valid u8"), + } + } + + /// Consume the builder, returning the newly constructed sync client + /// + /// # Returns + /// a new [`FalkorSyncClient`] + pub fn build(self) -> FalkorResult { + let connection_info = self + .connection_info + .unwrap_or("falkor://127.0.0.1:6379".try_into()?); + + let mut client = Self::get_client(connection_info.clone())?; + + #[cfg(feature = "redis")] + #[allow(irrefutable_let_patterns)] + if let FalkorConnectionInfo::Redis(redis_conn_info) = &connection_info { + if let Some(sentinel) = + super::blocking::get_sentinel_client(&mut client, redis_conn_info)? + { + client.set_sentinel(sentinel); + } + } + FalkorSyncClient::create(client, connection_info, self.num_connections.get()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sync_builder() { + let connection_info = "falkor://127.0.0.1:6379".try_into(); + assert!(connection_info.is_ok()); + + assert!(FalkorClientBuilder::new() + .with_connection_info(connection_info.unwrap()) + .build() + .is_ok()); + } + + #[test] + #[cfg(feature = "redis")] + fn test_sync_builder_redis_fallback() { + let client = FalkorClientBuilder::new().build(); + assert!(client.is_ok()); + + let FalkorConnectionInfo::Redis(redis_info) = client.unwrap()._connection_info; + assert_eq!(redis_info.addr.to_string().as_str(), "127.0.0.1:6379"); + } + + #[test] + fn test_connection_pool_size() { + let client = FalkorClientBuilder::new() + .with_num_connections(NonZeroU8::new(16).expect("Could not create a perfectly fine u8")) + .build(); + assert!(client.is_ok()); + + assert_eq!(client.unwrap().connection_pool_size(), 16); + } +} diff --git a/src/client/mod.rs b/src/client/mod.rs new file mode 100644 index 0000000..e5201b3 --- /dev/null +++ b/src/client/mod.rs @@ -0,0 +1,54 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +use crate::{connection::blocking::FalkorSyncConnection, FalkorDBError, FalkorResult}; + +pub(crate) mod blocking; +pub(crate) mod builder; + +#[allow(clippy::large_enum_variant)] +pub(crate) enum FalkorClientProvider { + #[allow(unused)] + None, + #[cfg(feature = "redis")] + Redis { + client: redis::Client, + sentinel: Option, + }, +} + +impl FalkorClientProvider { + pub(crate) fn get_connection(&mut self) -> FalkorResult { + Ok(match self { + #[cfg(feature = "redis")] + FalkorClientProvider::Redis { + sentinel: Some(sentinel), + .. + } => FalkorSyncConnection::Redis( + sentinel + .get_connection() + .map_err(|err| FalkorDBError::RedisError(err.to_string()))?, + ), + #[cfg(feature = "redis")] + FalkorClientProvider::Redis { client, .. } => FalkorSyncConnection::Redis( + client + .get_connection() + .map_err(|err| FalkorDBError::RedisError(err.to_string()))?, + ), + FalkorClientProvider::None => Err(FalkorDBError::UnavailableProvider)?, + }) + } + + #[cfg(feature = "redis")] + pub(crate) fn set_sentinel( + &mut self, + sentinel_client: redis::sentinel::SentinelClient, + ) { + match self { + FalkorClientProvider::Redis { sentinel, .. } => *sentinel = Some(sentinel_client), + FalkorClientProvider::None => {} + } + } +} diff --git a/src/connection/blocking.rs b/src/connection/blocking.rs new file mode 100644 index 0000000..1f65536 --- /dev/null +++ b/src/connection/blocking.rs @@ -0,0 +1,128 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +use crate::{client::blocking::FalkorSyncClientInner, FalkorDBError, FalkorResult, FalkorValue}; +use std::{ + collections::HashMap, + sync::{mpsc, Arc}, +}; + +pub(crate) enum FalkorSyncConnection { + #[allow(unused)] + None, + #[cfg(feature = "redis")] + Redis(redis::Connection), +} + +impl FalkorSyncConnection { + pub(crate) fn execute_command( + &mut self, + graph_name: Option<&str>, + command: &str, + subcommand: Option<&str>, + params: Option<&[&str]>, + ) -> FalkorResult { + match self { + #[cfg(feature = "redis")] + FalkorSyncConnection::Redis(redis_conn) => { + use redis::ConnectionLike as _; + let mut cmd = redis::cmd(command); + cmd.arg(subcommand); + cmd.arg(graph_name); + if let Some(params) = params { + for param in params { + cmd.arg(param.to_string()); + } + } + redis::FromRedisValue::from_owned_redis_value( + redis_conn + .req_command(&cmd) + .map_err(|err| match err.kind() { + redis::ErrorKind::IoError + | redis::ErrorKind::ClusterConnectionNotFound + | redis::ErrorKind::ClusterDown + | redis::ErrorKind::MasterDown => FalkorDBError::ConnectionDown, + _ => FalkorDBError::RedisError(err.to_string()), + })?, + ) + .map_err(|err| FalkorDBError::RedisParsingError(err.to_string())) + } + FalkorSyncConnection::None => Ok(FalkorValue::None), + } + } + + #[cfg(feature = "redis")] + pub(crate) fn get_redis_info( + &mut self, + section: Option<&str>, + ) -> FalkorResult> { + Ok(self + .execute_command(None, "INFO", section, None)? + .into_string()? + .split("\r\n") + .map(|info_item| info_item.split(':').collect::>()) + .flat_map(TryInto::<[&str; 2]>::try_into) + .map(|[key, val]| (key.to_string(), val.to_string())) + .collect()) + } +} + +/// A container for a connection that is borrowed from the pool. +/// Upon going out of scope, it will return the connection to the pool. +/// +/// This is publicly exposed for user-implementations of [`FalkorParsable`](crate::FalkorParsable) +pub struct BorrowedSyncConnection { + conn: Option, + return_tx: mpsc::SyncSender, + client: Arc, +} + +impl BorrowedSyncConnection { + pub(crate) fn new( + conn: FalkorSyncConnection, + return_tx: mpsc::SyncSender, + client: Arc, + ) -> Self { + Self { + conn: Some(conn), + return_tx, + client, + } + } + + pub(crate) fn as_inner(&mut self) -> FalkorResult<&mut FalkorSyncConnection> { + self.conn.as_mut().ok_or(FalkorDBError::EmptyConnection) + } + + pub(crate) fn execute_command( + &mut self, + graph_name: Option<&str>, + command: &str, + subcommand: Option<&str>, + params: Option<&[&str]>, + ) -> Result { + match self + .as_inner()? + .execute_command(graph_name, command, subcommand, params) + { + Err(FalkorDBError::ConnectionDown) => { + if let Ok(new_conn) = self.client.get_connection() { + self.conn = Some(new_conn); + return Err(FalkorDBError::ConnectionDown); + } + Err(FalkorDBError::NoConnection) + } + res => res, + } + } +} + +impl Drop for BorrowedSyncConnection { + fn drop(&mut self) { + if let Some(conn) = self.conn.take() { + self.return_tx.send(conn).ok(); + } + } +} diff --git a/src/connection/mod.rs b/src/connection/mod.rs new file mode 100644 index 0000000..2d11cf3 --- /dev/null +++ b/src/connection/mod.rs @@ -0,0 +1,6 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +pub(crate) mod blocking; diff --git a/src/connection_info/mod.rs b/src/connection_info/mod.rs new file mode 100644 index 0000000..417cd6b --- /dev/null +++ b/src/connection_info/mod.rs @@ -0,0 +1,154 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +use crate::{FalkorDBError, FalkorResult}; + +/// An agnostic container which allows maintaining of various connection details. +/// The different enum variants are enabled based on compilation features +#[derive(Clone, Debug)] +pub enum FalkorConnectionInfo { + #[cfg(feature = "redis")] + /// A Redis database connection + Redis(redis::ConnectionInfo), +} + +impl FalkorConnectionInfo { + fn fallback_provider(mut full_url: String) -> FalkorResult { + #[cfg(feature = "redis")] + Ok(FalkorConnectionInfo::Redis({ + if full_url.starts_with("falkor://") { + full_url = full_url.replace("falkor://", "redis://"); + } else if full_url.starts_with("falkors://") { + full_url = full_url.replace("falkors://", "rediss://"); + } + redis::IntoConnectionInfo::into_connection_info(full_url) + .map_err(|err| FalkorDBError::InvalidConnectionInfo(err.to_string()))? + })) + } + + /// Retrieves the internally stored address for this connection info + /// + /// # Returns + /// A [`String`] representation of the address and port, or a UNIX socket path + pub fn address(&self) -> String { + match self { + #[cfg(feature = "redis")] + FalkorConnectionInfo::Redis(redis_info) => redis_info.addr.to_string(), + } + } +} + +impl TryFrom<&str> for FalkorConnectionInfo { + type Error = FalkorDBError; + + fn try_from(value: &str) -> FalkorResult { + let (url, url_schema) = regex::Regex::new(r"^(?P[a-zA-Z][a-zA-Z0-9+\-.]*):") + .map_err(|err| FalkorDBError::ParsingError(format!("Error constructing regex: {err}")))? + .captures(value) + .and_then(|cap| cap.get(1)) + .map(|m| (value.to_string(), m.as_str())) + .unwrap_or((format!("falkor://{value}"), "falkor")); + + match url_schema { + "redis" | "rediss" => { + #[cfg(feature = "redis")] + return Ok(FalkorConnectionInfo::Redis( + redis::IntoConnectionInfo::into_connection_info(value) + .map_err(|err| FalkorDBError::InvalidConnectionInfo(err.to_string()))?, + )); + #[cfg(not(feature = "redis"))] + return Err(FalkorDBError::UnavailableProvider); + } + _ => FalkorConnectionInfo::fallback_provider(url), + } + } +} + +impl TryFrom for FalkorConnectionInfo { + type Error = FalkorDBError; + + #[inline] + fn try_from(value: String) -> FalkorResult { + Self::try_from(value.as_str()) + } +} + +impl TryFrom<(T, u16)> for FalkorConnectionInfo { + type Error = FalkorDBError; + + #[inline] + fn try_from(value: (T, u16)) -> FalkorResult { + Self::try_from(format!("{}:{}", value.0.to_string(), value.1)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{mem, str::FromStr}; + + #[test] + #[cfg(feature = "redis")] + fn test_redis_fallback_provider() { + let FalkorConnectionInfo::Redis(redis) = + FalkorConnectionInfo::fallback_provider("redis://127.0.0.1:6379".to_string()).unwrap(); + + assert_eq!(redis.addr.to_string(), "127.0.0.1:6379".to_string()); + } + + #[test] + #[cfg(feature = "redis")] + fn test_try_from_redis() { + let res = FalkorConnectionInfo::try_from("redis://0.0.0.0:1234"); + assert!(res.is_ok()); + + let redis_conn = res.unwrap(); + let raw_redis_conn = redis::ConnectionInfo::from_str("redis://0.0.0.0:1234").unwrap(); + assert_eq!( + mem::discriminant(&redis_conn), + mem::discriminant(&FalkorConnectionInfo::Redis(raw_redis_conn.clone())) + ); + + let FalkorConnectionInfo::Redis(conn) = redis_conn; + assert_eq!(conn.addr, raw_redis_conn.addr); + } + + #[test] + fn test_from_addr_port() { + let res = FalkorConnectionInfo::try_from(("127.0.0.1", 1234)); + assert!(res.is_ok()); + assert_eq!(res.unwrap().address(), "127.0.0.1:1234".to_string()); + } + + #[test] + fn test_invalid_scheme() { + let result = FalkorConnectionInfo::try_from("http://127.0.0.1:6379"); + assert!(result.is_err()); + } + + #[test] + fn test_missing_host() { + let result = FalkorConnectionInfo::try_from("redis://:6379"); + assert!(result.is_err()); + } + + #[test] + fn test_invalid_port() { + let result = FalkorConnectionInfo::try_from("redis://127.0.0.1:abc"); + assert!(result.is_err()); + } + + #[test] + fn test_unsupported_feature() { + let result = FalkorConnectionInfo::try_from("custom://127.0.0.1:6379"); + assert!(result.is_err()); + } + + #[test] + fn test_missing_scheme() { + let result = FalkorConnectionInfo::try_from("127.0.0.1:6379"); + assert!(result.is_ok()); + } +} diff --git a/src/error/mod.rs b/src/error/mod.rs new file mode 100644 index 0000000..c0f82ab --- /dev/null +++ b/src/error/mod.rs @@ -0,0 +1,121 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +use crate::SchemaType; + +/// A verbose error enum used throughout the client, messages are static string slices. +/// this allows easy error integration using [`thiserror`] +#[derive(thiserror::Error, Debug, PartialEq)] +pub enum FalkorDBError { + /// A required ID for parsing was not found in the schema. + #[error("A required Id for parsing was not found in the schema")] + MissingSchemaId(SchemaType), + /// Could not connect to Redis Sentinel, or a critical Sentinel operation has failed. + #[error( + "Could not connect to Redis Sentinel, or a critical Sentinel operation has failed: {0}" + )] + SentinelConnection(String), + /// Received unsupported number of sentinel masters in list, there can be only one. + #[error("Received unsupported number of sentinel masters in list, there can be only one")] + SentinelMastersCount, + ///This requested returned a connection error, however, we may be able to create a new connection to the server, this operation should probably be retried in a bit. + #[error("This requested returned a connection error, however, we may be able to create a new connection to the server, this operation should probably be retried in a bit.")] + ConnectionDown, + /// An error occurred while sending the request to Redis. + #[error("An error occurred while sending the request to Redis: {0}")] + RedisError(String), + /// An error occurred while parsing the Redis response. + #[error("An error occurred while parsing the Redis response: {0}")] + RedisParsingError(String), + /// The provided connection info is invalid. + #[error("Could not parse the provided connection info: {0}")] + InvalidConnectionInfo(String), + /// The connection returned invalid data for this command. + #[error("The connection returned invalid data for this command")] + InvalidDataReceived, + /// The provided URL scheme points at a database provider that is currently unavailable, make sure the correct feature is enabled. + #[error("The provided URL scheme points at a database provider that is currently unavailable, make sure the correct feature is enabled")] + UnavailableProvider, + /// An error occurred when dealing with reference counts or RefCells, perhaps mutual borrows? + #[error( + "An error occurred when dealing with reference counts or RefCells, perhaps mutual borrows?" + )] + RefCountBooBoo, + /// The execution plan did not adhere to usual structure, and could not be parsed. + #[error("The execution plan did not adhere to usual structure, and could not be parsed")] + CorruptExecutionPlan, + /// Could not connect to the server with the provided address. + #[error("Could not connect to the server with the provided address")] + NoConnection, + /// Attempting to use an empty connection object. + #[error("Attempting to use an empty connection object")] + EmptyConnection, + /// General parsing error. + #[error("General parsing error: {0}")] + ParsingError(String), + /// Received malformed header. + #[error("Could not parse header: {0}")] + ParsingHeader(String), + /// The id received for this label/property/relationship was unknown. + #[error("The id received for this label/property/relationship was unknown")] + ParsingCompactIdUnknown, + /// Unknown type. + #[error("Unknown type")] + ParsingUnknownType, + /// Element was not of type Bool. + #[error("Element was not of type Bool")] + ParsingBool, + /// Could not parse into config value, was not one of the supported types. + #[error("Could not parse into config value, was not one of the supported types")] + ParsingConfigValue, + /// Element was not of type I64. + #[error("Element was not of type I64")] + ParsingI64, + /// Element was not of type F64. + #[error("Element was not of type F64")] + ParsingF64, + /// Element was not of type FArray. + #[error("Element was not of type FArray")] + ParsingFArray, + /// Element was not of type FString. + #[error("Element was not of type FString")] + ParsingFString, + /// Element was not of type FEdge. + #[error("Element was not of type FEdge")] + ParsingFEdge, + /// Element was not of type FNode. + #[error("Element was not of type FNode")] + ParsingFNode, + /// Element was not of type FPath. + #[error("Element was not of type FPath")] + ParsingFPath, + /// Element was not of type FMap. + #[error("Element was not of type FMap: {0}")] + ParsingFMap(String), + /// Element was not of type FPoint. + #[error("Element was not of type FPoint")] + ParsingFPoint, + /// Key id was not of type i64. + #[error("Key id was not of type i64")] + ParsingKeyIdTypeMismatch, + /// Type marker was not of type i64. + #[error("Type marker was not of type i64")] + ParsingTypeMarkerTypeMismatch, + /// Both key id and type marker were not of type i64. + #[error("Both key id and type marker were not of type i64")] + ParsingKTVTypes, + /// Attempting to parse an FArray into a struct, but the array doesn't have the expected element count. + #[error("Attempting to parse an FArray into a struct, but the array doesn't have the expected element count: {0}")] + ParsingArrayToStructElementCount(String), + /// Invalid enum string variant was encountered when parsing + #[error("Invalid enum string variant was encountered when parsing: {0}")] + InvalidEnumType(String), +} + +impl From for FalkorDBError { + fn from(value: strum::ParseError) -> Self { + FalkorDBError::InvalidEnumType(value.to_string()) + } +} diff --git a/src/graph/blocking.rs b/src/graph/blocking.rs new file mode 100644 index 0000000..48b8548 --- /dev/null +++ b/src/graph/blocking.rs @@ -0,0 +1,577 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +use crate::{ + client::blocking::FalkorSyncClientInner, Constraint, ConstraintType, EntityType, ExecutionPlan, + FalkorIndex, FalkorResponse, FalkorResult, FalkorValue, GraphSchema, IndexType, + ProcedureQueryBuilder, QueryBuilder, ResultSet, SlowlogEntry, +}; +use std::{collections::HashMap, fmt::Display, sync::Arc}; + +/// The main graph API, this allows the user to perform graph operations while exposing as little details as possible. +/// # Thread Safety +/// This struct is NOT thread safe, and synchronization is up to the user. +/// Also, graph schema is not shared between instances of SyncGraph, even with the same name +#[derive(Clone)] +pub struct SyncGraph { + pub(crate) client: Arc, + graph_name: String, + pub(crate) graph_schema: GraphSchema, +} + +impl SyncGraph { + pub(crate) fn new( + client: Arc, + graph_name: T, + ) -> Self { + Self { + graph_name: graph_name.to_string(), + graph_schema: GraphSchema::new(graph_name, client.clone()), // Required for requesting refreshes + client, + } + } + + /// Returns the name of the graph for which this API performs operations. + /// + /// # Returns + /// The graph name as a string slice, without cloning. + pub fn graph_name(&self) -> &str { + self.graph_name.as_str() + } + + fn execute_command( + &self, + command: &str, + subcommand: Option<&str>, + params: Option<&[&str]>, + ) -> FalkorResult { + self.client + .borrow_connection(self.client.clone())? + .execute_command(Some(self.graph_name.as_str()), command, subcommand, params) + } + + /// Deletes the graph stored in the database, and drop all the schema caches. + /// NOTE: This still maintains the graph API, operations are still viable. + pub fn delete(&mut self) -> FalkorResult<()> { + self.execute_command("GRAPH.DELETE", None, None)?; + self.graph_schema.clear(); + Ok(()) + } + + /// Retrieves the slowlog data, which contains info about the N slowest queries. + /// + /// # Returns + /// A [`Vec`] of [`SlowlogEntry`], providing information about each query. + pub fn slowlog(&self) -> FalkorResult> { + let res = self + .execute_command("GRAPH.SLOWLOG", None, None)? + .into_vec()?; + + Ok(res.into_iter().flat_map(SlowlogEntry::try_from).collect()) + } + + /// Resets the slowlog, all query time data will be cleared. + pub fn slowlog_reset(&self) -> FalkorResult { + self.execute_command("GRAPH.SLOWLOG", None, Some(&["RESET"])) + } + + /// Creates a [`QueryBuilder`] for this graph, in an attempt to profile a specific query + /// This [`QueryBuilder`] has to be dropped or ran using [`QueryBuilder::perform`], before reusing the graph, as it takes a mutable reference to the graph for as long as it exists + /// + /// # Arguments + /// * `query_string`: The query to profile + /// + /// # Returns + /// A [`QueryBuilder`] object, which when performed will return an [`ExecutionPlan`] + pub fn profile<'a>( + &'a mut self, + query_string: &'a str, + ) -> QueryBuilder { + QueryBuilder::<'a>::new(self, "GRAPH.PROFILE", query_string) + } + + /// Creates a [`QueryBuilder`] for this graph, in an attempt to explain a specific query + /// This [`QueryBuilder`] has to be dropped or ran using [`QueryBuilder::perform`], before reusing the graph, as it takes a mutable reference to the graph for as long as it exists + /// + /// # Arguments + /// * `query_string`: The query to explain the process for + /// + /// # Returns + /// A [`QueryBuilder`] object, which when performed will return an [`ExecutionPlan`] + pub fn explain<'a>( + &'a mut self, + query_string: &'a str, + ) -> QueryBuilder { + QueryBuilder::new(self, "GRAPH.EXPLAIN", query_string) + } + + /// Creates a [`QueryBuilder`] for this graph + /// This [`QueryBuilder`] has to be dropped or ran using [`QueryBuilder::perform`], before reusing the graph, as it takes a mutable reference to the graph for as long as it exists + /// + /// # Arguments + /// * `query_string`: The query to run + /// + /// # Returns + /// A [`QueryBuilder`] object, which when performed will return a [`FalkorResponse`] + pub fn query<'a>( + &'a mut self, + query_string: &'a str, + ) -> QueryBuilder> { + QueryBuilder::new(self, "GRAPH.QUERY", query_string) + } + + /// Creates a [`QueryBuilder`] for this graph, for a readonly query + /// This [`QueryBuilder`] has to be dropped or ran using [`QueryBuilder::perform`], before reusing the graph, as it takes a mutable reference to the graph for as long as it exists + /// Read-only queries are more limited with the operations they are allowed to perform. + /// + /// # Arguments + /// * `query_string`: The query to run + /// + /// # Returns + /// A [`QueryBuilder`] object + pub fn ro_query<'a>( + &'a mut self, + query_string: &'a str, + ) -> QueryBuilder> { + QueryBuilder::new(self, "GRAPH.QUERY_RO", query_string) + } + + /// Creates a [`ProcedureQueryBuilder`] for this graph + /// This [`ProcedureQueryBuilder`] has to be dropped or ran using [`ProcedureQueryBuilder::perform`], before reusing the graph, as it takes a mutable reference to the graph for as long as it exists + /// Read-only queries are more limited with the operations they are allowed to perform. + /// + /// # Arguments + /// * `procedure_name`: The name of the procedure to call + /// + /// # Returns + /// A [`ProcedureQueryBuilder`] object + pub fn call_procedure<'a, P>( + &'a mut self, + procedure_name: &'a str, + ) -> ProcedureQueryBuilder

{ + ProcedureQueryBuilder::new(self, procedure_name) + } + + /// Creates a [`ProcedureQueryBuilder`] for this graph, for a readonly procedure + /// This [`ProcedureQueryBuilder`] has to be dropped or ran using [`ProcedureQueryBuilder::perform`], before reusing the graph, as it takes a mutable reference to the graph for as long as it exists + /// Read-only procedures are more limited with the operations they are allowed to perform. + /// + /// # Arguments + /// * `procedure_name`: The name of the procedure to call + /// + /// # Returns + /// A [`ProcedureQueryBuilder`] object + pub fn call_procedure_ro<'a, P>( + &'a mut self, + procedure_name: &'a str, + ) -> ProcedureQueryBuilder

{ + ProcedureQueryBuilder::new_readonly(self, procedure_name) + } + + /// Calls the DB.INDICES procedure on the graph, returning all the indexing methods currently used + /// + /// # Returns + /// A [`Vec`] of [`FalkorIndex`] + pub fn list_indices(&mut self) -> FalkorResult>> { + ProcedureQueryBuilder::>>::new(self, "DB.INDEXES").execute() + } + + /// Creates a new index in the graph, for the selected entity type(Node/Edge), selected label, and properties + /// + /// # Arguments + /// * `index_field_type`: + /// * `entity_type`: + /// * `label`: + /// * `properties`: + /// * `options`: + /// + /// # Returns + /// A [`ResultSet`] containing information on the created index + pub fn create_index( + &mut self, + index_field_type: IndexType, + entity_type: EntityType, + label: &str, + properties: &[P], + options: Option<&HashMap>, + ) -> FalkorResult> { + // Create index from these properties + let properties_string = properties + .iter() + .map(|element| format!("l.{}", element)) + .collect::>() + .join(", "); + + let pattern = match entity_type { + EntityType::Node => format!("(l:{})", label), + EntityType::Edge => format!("()-[l:{}]->()", label), + }; + + let idx_type = match index_field_type { + IndexType::Range => "", + IndexType::Vector => "VECTOR ", + IndexType::Fulltext => "FULLTEXT ", + }; + + let options_string = options + .map(|hashmap| { + hashmap + .iter() + .map(|(key, val)| format!("'{key}':'{val}'")) + .collect::>() + .join(",") + }) + .map(|options_string| format!(" OPTIONS {{ {} }}", options_string)) + .unwrap_or_default(); + + self.query( + format!( + "CREATE {idx_type}INDEX FOR {pattern} ON ({}){}", + properties_string, options_string + ) + .as_str(), + ) + .execute() + } + + /// Drop an existing index, by specifying its type, entity, label and specific properties + /// + /// # Arguments + /// * `index_field_type` + pub fn drop_index( + &mut self, + index_field_type: IndexType, + entity_type: EntityType, + label: L, + properties: &[P], + ) -> FalkorResult> { + let properties_string = properties + .iter() + .map(|element| format!("e.{}", element.to_string())) + .collect::>() + .join(", "); + + let pattern = match entity_type { + EntityType::Node => format!("(e:{})", label.to_string()), + EntityType::Edge => format!("()-[e:{}]->()", label.to_string()), + }; + + let idx_type = match index_field_type { + IndexType::Range => "", + IndexType::Vector => "VECTOR", + IndexType::Fulltext => "FULLTEXT", + } + .to_string(); + + self.query( + format!( + "DROP {idx_type} INDEX for {pattern} ON ({})", + properties_string + ) + .as_str(), + ) + .execute() + } + + /// Calls the DB.CONSTRAINTS procedure on the graph, returning an array of the graph's constraints + /// + /// # Returns + /// A tuple where the first element is a [`Vec`] of [`Constraint`]s, and the second element is a [`Vec`] of stats as [`String`]s + pub fn list_constraints(&mut self) -> FalkorResult>> { + ProcedureQueryBuilder::>>::new(self, "DB.CONSTRAINTS") + .execute() + } + + /// Creates a new constraint for this graph, making the provided properties mandatory + /// + /// # Arguments + /// * `entity_type`: Whether to apply this constraint on nodes or relationships. + /// * `label`: Entities with this label will have this constraint applied to them. + /// * `properties`: A slice of the names of properties this constraint will apply to. + pub fn create_mandatory_constraint( + &self, + entity_type: EntityType, + label: &str, + properties: &[&str], + ) -> FalkorResult { + let entity_type = entity_type.to_string(); + let properties_count = properties.len().to_string(); + + let mut params = Vec::with_capacity(5 + properties.len()); + params.extend([ + "MANDATORY", + entity_type.as_str(), + label, + "PROPERTIES", + properties_count.as_str(), + ]); + params.extend(properties); + + self.execute_command("GRAPH.CONSTRAINT", Some("CREATE"), Some(params.as_slice())) + } + + /// Creates a new constraint for this graph, making the provided properties unique + /// + /// # Arguments + /// * `entity_type`: Whether to apply this constraint on nodes or relationships. + /// * `label`: Entities with this label will have this constraint applied to them. + /// * `properties`: A slice of the names of properties this constraint will apply to. + pub fn create_unique_constraint( + &mut self, + entity_type: EntityType, + label: String, + properties: &[&str], + ) -> FalkorResult { + self.create_index( + IndexType::Range, + entity_type, + label.as_str(), + properties, + None, + )?; + + let entity_type = entity_type.to_string(); + let properties_count = properties.len().to_string(); + let mut params: Vec<&str> = Vec::with_capacity(5 + properties.len()); + params.extend([ + "UNIQUE", + entity_type.as_str(), + label.as_str(), + "PROPERTIES", + properties_count.as_str(), + ]); + params.extend(properties); + + // create constraint using index + self.execute_command("GRAPH.CONSTRAINT", Some("CREATE"), Some(params.as_slice())) + } + + /// Drop an existing constraint from the graph + /// + /// # Arguments + /// * `constraint_type`: Which type of constraint to remove. + /// * `entity_type`: Whether this constraint exists on nodes or relationships. + /// * `label`: Remove the constraint from entities with this label. + /// * `properties`: A slice of the names of properties to remove the constraint from. + pub fn drop_constraint( + &self, + constraint_type: ConstraintType, + entity_type: EntityType, + label: &str, + properties: &[&str], + ) -> FalkorResult { + let constraint_type = constraint_type.to_string(); + let entity_type = entity_type.to_string(); + let properties_count = properties.len().to_string(); + + let mut params = Vec::with_capacity(5 + properties.len()); + params.extend([ + constraint_type.as_str(), + entity_type.as_str(), + label, + "PROPERTIES", + properties_count.as_str(), + ]); + params.extend(properties); + + self.execute_command("GRAPH.CONSTRAINT", Some("DROP"), Some(params.as_slice())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{test_utils::open_test_graph, IndexType}; + + #[test] + fn test_create_drop_index() { + let mut graph = open_test_graph("test_create_drop_index"); + graph + .inner + .create_index( + IndexType::Fulltext, + EntityType::Node, + "actor", + &["Hello"], + None, + ) + .expect("Could not create index"); + + let indices = graph.inner.list_indices().expect("Could not list indices"); + + assert_eq!(indices.data.len(), 2); + assert_eq!( + indices.data[0].field_types["Hello"], + vec![IndexType::Fulltext] + ); + + graph + .inner + .drop_index( + IndexType::Fulltext, + EntityType::Node, + "actor".to_string(), + &["Hello"], + ) + .expect("Could not drop index"); + } + + #[test] + fn test_list_indices() { + let mut graph = open_test_graph("test_list_indices"); + let indices = graph.inner.list_indices().expect("Could not list indices"); + + assert_eq!(indices.data.len(), 1); + assert_eq!(indices.data[0].entity_type, EntityType::Node); + assert_eq!(indices.data[0].index_label, "actor".to_string()); + assert_eq!(indices.data[0].field_types.len(), 1); + assert_eq!( + indices.data[0].field_types, + HashMap::from([("name".to_string(), vec![IndexType::Fulltext])]) + ); + } + + #[test] + fn test_create_drop_mandatory_constraint() { + let graph = open_test_graph("test_mandatory_constraint"); + + graph + .inner + .create_mandatory_constraint(EntityType::Edge, "act", &["hello", "goodbye"]) + .expect("Could not create constraint"); + + graph + .inner + .drop_constraint( + ConstraintType::Mandatory, + EntityType::Edge, + "act", + &["hello", "goodbye"], + ) + .expect("Could not drop constraint"); + } + + #[test] + fn test_create_drop_unique_constraint() { + let mut graph = open_test_graph("test_unique_constraint"); + + graph + .inner + .create_unique_constraint( + EntityType::Node, + "actor".to_string(), + &["first_name", "last_name"], + ) + .expect("Could not create constraint"); + + graph + .inner + .drop_constraint( + ConstraintType::Unique, + EntityType::Node, + "actor", + &["first_name", "last_name"], + ) + .expect("Could not drop constraint"); + } + + #[test] + fn test_list_constraints() { + let mut graph = open_test_graph("test_list_constraint"); + + graph + .inner + .create_unique_constraint( + EntityType::Node, + "actor".to_string(), + &["first_name", "last_name"], + ) + .expect("Could not create constraint"); + + let res = graph + .inner + .list_constraints() + .expect("Could not list constraints"); + assert_eq!(res.data.len(), 1); + } + + #[test] + fn test_slowlog() { + let mut graph = open_test_graph("test_slowlog"); + + graph + .inner + .query("UNWIND range(0, 500) AS x RETURN x") + .execute() + .expect("Could not generate the fast query"); + graph + .inner + .query("UNWIND range(0, 100000) AS x RETURN x") + .execute() + .expect("Could not generate the slow query"); + + let slowlog = graph + .inner + .slowlog() + .expect("Could not get slowlog entries"); + + assert_eq!(slowlog.len(), 2); + assert_eq!( + slowlog[0].arguments, + "UNWIND range(0, 500) AS x RETURN x".to_string() + ); + assert_eq!( + slowlog[1].arguments, + "UNWIND range(0, 100000) AS x RETURN x".to_string() + ); + + graph + .inner + .slowlog_reset() + .expect("Could not reset slowlog memory"); + let slowlog_after_reset = graph + .inner + .slowlog() + .expect("Could not get slowlog entries after reset"); + assert!(slowlog_after_reset.is_empty()); + } + + #[test] + fn test_explain() { + let mut graph = open_test_graph("test_explain"); + + let execution_plan = graph.inner.explain("MATCH (a:actor) WITH a MATCH (b:actor) WHERE a.age = b.age AND a <> b RETURN a, collect(b) LIMIT 100").execute().expect("Could not create execution plan"); + assert_eq!(execution_plan.plan().len(), 7); + assert!(execution_plan.operations().get("Aggregate").is_some()); + assert_eq!(execution_plan.operations()["Aggregate"].len(), 1); + + assert_eq!( + execution_plan.string_representation(), + "\nResults\n Limit\n Aggregate\n Filter\n Node By Label Scan | (b:actor)\n Project\n Node By Label Scan | (a:actor)" + ); + } + + #[test] + fn test_profile() { + let mut graph = open_test_graph("test_profile"); + + let execution_plan = graph + .inner + .profile("UNWIND range(0, 1000) AS x RETURN x") + .execute() + .expect("Could not generate the query"); + + assert_eq!(execution_plan.plan().len(), 3); + + let expected = vec!["Results", "Project", "Unwind"]; + let mut current_rc = execution_plan.operation_tree().clone(); + for step in expected { + assert_eq!(current_rc.name, step); + if step != "Unwind" { + current_rc = current_rc.children[0].clone(); + } + } + } +} diff --git a/src/graph/mod.rs b/src/graph/mod.rs new file mode 100644 index 0000000..eb20df3 --- /dev/null +++ b/src/graph/mod.rs @@ -0,0 +1,7 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +pub(crate) mod blocking; +pub(crate) mod query_builder; diff --git a/src/graph/query_builder.rs b/src/graph/query_builder.rs new file mode 100644 index 0000000..c0d1da0 --- /dev/null +++ b/src/graph/query_builder.rs @@ -0,0 +1,478 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +use crate::{ + connection::blocking::BorrowedSyncConnection, + parser::utils::{parse_header, parse_result_set}, + Constraint, ExecutionPlan, FalkorDBError, FalkorIndex, FalkorParsable, FalkorResponse, + FalkorResult, FalkorValue, ResultSet, SyncGraph, +}; +use std::ops::Not; +use std::{collections::HashMap, fmt::Display, marker::PhantomData}; + +pub(crate) fn construct_query( + query_str: Q, + params: Option<&HashMap>, +) -> String { + let params_str = params + .map(|p| { + p.iter() + .map(|(k, v)| format!("{k}={v}")) + .collect::>() + .join(" ") + }) + .and_then(|params_str| { + params_str + .is_empty() + .not() + .then_some(format!("CYPHER {params_str} ")) + }) + .unwrap_or_default(); + format!("{params_str}{query_str}") +} + +/// A Builder-pattern struct that allows creating and executing queries on a graph +pub struct QueryBuilder<'a, Output> { + _unused: PhantomData, + graph: &'a mut SyncGraph, + command: &'a str, + query_string: &'a str, + params: Option<&'a HashMap>, + timeout: Option, +} + +impl<'a, Output> QueryBuilder<'a, Output> { + pub(crate) fn new( + graph: &'a mut SyncGraph, + command: &'a str, + query_string: &'a str, + ) -> Self { + Self { + _unused: PhantomData, + graph, + command, + query_string, + params: None, + timeout: None, + } + } + + /// Pass the following params to the query as "CYPHER {param_key}={param_val}" + /// + /// # Arguments + /// * `params`: A [`HashMap`] of params in key-val format + pub fn with_params( + self, + params: &'a HashMap, + ) -> Self { + Self { + params: Some(params), + ..self + } + } + + /// Specify a timeout after which to abort the query + /// + /// # Arguments + /// * `timeout`: the timeout after which to abort, in ms + pub fn with_timeout( + self, + timeout: i64, + ) -> Self { + Self { + timeout: Some(timeout), + ..self + } + } + + fn common_execute_steps( + &mut self, + conn: &mut BorrowedSyncConnection, + ) -> FalkorResult { + let query = construct_query(self.query_string, self.params); + + let timeout = self.timeout.map(|timeout| format!("timeout {timeout}")); + let mut params = vec![query.as_str(), "--compact"]; + params.extend(timeout.as_deref()); + + conn.execute_command( + Some(self.graph.graph_name()), + self.command, + None, + Some(params.as_slice()), + ) + } +} + +impl<'a> QueryBuilder<'a, FalkorResponse> { + /// Executes the query, retuning a [`FalkorResponse`], with a [`ResultSet`] as its `data` member + pub fn execute(mut self) -> FalkorResult> { + let mut conn = self + .graph + .client + .borrow_connection(self.graph.client.clone())?; + let res = self.common_execute_steps(&mut conn)?.into_vec()?; + + match res.len() { + 1 => { + let stats = res.into_iter().next().ok_or( + FalkorDBError::ParsingArrayToStructElementCount( + "One element exist but using next() failed".to_string(), + ), + )?; + + FalkorResponse::from_response(None, vec![], stats) + } + 2 => { + let [header, stats]: [FalkorValue; 2] = res.try_into().map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Two elements exist but couldn't be parsed to an array".to_string(), + ) + })?; + + FalkorResponse::from_response(Some(header), vec![], stats) + } + 3 => { + let [header, data, stats]: [FalkorValue; 3] = res.try_into().map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "3 elements exist but couldn't be parsed to an array".to_string(), + ) + })?; + + FalkorResponse::from_response_with_headers( + parse_result_set(data, &mut self.graph.graph_schema)?, + parse_header(header)?, + stats, + ) + } + _ => Err(FalkorDBError::ParsingArrayToStructElementCount( + "Invalid number of elements returned from query".to_string(), + ))?, + } + } +} + +impl<'a> QueryBuilder<'a, ExecutionPlan> { + /// Executes the query, returning an [`ExecutionPlan`] from the data returned + pub fn execute(mut self) -> FalkorResult { + let mut conn = self + .graph + .client + .borrow_connection(self.graph.client.clone())?; + let res = self.common_execute_steps(&mut conn)?; + + ExecutionPlan::try_from(res) + } +} + +pub(crate) fn generate_procedure_call( + procedure: P, + args: Option<&[T]>, + yields: Option<&[Z]>, +) -> (String, Option>) { + let args_str = args + .unwrap_or_default() + .iter() + .map(|e| format!("${}", e)) + .collect::>() + .join(","); + let mut query_string = format!("CALL {}({})", procedure, args_str); + + let params = args.map(|args| { + args.iter() + .enumerate() + .fold(HashMap::new(), |mut acc, (idx, param)| { + acc.insert(format!("param{idx}"), param.to_string()); + acc + }) + }); + + if let Some(yields) = yields { + query_string += format!( + " YIELD {}", + yields + .iter() + .map(|element| element.to_string()) + .collect::>() + .join(",") + ) + .as_str(); + } + + (query_string, params) +} + +/// A Builder-pattern struct that allows creating and executing procedure call on a graph +pub struct ProcedureQueryBuilder<'a, Output> { + _unused: PhantomData, + graph: &'a mut SyncGraph, + readonly: bool, + procedure_name: &'a str, + args: Option<&'a [&'a str]>, + yields: Option<&'a [&'a str]>, +} + +impl<'a, Output> ProcedureQueryBuilder<'a, Output> { + pub(crate) fn new( + graph: &'a mut SyncGraph, + procedure_name: &'a str, + ) -> Self { + Self { + _unused: PhantomData, + graph, + readonly: false, + procedure_name, + args: None, + yields: None, + } + } + + pub(crate) fn new_readonly( + graph: &'a mut SyncGraph, + procedure_name: &'a str, + ) -> Self { + Self { + _unused: PhantomData, + graph, + readonly: true, + procedure_name, + args: None, + yields: None, + } + } + + /// Pass arguments to the procedure call + /// + /// # Arguments + /// * `args`: The arguments to pass + pub fn with_args( + self, + args: &'a [&str], + ) -> Self { + Self { + args: Some(args), + ..self + } + } + + /// Tell the procedure call it should yield the following results + /// + /// # Arguments + /// * `yields`: The values to yield + pub fn with_yields( + self, + yields: &'a [&str], + ) -> Self { + Self { + yields: Some(yields), + ..self + } + } + + fn common_execute_steps( + &mut self, + conn: &mut BorrowedSyncConnection, + ) -> FalkorResult { + let command = match self.readonly { + true => "GRAPH.QUERY_RO", + false => "GRAPH.QUERY", + }; + + let (query_string, params) = + generate_procedure_call(self.procedure_name, self.args, self.yields); + let query = construct_query(query_string, params.as_ref()); + + conn.execute_command( + Some(self.graph.graph_name()), + command, + None, + Some(&[query.as_str(), "--compact"]), + ) + } +} + +impl<'a> ProcedureQueryBuilder<'a, FalkorResponse>> { + /// Executes the procedure call and return a [`FalkorResponse`] type containing a result set of [`FalkorIndex`]s + /// This functions consumes self + pub fn execute(mut self) -> FalkorResult>> { + let mut conn = self + .graph + .client + .borrow_connection(self.graph.client.clone())?; + + let [header, indices, stats]: [FalkorValue; 3] = self + .common_execute_steps(&mut conn)? + .into_vec()? + .try_into() + .map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 3 elements in query response".to_string(), + ) + })?; + + FalkorResponse::from_response( + Some(header), + indices + .into_vec()? + .into_iter() + .flat_map(|index| { + FalkorIndex::from_falkor_value(index, &mut self.graph.graph_schema) + }) + .collect(), + stats, + ) + } +} + +impl<'a> ProcedureQueryBuilder<'a, FalkorResponse>> { + /// Executes the procedure call and return a [`FalkorResponse`] type containing a result set of [`Constraint`]s + /// This functions consumes self + pub fn execute(mut self) -> FalkorResult>> { + let mut conn = self + .graph + .client + .borrow_connection(self.graph.client.clone())?; + + let [header, query_res, stats]: [FalkorValue; 3] = self + .common_execute_steps(&mut conn)? + .into_vec()? + .try_into() + .map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 3 elements in query response".to_string(), + ) + })?; + + FalkorResponse::from_response( + Some(header), + query_res + .into_vec()? + .into_iter() + .flat_map(|item| Constraint::from_falkor_value(item, &mut self.graph.graph_schema)) + .collect(), + stats, + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_generate_procedure_call_no_args_no_yields() { + let procedure = "my_procedure"; + let args: Option<&[String]> = None; + let yields: Option<&[String]> = None; + + let expected_query = "CALL my_procedure()".to_string(); + let expected_params: Option> = None; + + let result = generate_procedure_call(procedure, args, yields); + + assert_eq!(result, (expected_query, expected_params)); + } + + #[test] + fn test_generate_procedure_call_with_args_no_yields() { + let procedure = "my_procedure"; + let args = &["arg1".to_string(), "arg2".to_string()]; + let yields: Option<&[String]> = None; + + let expected_query = "CALL my_procedure($arg1,$arg2)".to_string(); + let mut expected_params = HashMap::new(); + expected_params.insert("param0".to_string(), "arg1".to_string()); + expected_params.insert("param1".to_string(), "arg2".to_string()); + + let result = generate_procedure_call(procedure, Some(args), yields); + + assert_eq!(result, (expected_query, Some(expected_params))); + } + + #[test] + fn test_generate_procedure_call_no_args_with_yields() { + let procedure = "my_procedure"; + let args: Option<&[String]> = None; + let yields = &["yield1".to_string(), "yield2".to_string()]; + + let expected_query = "CALL my_procedure() YIELD yield1,yield2".to_string(); + let expected_params: Option> = None; + + let result = generate_procedure_call(procedure, args, Some(yields)); + + assert_eq!(result, (expected_query, expected_params)); + } + + #[test] + fn test_generate_procedure_call_with_args_and_yields() { + let procedure = "my_procedure"; + let args = &["arg1".to_string(), "arg2".to_string()]; + let yields = &["yield1".to_string(), "yield2".to_string()]; + + let expected_query = "CALL my_procedure($arg1,$arg2) YIELD yield1,yield2".to_string(); + let mut expected_params = HashMap::new(); + expected_params.insert("param0".to_string(), "arg1".to_string()); + expected_params.insert("param1".to_string(), "arg2".to_string()); + + let result = generate_procedure_call(procedure, Some(args), Some(yields)); + + assert_eq!(result, (expected_query, Some(expected_params))); + } + + #[test] + fn test_construct_query_with_params() { + let query_str = "MATCH (n) RETURN n"; + let mut params = HashMap::new(); + params.insert("name", "Alice"); + params.insert("age", "30"); + + let result = construct_query(query_str, Some(¶ms)); + assert!(result.starts_with("CYPHER ")); + assert!(result.ends_with(" RETURN n")); + assert!(result.contains(" name=Alice ")); + assert!(result.contains(" age=30 ")); + } + + #[test] + fn test_construct_query_without_params() { + let query_str = "MATCH (n) RETURN n"; + let result = construct_query::<&str, &str, &str>(query_str, None); + assert_eq!(result, "MATCH (n) RETURN n"); + } + + #[test] + fn test_construct_query_empty_params() { + let query_str = "MATCH (n) RETURN n"; + let params: HashMap<&str, &str> = HashMap::new(); + let result = construct_query(query_str, Some(¶ms)); + assert_eq!(result, "MATCH (n) RETURN n"); + } + + #[test] + fn test_construct_query_single_param() { + let query_str = "MATCH (n) RETURN n"; + let mut params = HashMap::new(); + params.insert("name", "Alice"); + + let result = construct_query(query_str, Some(¶ms)); + assert_eq!(result, "CYPHER name=Alice MATCH (n) RETURN n"); + } + + #[test] + fn test_construct_query_multiple_params() { + let query_str = "MATCH (n) RETURN n"; + let mut params = HashMap::new(); + params.insert("name", "Alice"); + params.insert("age", "30"); + params.insert("city", "Wonderland"); + + let result = construct_query(query_str, Some(¶ms)); + assert!(result.starts_with("CYPHER ")); + assert!(result.contains(" name=Alice ")); + assert!(result.contains(" age=30 ")); + assert!(result.contains(" city=Wonderland ")); + assert!(result.ends_with("MATCH (n) RETURN n")); + } +} diff --git a/src/graph_schema/mod.rs b/src/graph_schema/mod.rs new file mode 100644 index 0000000..82950cf --- /dev/null +++ b/src/graph_schema/mod.rs @@ -0,0 +1,373 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +use crate::{ + client::blocking::FalkorSyncClientInner, value::utils::parse_type, FalkorDBError, FalkorResult, + FalkorValue, +}; +use std::collections::HashMap; +use std::sync::Arc; + +pub(crate) fn get_refresh_command(schema_type: SchemaType) -> &'static str { + match schema_type { + SchemaType::Labels => "DB.LABELS", + SchemaType::Properties => "DB.PROPERTYKEYS", + SchemaType::Relationships => "DB.RELATIONSHIPTYPES", + } +} + +// Intermediate type for map parsing +#[derive(Debug)] +pub(crate) struct FKeyTypeVal { + pub(crate) key: i64, + pub(crate) type_marker: i64, + pub(crate) val: FalkorValue, +} + +impl TryFrom for FKeyTypeVal { + type Error = FalkorDBError; + + fn try_from(value: FalkorValue) -> FalkorResult { + let [key_raw, type_raw, val]: [FalkorValue; 3] = + value.into_vec()?.try_into().map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 3 elements for key-type-value property".to_string(), + ) + })?; + + let key = key_raw.to_i64(); + let type_marker = type_raw.to_i64(); + + match (key, type_marker) { + (Some(key), Some(type_marker)) => Ok(FKeyTypeVal { + key, + type_marker, + val, + }), + (Some(_), None) => Err(FalkorDBError::ParsingTypeMarkerTypeMismatch)?, + (None, Some(_)) => Err(FalkorDBError::ParsingKeyIdTypeMismatch)?, + _ => Err(FalkorDBError::ParsingKTVTypes)?, + } + } +} + +/// An enum specifying which schema type we are addressing +/// When querying using the compact parser, ids are returned for the various schema entities instead of strings +/// Using this enum we know which of the schema maps to access in order to convert these ids to strings +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum SchemaType { + /// The schema for [`Node`](crate::Node) labels + Labels, + /// The schema for [`Node`](crate::Node) or [`Edge`](crate::Edge) properties (attribute keys) + Properties, + /// The schema for [`Edge`](crate::Edge) labels + Relationships, +} + +pub(crate) type IdMap = HashMap; + +/// A struct containing the various schema maps, allowing conversions between ids and their string representations. +#[derive(Clone)] +pub struct GraphSchema { + client: Arc, + graph_name: String, + version: i64, + labels: IdMap, + properties: IdMap, + relationships: IdMap, +} + +impl GraphSchema { + pub(crate) fn new( + graph_name: T, + client: Arc, + ) -> Self { + Self { + client, + graph_name: graph_name.to_string(), + version: 0, + labels: IdMap::new(), + properties: IdMap::new(), + relationships: IdMap::new(), + } + } + + /// Clears all cached schemas, this will cause a refresh when next attempting to parse a compact query. + pub fn clear(&mut self) { + self.version = 0; + self.labels.clear(); + self.properties.clear(); + self.relationships.clear(); + } + + /// Returns a read-write-locked map, of the relationship ids to their respective string representations. + /// Minimize locking these to avoid starvation. + pub fn relationships(&self) -> &IdMap { + &self.relationships + } + + /// Returns a read-write-locked map, of the label ids to their respective string representations. + /// Minimize locking these to avoid starvation. + pub fn labels(&self) -> &IdMap { + &self.labels + } + + /// Returns a read-write-locked map, of the property ids to their respective string representations. + /// Minimize locking these to avoid starvation. + pub fn properties(&self) -> &IdMap { + &self.properties + } + + #[inline] + fn get_id_map_by_schema_type( + &self, + schema_type: SchemaType, + ) -> &IdMap { + match schema_type { + SchemaType::Labels => &self.labels, + SchemaType::Properties => &self.properties, + SchemaType::Relationships => &self.relationships, + } + } + + fn refresh( + &mut self, + schema_type: SchemaType, + ) -> FalkorResult<()> { + let id_map = match schema_type { + SchemaType::Labels => &mut self.labels, + SchemaType::Properties => &mut self.properties, + SchemaType::Relationships => &mut self.relationships, + }; + + // This is essentially the call_procedure(), but can be done here without access to the graph(which would cause ownership issues) + let [_, keys, _]: [FalkorValue; 3] = self + .client + .borrow_connection(self.client.clone())? + .execute_command( + Some(self.graph_name.as_str()), + "GRAPH.QUERY", + None, + Some(&[format!("CALL {}()", get_refresh_command(schema_type)).as_str()]), + )? + .into_vec()? + .try_into() + .map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 3 types for header-resultset-stats response from refresh query" + .to_string(), + ) + })?; + + let new_keys = keys + .into_vec()? + .into_iter() + .enumerate() + .flat_map(|(idx, item)| { + FalkorResult::<(i64, String)>::Ok(( + idx as i64, + item.into_vec()? + .into_iter() + .next() + .ok_or(FalkorDBError::ParsingError( + "Expected new label/property to be the first element in an array" + .to_string(), + )) + .and_then(|item| item.into_string())?, + )) + }) + .collect::>(); + + *id_map = new_keys; + Ok(()) + } + + pub(crate) fn parse_id_vec( + &mut self, + raw_ids: Vec, + schema_type: SchemaType, + ) -> FalkorResult> { + let mut out_vec = Vec::with_capacity(raw_ids.len()); + for raw_id in raw_ids { + let id = raw_id.to_i64().ok_or(FalkorDBError::ParsingI64)?; + out_vec.push( + match self + .get_id_map_by_schema_type(schema_type) + .get(&id) + .cloned() + { + None => { + self.refresh(schema_type)?; + self.get_id_map_by_schema_type(schema_type) + .get(&id) + .cloned() + .ok_or(FalkorDBError::MissingSchemaId(schema_type))? + } + Some(exists) => exists, + }, + ); + } + + Ok(out_vec) + } + + pub(crate) fn parse_properties_map( + &mut self, + value: FalkorValue, + ) -> FalkorResult> { + let raw_properties_vec = value.into_vec()?; + let mut out_map = HashMap::with_capacity(raw_properties_vec.len()); + + for item in raw_properties_vec { + let ktv = FKeyTypeVal::try_from(item)?; + let key = match self.properties.get(&ktv.key).cloned() { + None => { + // Refresh, but this time when we try again, throw an error on failure + self.refresh(SchemaType::Properties)?; + self.properties + .get(&ktv.key) + .cloned() + .ok_or(FalkorDBError::MissingSchemaId(SchemaType::Properties))? + } + Some(key) => key, + }; + + out_map.insert(key, parse_type(ktv.type_marker, ktv.val, self)?); + } + + Ok(out_map) + } +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use crate::{ + client::blocking::create_empty_inner_client, test_utils::create_test_client, SyncGraph, + }; + use std::collections::HashMap; + + pub(crate) fn open_readonly_graph_with_modified_schema() -> SyncGraph { + let client = create_test_client(); + let mut graph = client.select_graph("imdb"); + + graph.graph_schema.properties = HashMap::from([ + (0, "age".to_string()), + (1, "is_boring".to_string()), + (2, "something_else".to_string()), + (3, "secs_since_login".to_string()), + ]); + + graph.graph_schema.labels = + HashMap::from([(0, "much".to_string()), (1, "actor".to_string())]); + + graph.graph_schema.relationships = + HashMap::from([(0, "very".to_string()), (1, "wow".to_string())]); + + graph + } + + #[test] + fn test_label_not_exists() { + let mut parser = GraphSchema::new("graph_name".to_string(), create_empty_inner_client()); + let input_value = FalkorValue::Array(vec![FalkorValue::Array(vec![ + FalkorValue::I64(1), + FalkorValue::I64(2), + FalkorValue::String("test".to_string()), + ])]); + + let result = parser.parse_properties_map(input_value); + assert!(result.is_err()); + } + + #[test] + fn test_parse_properties_map() { + let mut parser = GraphSchema::new("graph_name".to_string(), create_empty_inner_client()); + parser.properties = HashMap::from([ + (1, "property1".to_string()), + (2, "property2".to_string()), + (3, "property3".to_string()), + ]); + + // Create a FalkorValue to test + let input_value = FalkorValue::Array(vec![ + FalkorValue::Array(vec![ + FalkorValue::I64(1), + FalkorValue::I64(2), + FalkorValue::String("test".to_string()), + ]), + FalkorValue::Array(vec![ + FalkorValue::I64(2), + FalkorValue::I64(3), + FalkorValue::I64(42), + ]), + FalkorValue::Array(vec![ + FalkorValue::I64(3), + FalkorValue::I64(4), + FalkorValue::Bool(true), + ]), + ]); + + let result = parser.parse_properties_map(input_value); + + let expected_map = HashMap::from([ + ( + "property1".to_string(), + FalkorValue::String("test".to_string()), + ), + ("property2".to_string(), FalkorValue::I64(42)), + ("property3".to_string(), FalkorValue::Bool(true)), + ]); + assert_eq!(result.unwrap(), expected_map); + } + + #[test] + fn test_parse_id_vec() { + let mut parser = GraphSchema::new("graph_name".to_string(), create_empty_inner_client()); + + parser.labels = HashMap::from([ + (1, "property1".to_string()), + (2, "property2".to_string()), + (3, "property3".to_string()), + ]); + + let labels_ok_res = + parser.parse_id_vec(vec![3.into(), 1.into(), 2.into()], SchemaType::Labels); + assert!(labels_ok_res.is_ok()); + assert_eq!( + labels_ok_res.unwrap(), + vec!["property3", "property1", "property2"] + ); + + // Should fail, these are not relationships + let labels_not_ok_res = parser.parse_id_vec( + vec![3.into(), 1.into(), 2.into()], + SchemaType::Relationships, + ); + assert!(labels_not_ok_res.is_err()); + + parser.clear(); + + parser.relationships = HashMap::from([ + (1, "property4".to_string()), + (2, "property5".to_string()), + (3, "property6".to_string()), + ]); + + let rels_ok_res = parser.parse_id_vec( + vec![3.into(), 1.into(), 2.into()], + SchemaType::Relationships, + ); + assert!(rels_ok_res.is_ok()); + assert_eq!( + rels_ok_res.unwrap(), + vec![ + "property6".to_string(), + "property4".to_string(), + "property5".to_string() + ] + ) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..736d3f7 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,83 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +#![deny(missing_docs)] +#![doc = include_str!("../README.md")] + +#[cfg(not(feature = "redis"))] +compile_error!("The `redis` feature must be enabled."); + +mod client; +mod connection; +mod connection_info; +mod error; +mod graph; +mod graph_schema; +mod parser; +mod response; +mod value; + +#[cfg(feature = "redis")] +mod redis_ext; + +/// A [`Result`] which only returns [`FalkorDBError`] as its E type +pub type FalkorResult = Result; + +pub use client::{blocking::FalkorSyncClient, builder::FalkorClientBuilder}; +pub use connection_info::FalkorConnectionInfo; +pub use error::FalkorDBError; +pub use graph::{ + blocking::SyncGraph, + query_builder::{ProcedureQueryBuilder, QueryBuilder}, +}; +pub use graph_schema::{GraphSchema, SchemaType}; +pub use parser::FalkorParsable; +pub use response::{ + constraint::{Constraint, ConstraintStatus, ConstraintType}, + execution_plan::ExecutionPlan, + index::{FalkorIndex, IndexStatus, IndexType}, + slowlog_entry::SlowlogEntry, + FalkorResponse, ResultSet, +}; +pub use value::{ + config::ConfigValue, + graph_entities::{Edge, EntityType, Node}, + path::Path, + point::Point, + FalkorValue, +}; + +#[cfg(test)] +pub(crate) mod test_utils { + use super::*; + + pub(crate) struct TestSyncGraphHandle { + pub(crate) inner: SyncGraph, + } + + impl Drop for TestSyncGraphHandle { + fn drop(&mut self) { + self.inner.delete().ok(); + } + } + + pub(crate) fn create_test_client() -> FalkorSyncClient { + FalkorClientBuilder::new() + .build() + .expect("Could not create client") + } + + pub(crate) fn open_test_graph(graph_name: &str) -> TestSyncGraphHandle { + let client = create_test_client(); + + client.select_graph(graph_name).delete().ok(); + + TestSyncGraphHandle { + inner: client + .copy_graph("imdb", graph_name) + .expect("Could not copy graph for test"), + } + } +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 0000000..2c1edb1 --- /dev/null +++ b/src/parser/mod.rs @@ -0,0 +1,17 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +pub mod utils; + +use crate::{FalkorResult, FalkorValue, GraphSchema}; + +/// This trait allows implementing a parser from the table-style result sent by the database, to any other struct +pub trait FalkorParsable: Sized { + /// Parse the following value, using the graph schem owned by the graph object, and the connection used to make the request + fn from_falkor_value( + value: FalkorValue, + graph_schema: &mut GraphSchema, + ) -> FalkorResult; +} diff --git a/src/parser/utils.rs b/src/parser/utils.rs new file mode 100644 index 0000000..0ba5610 --- /dev/null +++ b/src/parser/utils.rs @@ -0,0 +1,125 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +use crate::{ + value::utils::parse_type, FalkorDBError, FalkorResult, FalkorValue, GraphSchema, ResultSet, +}; + +pub(crate) fn string_vec_from_val(value: FalkorValue) -> FalkorResult> { + value.into_vec().map(|value_as_vec| { + value_as_vec + .into_iter() + .flat_map(FalkorValue::into_string) + .collect() + }) +} + +pub(crate) fn parse_header(header: FalkorValue) -> FalkorResult> { + let in_vec = header.into_vec()?; + + let mut out_vec = Vec::with_capacity(in_vec.len()); + for item in in_vec { + let item_vec = item.into_vec()?; + + out_vec.push( + if item_vec.len() == 2 { + let [_, key]: [FalkorValue; 2] = item_vec.try_into().map_err(|_| { + FalkorDBError::ParsingHeader( + "Could not get 2-sized array despite there being 2 elements".to_string(), + ) + })?; + key + } else { + item_vec + .into_iter() + .next() + .ok_or(FalkorDBError::ParsingHeader( + "Expected at least one item in header vector".to_string(), + ))? + } + .into_string()?, + ) + } + + Ok(out_vec) +} + +pub(crate) fn parse_result_set( + data: FalkorValue, + graph_schema: &mut GraphSchema, +) -> FalkorResult { + let data_vec = data.into_vec()?; + + let mut out_vec = Vec::with_capacity(data_vec.len()); + for column in data_vec { + out_vec.push(parse_type(6, column, graph_schema)?.into_vec()?); + } + + Ok(out_vec) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{FalkorDBError, FalkorValue}; + + #[test] + fn test_parse_header_valid_single_key() { + let header = FalkorValue::Array(vec![FalkorValue::Array(vec![FalkorValue::String( + "key1".to_string(), + )])]); + let result = parse_header(header); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), vec!["key1".to_string()]); + } + + #[test] + fn test_parse_header_valid_multiple_keys() { + let header = FalkorValue::Array(vec![ + FalkorValue::Array(vec![ + FalkorValue::String("type".to_string()), + FalkorValue::String("header1".to_string()), + ]), + FalkorValue::Array(vec![FalkorValue::String("key2".to_string())]), + ]); + let result = parse_header(header); + assert!(result.is_ok()); + assert_eq!( + result.unwrap(), + vec!["header1".to_string(), "key2".to_string()] + ); + } + + #[test] + fn test_parse_header_empty_header() { + let header = FalkorValue::Array(vec![]); + let result = parse_header(header); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), Vec::::new()); + } + + #[test] + fn test_parse_header_empty_vec() { + let header = FalkorValue::Array(vec![FalkorValue::Array(vec![])]); + let result = parse_header(header); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err(), + FalkorDBError::ParsingHeader("Expected at least one item in header vector".to_string()) + ); + } + + #[test] + fn test_parse_header_many_elements() { + let header = FalkorValue::Array(vec![FalkorValue::Array(vec![ + FalkorValue::String("just_some_header".to_string()), + FalkorValue::String("header1".to_string()), + FalkorValue::String("extra".to_string()), + ])]); + let result = parse_header(header); + assert!(result.is_ok()); + assert_eq!(result.unwrap()[0], "just_some_header"); + } +} diff --git a/src/redis_ext.rs b/src/redis_ext.rs new file mode 100644 index 0000000..88feba0 --- /dev/null +++ b/src/redis_ext.rs @@ -0,0 +1,53 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +use crate::{ConfigValue, FalkorDBError, FalkorValue}; +use redis::{FromRedisValue, RedisResult, RedisWrite, ToRedisArgs}; + +impl ToRedisArgs for ConfigValue { + fn write_redis_args( + &self, + out: &mut W, + ) where + W: ?Sized + RedisWrite, + { + match self { + ConfigValue::String(str_val) => str_val.write_redis_args(out), + ConfigValue::Int64(int_val) => int_val.write_redis_args(out), + } + } +} + +impl TryFrom<&redis::Value> for ConfigValue { + type Error = FalkorDBError; + fn try_from(value: &redis::Value) -> Result { + Ok(match value { + redis::Value::Int(int_val) => ConfigValue::Int64(*int_val), + redis::Value::Data(str_data) => { + ConfigValue::String(String::from_utf8_lossy(str_data.as_slice()).to_string()) + } + _ => return Err(FalkorDBError::InvalidDataReceived), + }) + } +} + +impl FromRedisValue for FalkorValue { + fn from_redis_value(v: &redis::Value) -> RedisResult { + Ok(match v { + redis::Value::Nil => FalkorValue::None, + redis::Value::Int(int_val) => FalkorValue::I64(*int_val), + redis::Value::Data(str_val) => { + FalkorValue::String(String::from_utf8_lossy(str_val.as_slice()).to_string()) + } + redis::Value::Bulk(bulk) => FalkorValue::Array( + bulk.iter() + .flat_map(FalkorValue::from_redis_value) + .collect(), + ), + redis::Value::Status(status) => FalkorValue::String(status.to_string()), + redis::Value::Okay => FalkorValue::None, + }) + } +} diff --git a/src/response/constraint.rs b/src/response/constraint.rs new file mode 100644 index 0000000..8d1aadf --- /dev/null +++ b/src/response/constraint.rs @@ -0,0 +1,72 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +use crate::{ + value::utils::parse_type, EntityType, FalkorDBError, FalkorParsable, FalkorResult, FalkorValue, + GraphSchema, +}; + +/// The type of restriction to apply for the property +#[derive(Copy, Clone, Debug, Eq, PartialEq, strum::EnumString, strum::Display)] +#[strum(serialize_all = "UPPERCASE")] +pub enum ConstraintType { + /// This property may only appear once on entities of this type and label. + Unique, + /// This property must be provided when creating a new entity of this type and label, + /// and must exist on all entities of this type and label. + Mandatory, +} + +/// The status of this constraint +#[derive(Copy, Clone, Debug, Eq, PartialEq, strum::EnumString, strum::Display)] +pub enum ConstraintStatus { + /// This constraint is active on all entities of this type and label. + #[strum(serialize = "OPERATIONAL")] + Active, + /// This constraint is still being applied and verified. + #[strum(serialize = "UNDER CONSTRUCTION")] + Pending, + /// This constraint could not be applied, not all entities of this type and label are compliant. + Failed, +} + +/// A constraint applied on all 'properties' of the graph entity 'label' in this graph +#[derive(Clone, Debug, PartialEq)] +pub struct Constraint { + /// Is this constraint applies the 'unique' modifier or the 'mandatory' modifier + pub constraint_type: ConstraintType, + /// The name of this constraint + pub label: String, + /// The properties this constraint applies to + pub properties: Vec, + /// Whether this constraint applies to nodes or relationships + pub entity_type: EntityType, + /// Whether this constraint status is already active, still under construction, or failed construction + pub status: ConstraintStatus, +} + +impl FalkorParsable for Constraint { + fn from_falkor_value( + value: FalkorValue, + graph_schema: &mut GraphSchema, + ) -> FalkorResult { + let value_vec = parse_type(6, value, graph_schema)?.into_vec()?; + + let [constraint_type_raw, label_raw, properties_raw, entity_type_raw, status_raw]: [FalkorValue; 5] = value_vec.try_into() + .map_err(|_| FalkorDBError::ParsingArrayToStructElementCount("Expected exactly 5 elements in constraint object".to_string()))?; + + Ok(Constraint { + constraint_type: constraint_type_raw.into_string()?.as_str().try_into()?, + label: label_raw.into_string()?, + properties: properties_raw + .into_vec()? + .into_iter() + .flat_map(FalkorValue::into_string) + .collect(), + entity_type: entity_type_raw.into_string()?.as_str().try_into()?, + status: status_raw.into_string()?.as_str().try_into()?, + }) + } +} diff --git a/src/response/execution_plan.rs b/src/response/execution_plan.rs new file mode 100644 index 0000000..4d3f646 --- /dev/null +++ b/src/response/execution_plan.rs @@ -0,0 +1,251 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +use crate::{FalkorDBError, FalkorResult, FalkorValue}; +use regex::Regex; +use std::cell::RefCell; +use std::cmp::Ordering; +use std::collections::{HashMap, VecDeque}; +use std::ops::Not; +use std::rc::Rc; + +#[derive(Debug)] +struct IntermediateOperation { + name: String, + args: Option>, + records_produced: Option, + execution_time: Option, + depth: usize, + children: Vec>>, +} + +impl IntermediateOperation { + fn new( + depth: usize, + operation_string: &str, + ) -> FalkorResult { + let mut args = operation_string.split('|').collect::>(); + let name = args + .pop_front() + .ok_or(FalkorDBError::CorruptExecutionPlan)? + .trim(); + + let (records_produced, execution_time) = match args.pop_back() { + Some(last_arg) if last_arg.contains("Records produced") => ( + Regex::new(r"Records produced: (\d+)") + .map_err(|err| { + FalkorDBError::ParsingError(format!("Error constructing regex: {err}")) + })? + .captures(last_arg.trim()) + .and_then(|cap| cap.get(1)) + .and_then(|m| m.as_str().parse().ok()), + Regex::new(r"Execution time: (\d+\.\d+) ms") + .map_err(|err| { + FalkorDBError::ParsingError(format!("Error constructing regex: {err}")) + })? + .captures(last_arg.trim()) + .and_then(|cap| cap.get(1)) + .and_then(|m| m.as_str().parse().ok()), + ), + Some(last_arg) => { + args.push_back(last_arg); + (None, None) + } + None => (None, None), + }; + + Ok(Self { + name: name.to_string(), + args: args + .is_empty() + .not() + .then(|| args.into_iter().map(ToString::to_string).collect()), + records_produced, + execution_time, + depth, + children: vec![], + }) + } +} + +/// A graph operation, with its statistics if available, and pointers to its child operations +#[derive(Debug, Default, Clone, PartialEq)] +pub struct Operation { + /// The operation name, or string representation + pub name: String, + /// All arguments following + pub args: Option>, + /// The amount of records produced by this specific operation(regardless of later filtering), if any + pub records_produced: Option, + /// The time it took to execute this operation, if available + pub execution_time: Option, + /// all child operations performed on data retrieved, filtered or aggregated by this operation + pub children: Vec>, + depth: usize, +} + +/// An execution plan, allowing access both to the human-readable text representation, access to a per-operation map, or traversable operation tree +#[derive(Debug, Clone, PartialEq)] +pub struct ExecutionPlan { + string_representation: String, + plan: Vec, + operations: HashMap>>, + operation_tree: Rc, +} + +impl ExecutionPlan { + /// Returns the plan as a slice of human-readable strings + pub fn plan(&self) -> &[String] { + self.plan.as_slice() + } + + /// Returns a slice of strings representing each step in the execution plan, which can be iterated. + pub fn operations(&self) -> &HashMap>> { + &self.operations + } + + /// Returns a shared pointer to the operation tree, allowing easy immutable traversal + pub fn operation_tree(&self) -> &Rc { + &self.operation_tree + } + + /// Returns a string representation of the entire execution plan + pub fn string_representation(&self) -> &str { + self.string_representation.as_str() + } + + fn create_node( + depth: usize, + operation_string: &str, + traversal_stack: &mut Vec>>, + ) -> FalkorResult<()> { + let new_node = Rc::new(RefCell::new(IntermediateOperation::new( + depth, + operation_string, + )?)); + + traversal_stack.push(Rc::clone(&new_node)); + Ok(()) + } + + fn finalize_operation( + current_refcell: Rc> + ) -> FalkorResult> { + let current_op = Rc::try_unwrap(current_refcell) + .map_err(|_| FalkorDBError::RefCountBooBoo)? + .into_inner(); + + let mut out_vec = Vec::with_capacity(current_op.children.len()); + for child in current_op.children.into_iter() { + out_vec.push(Self::finalize_operation(child)?); + } + + Ok(Rc::new(Operation { + name: current_op.name, + args: current_op.args, + records_produced: current_op.records_produced, + execution_time: current_op.execution_time, + depth: current_op.depth, + children: out_vec, + })) + } + + fn operations_map_from_tree( + current_branch: &Rc, + map: &mut HashMap>>, + ) { + map.entry(current_branch.name.clone()) + .or_default() + .push(Rc::clone(current_branch)); + + for child in ¤t_branch.children { + Self::operations_map_from_tree(child, map); + } + } +} + +impl TryFrom for ExecutionPlan { + type Error = FalkorDBError; + + fn try_from(value: FalkorValue) -> Result { + let execution_plan_operations: Vec<_> = value + .into_vec()? + .into_iter() + .flat_map(FalkorValue::into_string) + .collect(); + + let string_representation = ["".to_string()] + .into_iter() + .chain(execution_plan_operations.iter().cloned()) + .collect::>() + .join("\n"); + + let mut current_traversal_stack = vec![]; + for node in execution_plan_operations.iter().map(String::as_str) { + let depth = node.matches(" ").count(); + let node = node.trim(); + + let current_node = match current_traversal_stack.last().cloned() { + None => { + current_traversal_stack.push(Rc::new(RefCell::new( + IntermediateOperation::new(depth, node)?, + ))); + continue; + } + Some(current_node) => current_node, + }; + + let current_depth = current_node.borrow().depth; + match depth.cmp(¤t_depth) { + Ordering::Less => { + let times_to_pop = (current_depth - depth) + 1; + if times_to_pop > current_traversal_stack.len() { + return Err(FalkorDBError::CorruptExecutionPlan); + } + for _ in 0..times_to_pop { + current_traversal_stack.pop(); + } + + // Create this node as a child to the last node with one less depth than the new node + Self::create_node(depth, node, &mut current_traversal_stack)?; + } + Ordering::Equal => { + // Push new node to the parent node + current_traversal_stack.pop(); + Self::create_node(depth, node, &mut current_traversal_stack)?; + } + Ordering::Greater => { + if depth - current_depth > 1 { + // Too big a skip + return Err(FalkorDBError::CorruptExecutionPlan); + } + + let new_node = Rc::new(RefCell::new(IntermediateOperation::new(depth, node)?)); + current_traversal_stack.push(Rc::clone(&new_node)); + + // New node is a child of the current node, so we will push it as a child + current_node.borrow_mut().children.push(new_node); + } + } + } + + // Must drop traversal stack first + let root_node = current_traversal_stack + .into_iter() + .next() + .ok_or(FalkorDBError::CorruptExecutionPlan)?; + let operation_tree = Self::finalize_operation(root_node)?; + + let mut operations = HashMap::new(); + Self::operations_map_from_tree(&operation_tree, &mut operations); + + Ok(ExecutionPlan { + string_representation, + plan: execution_plan_operations, + operations, + operation_tree, + }) + } +} diff --git a/src/response/index.rs b/src/response/index.rs new file mode 100644 index 0000000..ea391dd --- /dev/null +++ b/src/response/index.rs @@ -0,0 +1,107 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +use crate::{ + value::utils::{parse_type, parse_vec, type_val_from_value}, + EntityType, FalkorDBError, FalkorParsable, FalkorValue, GraphSchema, +}; +use std::collections::HashMap; + +/// The status of this index +#[derive(Copy, Clone, Debug, Eq, PartialEq, strum::EnumString, strum::Display)] +#[strum(serialize_all = "UPPERCASE")] +pub enum IndexStatus { + /// This index is active. + #[strum(serialize = "OPERATIONAL")] + Active, + /// This index is still being created. + #[strum(serialize = "UNDER CONSTRUCTION")] + Pending, +} + +/// The type of this indexed field +#[derive(Copy, Clone, Debug, Eq, PartialEq, strum::EnumString, strum::Display)] +#[strum(serialize_all = "UPPERCASE")] +pub enum IndexType { + /// This index is a range + Range, + /// This index is raw vector data + Vector, + /// This index is a string + Fulltext, +} + +fn parse_types_map(value: FalkorValue) -> Result>, FalkorDBError> { + let value: HashMap = value.try_into()?; + + let mut out_map = HashMap::with_capacity(value.len()); + for (key, val) in value.into_iter() { + let val = val.into_vec()?; + let mut field_types = Vec::with_capacity(val.len()); + for field_type in val { + field_types.push(field_type.into_string()?.as_str().try_into()?); + } + + out_map.insert(key, field_types); + } + + Ok(out_map) +} + +/// Contains all the info regarding an index on the database +#[derive(Clone, Debug, PartialEq)] +pub struct FalkorIndex { + /// Whether this index is for a Node or on an Edge + pub entity_type: EntityType, + /// Whether this index is active or still under construction + pub status: IndexStatus, + /// What is this index's label + pub index_label: String, + /// What fields to index by + pub fields: Vec, + /// Whether each field is a text field, range, etc. + pub field_types: HashMap>, + /// Which language is the text used to index in + pub language: String, + /// Words to avoid indexing as they are very common and will just be a waste of resources + pub stopwords: Vec, + /// Various other information for querying by the user + pub info: HashMap, +} + +impl FalkorIndex { + fn from_raw_values(items: Vec) -> Result { + let [label, fields, field_types, language, stopwords, entity_type, status, info]: [FalkorValue; 8] = items.try_into().map_err(|_| FalkorDBError::ParsingArrayToStructElementCount("Expected exactly 8 elements in index object".to_string()))?; + + Ok(Self { + entity_type: entity_type.into_string()?.as_str().try_into()?, + status: status.into_string()?.as_str().try_into()?, + index_label: label.try_into()?, + fields: parse_vec(fields)?, + field_types: parse_types_map(field_types)?, + language: language.try_into()?, + stopwords: parse_vec(stopwords)?, + info: info.try_into()?, + }) + } +} + +impl FalkorParsable for FalkorIndex { + fn from_falkor_value( + value: FalkorValue, + graph_schema: &mut GraphSchema, + ) -> Result { + let semi_parsed_items = value + .into_vec()? + .into_iter() + .flat_map(|item| { + let (type_marker, val) = type_val_from_value(item)?; + parse_type(type_marker, val, graph_schema) + }) + .collect::>(); + + Self::from_raw_values(semi_parsed_items) + } +} diff --git a/src/response/mod.rs b/src/response/mod.rs new file mode 100644 index 0000000..f94c0d6 --- /dev/null +++ b/src/response/mod.rs @@ -0,0 +1,69 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +use crate::{ + parser::utils::{parse_header, string_vec_from_val}, + FalkorResult, FalkorValue, +}; + +pub(crate) mod constraint; +pub(crate) mod execution_plan; +pub(crate) mod index; +pub(crate) mod slowlog_entry; + +/// A [`Vec`], representing a table of other [`Vec`]s, representing columns, containing [`FalkorValue`]s +pub type ResultSet = Vec>; + +/// A response struct which also contains the returned header and stats data +#[derive(Clone, Debug, Default)] +pub struct FalkorResponse { + /// Header for the result data, usually contains the scalar aliases for the columns + pub header: Vec, + /// The actual data returned from the database + pub data: T, + /// Various statistics regarding the request, such as execution time and number of successful operations + pub stats: Vec, +} + +impl FalkorResponse { + /// Creates a [`FalkorResponse`] from the specified data, and raw stats, where raw headers are optional + /// + /// # Arguments + /// * `headers`: a [`FalkorValue`] that is expected to be of variant [`FalkorValue::Array`], where each element is expected to be of variant [`FalkorValue::String`] + /// * `data`: The actual data + /// * `stats`: a [`FalkorValue`] that is expected to be of variant [`FalkorValue::Array`], where each element is expected to be of variant [`FalkorValue::String`] + pub fn from_response( + headers: Option, + data: T, + stats: FalkorValue, + ) -> FalkorResult { + Ok(Self { + header: match headers { + Some(headers) => parse_header(headers)?, + None => vec![], + }, + data, + stats: string_vec_from_val(stats)?, + }) + } + + /// Creates a [`FalkorResponse`] from the specified data, and raw stats, where parsed headers are given and parsing them is not needed + /// + /// # Arguments + /// * `data`: The actual data + /// * `headers`: The already parsed headers, this is usually used to pass an empty header vec, or if we already parsed the headers for use in some other context and don't want to repeat + /// * `stats`: a [`FalkorValue`] that is expected to be of variant [`FalkorValue::Array`], where each element is expected to be of variant [`FalkorValue::String`] + pub fn from_response_with_headers( + data: T, + header: Vec, + stats: FalkorValue, + ) -> FalkorResult { + Ok(Self { + header, + data, + stats: string_vec_from_val(stats)?, + }) + } +} diff --git a/src/response/slowlog_entry.rs b/src/response/slowlog_entry.rs new file mode 100644 index 0000000..f425e2f --- /dev/null +++ b/src/response/slowlog_entry.rs @@ -0,0 +1,45 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +use crate::{FalkorDBError, FalkorValue}; + +/// A slowlog entry, representing one of the N slowest queries in the current log +#[derive(Clone, Debug, PartialEq)] +pub struct SlowlogEntry { + /// At which time was this query received + pub timestamp: i64, + /// Which command was used to perform this query + pub command: String, + /// The query itself + pub arguments: String, + /// How long did performing this query take. + pub time_taken: f64, +} + +impl TryFrom for SlowlogEntry { + type Error = FalkorDBError; + + fn try_from(value: FalkorValue) -> Result { + let [timestamp, command, arguments, time_taken] = + value.into_vec()?.try_into().map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 4 elements of slowlog entry".to_string(), + ) + })?; + + Ok(Self { + timestamp: timestamp + .into_string()? + .parse() + .map_err(|_| FalkorDBError::ParsingI64)?, + command: command.into_string()?, + arguments: arguments.into_string()?, + time_taken: time_taken + .into_string()? + .parse() + .map_err(|_| FalkorDBError::ParsingF64)?, + }) + } +} diff --git a/src/value/config.rs b/src/value/config.rs new file mode 100644 index 0000000..80baeac --- /dev/null +++ b/src/value/config.rs @@ -0,0 +1,68 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +use crate::{FalkorDBError, FalkorValue}; +use std::fmt::{Display, Formatter}; + +/// An enum representing the two viable types for a config value +#[derive(Clone, Debug, PartialEq)] +pub enum ConfigValue { + /// A string value + String(String), + /// An int value, also used to represent booleans + Int64(i64), +} + +impl ConfigValue { + /// Returns a copy of the contained int value, if there is one. + pub fn as_i64(&self) -> Option { + match self { + ConfigValue::String(_) => None, + ConfigValue::Int64(i64) => Some(*i64), + } + } +} + +impl Display for ConfigValue { + fn fmt( + &self, + f: &mut Formatter<'_>, + ) -> std::fmt::Result { + match self { + ConfigValue::String(str_val) => str_val.fmt(f), + ConfigValue::Int64(int_val) => int_val.fmt(f), + } + } +} + +impl From for ConfigValue { + fn from(value: i64) -> Self { + ConfigValue::Int64(value) + } +} + +impl From for ConfigValue { + fn from(value: String) -> Self { + ConfigValue::String(value) + } +} + +impl From<&str> for ConfigValue { + fn from(value: &str) -> Self { + ConfigValue::String(value.to_string()) + } +} + +impl TryFrom for ConfigValue { + type Error = FalkorDBError; + + fn try_from(value: FalkorValue) -> Result { + match value { + FalkorValue::String(str_val) => Ok(ConfigValue::String(str_val)), + FalkorValue::I64(int_val) => Ok(ConfigValue::Int64(int_val)), + _ => Err(FalkorDBError::ParsingConfigValue), + } + } +} diff --git a/src/value/graph_entities.rs b/src/value/graph_entities.rs new file mode 100644 index 0000000..ddd742f --- /dev/null +++ b/src/value/graph_entities.rs @@ -0,0 +1,101 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +use crate::{FalkorDBError, FalkorParsable, FalkorResult, FalkorValue, GraphSchema, SchemaType}; +use std::collections::{HashMap, HashSet}; + +/// Whether this element is a node or edge in the graph +#[derive(Copy, Clone, Debug, Eq, PartialEq, strum::EnumString, strum::Display)] +#[strum(serialize_all = "UPPERCASE")] +pub enum EntityType { + /// A node in the graph + Node, + /// An edge in the graph, meaning a relationship between two nodes + #[strum(serialize = "RELATIONSHIP")] + Edge, +} + +/// A node in the graph, containing a unique id, various labels describing it, and its own property. +#[derive(Clone, Debug, Default, PartialEq)] +pub struct Node { + /// The internal entity ID + pub entity_id: i64, + /// A [`Vec`] of the labels this node answers to + pub labels: Vec, + /// A [`HashMap`] of the properties in key-val form + pub properties: HashMap, +} + +impl FalkorParsable for Node { + fn from_falkor_value( + value: FalkorValue, + graph_schema: &mut GraphSchema, + ) -> FalkorResult { + let [entity_id, labels, properties]: [FalkorValue; 3] = + value.into_vec()?.try_into().map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 3 elements in node object".to_string(), + ) + })?; + let labels = labels.into_vec()?; + + let mut ids_hashset = HashSet::with_capacity(labels.len()); + for label in labels.iter() { + ids_hashset.insert( + label + .to_i64() + .ok_or(FalkorDBError::ParsingCompactIdUnknown)?, + ); + } + Ok(Node { + entity_id: entity_id.to_i64().ok_or(FalkorDBError::ParsingI64)?, + labels: graph_schema.parse_id_vec(labels, SchemaType::Labels)?, + properties: graph_schema.parse_properties_map(properties)?, + }) + } +} + +/// An edge in the graph, representing a relationship between two [`Node`]s. +#[derive(Clone, Debug, Default, PartialEq)] +pub struct Edge { + /// The internal entity ID + pub entity_id: i64, + /// What type is this relationship + pub relationship_type: String, + /// The entity ID of the origin node + pub src_node_id: i64, + /// The entity ID of the destination node + pub dst_node_id: i64, + /// A [`HashMap`] of the properties in key-val form + pub properties: HashMap, +} + +impl FalkorParsable for Edge { + fn from_falkor_value( + value: FalkorValue, + graph_schema: &mut GraphSchema, + ) -> FalkorResult { + let [entity_id, relations, src_node_id, dst_node_id, properties]: [FalkorValue; 5] = + value.into_vec()?.try_into().map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 5 elements in edge object".to_string(), + ) + })?; + + let relation = relations.to_i64().ok_or(FalkorDBError::ParsingI64)?; + let relationship = graph_schema + .relationships() + .get(&relation) + .ok_or(FalkorDBError::MissingSchemaId(SchemaType::Relationships))?; + + Ok(Edge { + entity_id: entity_id.to_i64().ok_or(FalkorDBError::ParsingI64)?, + relationship_type: relationship.to_string(), + src_node_id: src_node_id.to_i64().ok_or(FalkorDBError::ParsingI64)?, + dst_node_id: dst_node_id.to_i64().ok_or(FalkorDBError::ParsingI64)?, + properties: graph_schema.parse_properties_map(properties)?, + }) + } +} diff --git a/src/value/map.rs b/src/value/map.rs new file mode 100644 index 0000000..e6fd140 --- /dev/null +++ b/src/value/map.rs @@ -0,0 +1,161 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +use crate::{ + value::utils::parse_type, FalkorDBError, FalkorParsable, FalkorResult, FalkorValue, GraphSchema, +}; +use std::collections::HashMap; + +impl FalkorParsable for HashMap { + fn from_falkor_value( + value: FalkorValue, + graph_schema: &mut GraphSchema, + ) -> FalkorResult { + let val_vec = value.into_vec()?; + if val_vec.len() % 2 != 0 { + Err(FalkorDBError::ParsingFMap( + "Map should have an even amount of elements".to_string(), + ))?; + } + + let mut out_map = HashMap::with_capacity(val_vec.len()); + for pair in val_vec.chunks_exact(2) { + let [key, val]: [FalkorValue; 2] = pair.to_vec().try_into().map_err(|_| { + FalkorDBError::ParsingFMap( + "The vec returned from using chunks_exact(2) should be comprised of 2 elements" + .to_string(), + ) + })?; + + let [type_marker, val]: [FalkorValue; 2] = + val.into_vec()?.try_into().map_err(|_| { + FalkorDBError::ParsingFMap( + "The value in a map should be comprised of a type marker and value" + .to_string(), + ) + })?; + + out_map.insert( + key.into_string()?, + parse_type( + type_marker.to_i64().ok_or(FalkorDBError::ParsingKTVTypes)?, + val, + graph_schema, + )?, + ); + } + + Ok(out_map) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{client::blocking::create_empty_inner_client, GraphSchema}; + + #[test] + fn test_not_a_vec() { + let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); + + let res: FalkorResult> = FalkorParsable::from_falkor_value( + FalkorValue::String("Hello".to_string()), + &mut graph_schema, + ); + + assert!(res.is_err()) + } + + #[test] + fn test_vec_odd_element_count() { + let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); + + let res: FalkorResult> = FalkorParsable::from_falkor_value( + FalkorValue::Array(vec![FalkorValue::None; 7]), + &mut graph_schema, + ); + + assert!(res.is_err()) + } + + #[test] + fn test_val_element_is_not_array() { + let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); + + let res: FalkorResult> = FalkorParsable::from_falkor_value( + FalkorValue::Array(vec![ + FalkorValue::String("Key".to_string()), + FalkorValue::Bool(false), + ]), + &mut graph_schema, + ); + + assert!(res.is_err()) + } + + #[test] + fn test_val_element_has_only_1_element() { + let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); + + let res: FalkorResult> = FalkorParsable::from_falkor_value( + FalkorValue::Array(vec![ + FalkorValue::String("Key".to_string()), + FalkorValue::Array(vec![FalkorValue::I64(7)]), + ]), + &mut graph_schema, + ); + + assert!(res.is_err()) + } + + #[test] + fn test_val_element_has_ge_2_elements() { + let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); + + let res: FalkorResult> = FalkorParsable::from_falkor_value( + FalkorValue::Array(vec![ + FalkorValue::String("Key".to_string()), + FalkorValue::Array(vec![FalkorValue::I64(3); 3]), + ]), + &mut graph_schema, + ); + + assert!(res.is_err()) + } + + #[test] + fn test_val_element_mismatch_type_marker() { + let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); + + let res: FalkorResult> = FalkorParsable::from_falkor_value( + FalkorValue::Array(vec![ + FalkorValue::String("Key".to_string()), + FalkorValue::Array(vec![FalkorValue::I64(3), FalkorValue::Bool(true)]), + ]), + &mut graph_schema, + ); + + assert!(res.is_err()) + } + + #[test] + fn test_ok_values() { + let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); + + let res: HashMap = FalkorParsable::from_falkor_value( + FalkorValue::Array(vec![ + FalkorValue::String("IntKey".to_string()), + FalkorValue::Array(vec![FalkorValue::I64(3), FalkorValue::I64(1)]), + FalkorValue::String("BoolKey".to_string()), + FalkorValue::Array(vec![FalkorValue::I64(4), FalkorValue::Bool(true)]), + ]), + &mut graph_schema, + ) + .expect("Could not parse map"); + + assert_eq!(res.get("IntKey"), Some(FalkorValue::I64(1)).as_ref()); + assert_eq!(res.get("BoolKey"), Some(FalkorValue::Bool(true)).as_ref()); + } +} diff --git a/src/value/mod.rs b/src/value/mod.rs new file mode 100644 index 0000000..5e9c5e0 --- /dev/null +++ b/src/value/mod.rs @@ -0,0 +1,447 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +use crate::{FalkorDBError, FalkorParsable, FalkorResult, GraphSchema}; +use graph_entities::{Edge, Node}; +use path::Path; +use point::Point; +use std::{collections::HashMap, fmt::Debug}; + +pub(crate) mod config; +pub(crate) mod graph_entities; +pub(crate) mod map; +pub(crate) mod path; +pub(crate) mod point; +pub(crate) mod utils; + +/// An enum of all the supported Falkor types +#[derive(Clone, Debug, PartialEq)] +pub enum FalkorValue { + /// See [`Node`] + Node(Node), + /// See [`Edge`] + Edge(Edge), + /// A [`Vec`] of other [`FalkorValue`] + Array(Vec), + /// A [`HashMap`] of [`String`] as keys, and other [`FalkorValue`] as values + Map(HashMap), + /// Plain old string + String(String), + /// A boolean value + Bool(bool), + /// An [`i64`] value, Falkor only supports signed integers + I64(i64), + /// An [`f64`] value, Falkor only supports double precisions when not in Vectors + F64(f64), + /// See [`Point`] + Point(Point), + /// See [`Path`] + Path(Path), + /// A NULL type + None, +} + +macro_rules! impl_to_falkordb_value { + ($t:ty, $falkordbtype:expr) => { + impl From<$t> for FalkorValue { + fn from(value: $t) -> Self { + $falkordbtype(value as _) + } + } + }; +} + +impl_to_falkordb_value!(i8, Self::I64); +impl_to_falkordb_value!(i32, Self::I64); +impl_to_falkordb_value!(i64, Self::I64); + +impl_to_falkordb_value!(u8, Self::I64); +impl_to_falkordb_value!(u32, Self::I64); +impl_to_falkordb_value!(u64, Self::I64); + +impl_to_falkordb_value!(f32, Self::F64); +impl_to_falkordb_value!(f64, Self::F64); + +impl_to_falkordb_value!(String, Self::String); + +impl From<&str> for FalkorValue { + fn from(value: &str) -> Self { + Self::String(value.to_string()) + } +} + +impl From> for FalkorValue +where + FalkorValue: From, +{ + fn from(value: Vec) -> Self { + Self::Array( + value + .into_iter() + .map(|element| FalkorValue::from(element)) + .collect(), + ) + } +} + +impl TryFrom for Vec { + type Error = FalkorDBError; + + fn try_from(value: FalkorValue) -> FalkorResult { + match value { + FalkorValue::Array(val) => Ok(val), + _ => Err(FalkorDBError::ParsingFArray), + } + } +} + +impl TryFrom for f64 { + type Error = FalkorDBError; + + fn try_from(value: FalkorValue) -> FalkorResult { + match value { + FalkorValue::String(f64_str) => f64_str.parse().map_err(|_| FalkorDBError::ParsingF64), + FalkorValue::F64(f64_val) => Ok(f64_val), + _ => Err(FalkorDBError::ParsingF64), + } + } +} + +impl TryFrom for String { + type Error = FalkorDBError; + + fn try_from(value: FalkorValue) -> FalkorResult { + match value { + FalkorValue::String(val) => Ok(val), + _ => Err(FalkorDBError::ParsingFString), + } + } +} + +impl TryFrom for Edge { + type Error = FalkorDBError; + + fn try_from(value: FalkorValue) -> FalkorResult { + match value { + FalkorValue::Edge(edge) => Ok(edge), + _ => Err(FalkorDBError::ParsingFEdge), + } + } +} + +impl TryFrom for Node { + type Error = FalkorDBError; + + fn try_from(value: FalkorValue) -> FalkorResult { + match value { + FalkorValue::Node(node) => Ok(node), + _ => Err(FalkorDBError::ParsingFNode), + } + } +} + +impl TryFrom for Path { + type Error = FalkorDBError; + + fn try_from(value: FalkorValue) -> FalkorResult { + match value { + FalkorValue::Path(path) => Ok(path), + _ => Err(FalkorDBError::ParsingFPath), + } + } +} + +impl TryFrom for HashMap { + type Error = FalkorDBError; + + fn try_from(value: FalkorValue) -> FalkorResult { + match value { + FalkorValue::Map(map) => Ok(map), + _ => Err(FalkorDBError::ParsingFMap( + "Attempting to get a non-map element as a map".to_string(), + )), + } + } +} + +impl TryFrom for Point { + type Error = FalkorDBError; + + fn try_from(value: FalkorValue) -> FalkorResult { + match value { + FalkorValue::Point(point) => Ok(point), + _ => Err(FalkorDBError::ParsingFPoint), + } + } +} + +impl FalkorValue { + /// Returns a reference to the internal [`Vec`] if this is an FArray variant. + /// + /// # Returns + /// A reference to the internal [`Vec`] + pub fn as_vec(&self) -> Option<&Vec> { + match self { + FalkorValue::Array(val) => Some(val), + _ => None, + } + } + + /// Returns a reference to the internal [`String`] if this is an FString variant. + /// + /// # Returns + /// A reference to the internal [`String`] + pub fn as_string(&self) -> Option<&String> { + match self { + FalkorValue::String(val) => Some(val), + _ => None, + } + } + + /// Returns a reference to the internal [`Edge`] if this is an FEdge variant. + /// + /// # Returns + /// A reference to the internal [`Edge`] + pub fn as_edge(&self) -> Option<&Edge> { + match self { + FalkorValue::Edge(val) => Some(val), + _ => None, + } + } + + /// Returns a reference to the internal [`Node`] if this is an FNode variant. + /// + /// # Returns + /// A reference to the internal [`Node`] + pub fn as_node(&self) -> Option<&Node> { + match self { + FalkorValue::Node(val) => Some(val), + _ => None, + } + } + + /// Returns a reference to the internal [`Path`] if this is an FPath variant. + /// + /// # Returns + /// A reference to the internal [`Path`] + pub fn as_path(&self) -> Option<&Path> { + match self { + FalkorValue::Path(val) => Some(val), + _ => None, + } + } + + /// Returns a reference to the internal [`HashMap`] if this is an FMap variant. + /// + /// # Returns + /// A reference to the internal [`HashMap`] + pub fn as_map(&self) -> Option<&HashMap> { + match self { + FalkorValue::Map(val) => Some(val), + _ => None, + } + } + + /// Returns a reference to the internal [`Point`] if this is an FPoint variant. + /// + /// # Returns + /// A reference to the internal [`Point`] + pub fn as_point(&self) -> Option<&Point> { + match self { + FalkorValue::Point(val) => Some(val), + _ => None, + } + } + + /// Returns a Copy of the inner [`i64`] if this is an Int64 variant + /// + /// # Returns + /// A copy of the inner [`i64`] + pub fn to_i64(&self) -> Option { + match self { + FalkorValue::I64(val) => Some(*val), + _ => None, + } + } + + /// Returns a Copy of the inner [`bool`] if this is an FBool variant + /// + /// # Returns + /// A copy of the inner [`bool`] + pub fn to_bool(&self) -> Option { + match self { + FalkorValue::Bool(val) => Some(*val), + FalkorValue::String(bool_str) => match bool_str.as_str() { + "true" => Some(true), + "false" => Some(false), + _ => None, + }, + _ => None, + } + } + + /// Returns a Copy of the inner [`f64`] if this is an F64 variant + /// + /// # Returns + /// A copy of the inner [`f64`] + pub fn to_f64(&self) -> Option { + match self { + FalkorValue::F64(val) => Some(*val), + _ => None, + } + } + + /// Consumes itself and returns the inner [`Vec`] if this is an FArray variant + /// + /// # Returns + /// The inner [`Vec`] + pub fn into_vec(self) -> FalkorResult> { + self.try_into() + } + + /// Consumes itself and returns the inner [`String`] if this is an FString variant + /// + /// # Returns + /// The inner [`String`] + pub fn into_string(self) -> FalkorResult { + self.try_into() + } +} + +impl FalkorParsable for FalkorValue { + fn from_falkor_value( + value: FalkorValue, + _: &mut GraphSchema, + ) -> FalkorResult { + Ok(value) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashMap; + use std::f64::consts::PI; + + #[test] + fn test_as_vec() { + let vec_val = FalkorValue::Array(vec![FalkorValue::I64(1), FalkorValue::I64(2)]); + assert_eq!(vec_val.as_vec().unwrap().len(), 2); + + let non_vec_val = FalkorValue::I64(42); + assert!(non_vec_val.as_vec().is_none()); + } + + #[test] + fn test_as_string() { + let string_val = FalkorValue::String(String::from("hello")); + assert_eq!(string_val.as_string().unwrap(), "hello"); + + let non_string_val = FalkorValue::I64(42); + assert!(non_string_val.as_string().is_none()); + } + + #[test] + fn test_as_edge() { + let edge = Edge::default(); // Assuming Edge::new() is a valid constructor + let edge_val = FalkorValue::Edge(edge); + assert!(edge_val.as_edge().is_some()); + + let non_edge_val = FalkorValue::I64(42); + assert!(non_edge_val.as_edge().is_none()); + } + + #[test] + fn test_as_node() { + let node = Node::default(); // Assuming Node::new() is a valid constructor + let node_val = FalkorValue::Node(node); + assert!(node_val.as_node().is_some()); + + let non_node_val = FalkorValue::I64(42); + assert!(non_node_val.as_node().is_none()); + } + + #[test] + fn test_as_path() { + let path = Path::default(); // Assuming Path::new() is a valid constructor + let path_val = FalkorValue::Path(path); + assert!(path_val.as_path().is_some()); + + let non_path_val = FalkorValue::I64(42); + assert!(non_path_val.as_path().is_none()); + } + + #[test] + fn test_as_map() { + let mut map = HashMap::new(); + map.insert(String::from("key"), FalkorValue::I64(42)); + let map_val = FalkorValue::Map(map); + assert!(map_val.as_map().is_some()); + + let non_map_val = FalkorValue::I64(42); + assert!(non_map_val.as_map().is_none()); + } + + #[test] + fn test_as_point() { + let point = Point::default(); // Assuming Point::new() is a valid constructor + let point_val = FalkorValue::Point(point); + assert!(point_val.as_point().is_some()); + + let non_point_val = FalkorValue::I64(42); + assert!(non_point_val.as_point().is_none()); + } + + #[test] + fn test_to_i64() { + let int_val = FalkorValue::I64(42); + assert_eq!(int_val.to_i64().unwrap(), 42); + + let non_int_val = FalkorValue::String(String::from("hello")); + assert!(non_int_val.to_i64().is_none()); + } + + #[test] + fn test_to_bool() { + let bool_val = FalkorValue::Bool(true); + assert!(bool_val.to_bool().unwrap()); + + let bool_str_val = FalkorValue::String(String::from("false")); + assert!(!bool_str_val.to_bool().unwrap()); + + let invalid_bool_str_val = FalkorValue::String(String::from("notabool")); + assert!(invalid_bool_str_val.to_bool().is_none()); + + let non_bool_val = FalkorValue::I64(42); + assert!(non_bool_val.to_bool().is_none()); + } + + #[test] + fn test_to_f64() { + let float_val = FalkorValue::F64(PI); + assert_eq!(float_val.to_f64().unwrap(), PI); + + let non_float_val = FalkorValue::String(String::from("hello")); + assert!(non_float_val.to_f64().is_none()); + } + + #[test] + fn test_into_vec() { + let vec_val = FalkorValue::Array(vec![FalkorValue::I64(1), FalkorValue::I64(2)]); + assert_eq!(vec_val.into_vec().unwrap().len(), 2); + + let non_vec_val = FalkorValue::I64(42); + assert!(non_vec_val.into_vec().is_err()); + } + + #[test] + fn test_into_string() { + let string_val = FalkorValue::String(String::from("hello")); + assert_eq!(string_val.into_string().unwrap(), "hello"); + + let non_string_val = FalkorValue::I64(42); + assert!(non_string_val.into_string().is_err()); + } +} diff --git a/src/value/path.rs b/src/value/path.rs new file mode 100644 index 0000000..6f499fe --- /dev/null +++ b/src/value/path.rs @@ -0,0 +1,42 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +use crate::{Edge, FalkorDBError, FalkorParsable, FalkorResult, FalkorValue, GraphSchema, Node}; + +/// Represents a path between two nodes, contains all the nodes, and the relationships between them along the path +#[derive(Clone, Debug, Default, PartialEq)] +pub struct Path { + /// The nodes along the path, ordered + pub nodes: Vec, + /// The relationships between the nodes in the path, ordered + pub relationships: Vec, +} + +impl FalkorParsable for Path { + fn from_falkor_value( + value: FalkorValue, + graph_schema: &mut GraphSchema, + ) -> FalkorResult { + let [nodes, relationships]: [FalkorValue; 2] = + value.into_vec()?.try_into().map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 2 elements for path".to_string(), + ) + })?; + + Ok(Self { + nodes: nodes + .into_vec()? + .into_iter() + .flat_map(|node| Node::from_falkor_value(node, graph_schema)) + .collect(), + relationships: relationships + .into_vec()? + .into_iter() + .flat_map(|edge| Edge::from_falkor_value(edge, graph_schema)) + .collect(), + }) + } +} diff --git a/src/value/point.rs b/src/value/point.rs new file mode 100644 index 0000000..afa1a54 --- /dev/null +++ b/src/value/point.rs @@ -0,0 +1,111 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +use crate::{FalkorDBError, FalkorResult, FalkorValue}; + +/// A point in the world. +#[derive(Clone, Debug, Default, PartialEq)] +pub struct Point { + /// The latitude coordinate + pub latitude: f64, + /// The longitude coordinate + pub longitude: f64, +} + +impl Point { + /// Parses a point from a FalkorValue::Array, + /// taking the first element as an f64 latitude, and second element as an f64 longitude + /// + /// # Arguments + /// * `value`: The value to parse + /// + /// # Returns + /// Self, if successful + pub fn parse(value: FalkorValue) -> FalkorResult { + let [lat, long]: [FalkorValue; 2] = value.into_vec()?.try_into().map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 2 element in point - latitude and longitude".to_string(), + ) + })?; + + Ok(Point { + latitude: lat.to_f64().ok_or(FalkorDBError::ParsingF64)?, + longitude: long.to_f64().ok_or(FalkorDBError::ParsingF64)?, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_valid_point() { + let value = FalkorValue::Array(vec![FalkorValue::F64(45.0), FalkorValue::F64(90.0)]); + let result = Point::parse(value); + assert!(result.is_ok()); + let point = result.unwrap(); + assert_eq!(point.latitude, 45.0); + assert_eq!(point.longitude, 90.0); + } + + #[test] + fn test_parse_invalid_point_missing_elements() { + let value = FalkorValue::Array(vec![FalkorValue::F64(45.0)]); + let result = Point::parse(value); + assert!(result.is_err()); + match result { + Err(FalkorDBError::ParsingArrayToStructElementCount(msg)) => { + assert_eq!( + msg, + "Expected exactly 2 element in point - latitude and longitude".to_string() + ); + } + _ => panic!("Expected ParsingArrayToStructElementCount error"), + } + } + + #[test] + fn test_parse_invalid_point_extra_elements() { + let value = FalkorValue::Array(vec![ + FalkorValue::F64(45.0), + FalkorValue::F64(90.0), + FalkorValue::F64(30.0), + ]); + let result = Point::parse(value); + assert!(result.is_err()); + match result { + Err(FalkorDBError::ParsingArrayToStructElementCount(msg)) => { + assert_eq!( + msg, + "Expected exactly 2 element in point - latitude and longitude".to_string() + ); + } + _ => panic!("Expected ParsingArrayToStructElementCount error"), + } + } + + #[test] + fn test_parse_invalid_point_non_f64_elements() { + let value = FalkorValue::Array(vec![ + FalkorValue::String("45.0".to_string()), + FalkorValue::String("90.0".to_string()), + ]); + let result = Point::parse(value); + assert!(result.is_err()); + match result { + Err(FalkorDBError::ParsingF64) => {} + _ => panic!("Expected ParsingF64 error"), + } + } + + #[test] + fn test_parse_invalid_point_not_an_array() { + let value = FalkorValue::String("not an array".to_string()); + let result = Point::parse(value); + assert!(result.is_err()); + // Check for the specific error type if needed + } +} diff --git a/src/value/utils.rs b/src/value/utils.rs new file mode 100644 index 0000000..2a8e68b --- /dev/null +++ b/src/value/utils.rs @@ -0,0 +1,283 @@ +/* + * Copyright FalkorDB Ltd. 2023 - present + * Licensed under the Server Side Public License v1 (SSPLv1). + */ + +use crate::{FalkorDBError, FalkorParsable, FalkorValue, GraphSchema, Point}; + +pub(crate) fn type_val_from_value(value: FalkorValue) -> Result<(i64, FalkorValue), FalkorDBError> { + let [type_marker, val]: [FalkorValue; 2] = value.into_vec()?.try_into().map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 2 elements: type marker, and value".to_string(), + ) + })?; + let type_marker = type_marker.to_i64().ok_or(FalkorDBError::ParsingI64)?; + + Ok((type_marker, val)) +} + +pub(crate) fn parse_type( + type_marker: i64, + val: FalkorValue, + graph_schema: &mut GraphSchema, +) -> Result { + let res = match type_marker { + 1 => FalkorValue::None, + 2 => FalkorValue::String(val.into_string()?), + 3 => FalkorValue::I64(val.to_i64().ok_or(FalkorDBError::ParsingI64)?), + 4 => FalkorValue::Bool(val.to_bool().ok_or(FalkorDBError::ParsingBool)?), + 5 => FalkorValue::F64(val.try_into()?), + 6 => FalkorValue::Array({ + let val_vec = val.into_vec()?; + + let mut out_vec = Vec::with_capacity(val_vec.len()); + for item in val_vec { + let (type_marker, val) = type_val_from_value(item)?; + out_vec.push(parse_type(type_marker, val, graph_schema)?) + } + + out_vec + }), + // The following types are sent as an array and require specific parsing functions + 7 => FalkorValue::Edge(FalkorParsable::from_falkor_value(val, graph_schema)?), + 8 => FalkorValue::Node(FalkorParsable::from_falkor_value(val, graph_schema)?), + 9 => FalkorValue::Path(FalkorParsable::from_falkor_value(val, graph_schema)?), + 10 => FalkorValue::Map(FalkorParsable::from_falkor_value(val, graph_schema)?), + 11 => FalkorValue::Point(Point::parse(val)?), + _ => Err(FalkorDBError::ParsingUnknownType)?, + }; + + Ok(res) +} + +pub(crate) fn parse_vec>( + value: FalkorValue +) -> Result, FalkorDBError> { + Ok(value + .into_vec()? + .into_iter() + .flat_map(TryFrom::try_from) + .collect()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::graph_schema::tests::open_readonly_graph_with_modified_schema; + + #[test] + fn test_parse_edge() { + let mut graph = open_readonly_graph_with_modified_schema(); + + let res = parse_type( + 7, + FalkorValue::Array(vec![ + FalkorValue::I64(100), // edge id + FalkorValue::I64(0), // edge type + FalkorValue::I64(51), // src node + FalkorValue::I64(52), // dst node + FalkorValue::Array(vec![ + FalkorValue::Array(vec![ + FalkorValue::I64(0), + FalkorValue::I64(3), + FalkorValue::I64(20), + ]), + FalkorValue::Array(vec![ + FalkorValue::I64(1), + FalkorValue::I64(4), + FalkorValue::Bool(false), + ]), + ]), + ]), + &mut graph.graph_schema, + ); + assert!(res.is_ok()); + + let falkor_edge = res.unwrap(); + + let FalkorValue::Edge(edge) = falkor_edge else { + panic!("Was not of type edge") + }; + assert_eq!(edge.entity_id, 100); + assert_eq!(edge.relationship_type, "very".to_string()); + assert_eq!(edge.src_node_id, 51); + assert_eq!(edge.dst_node_id, 52); + + assert_eq!(edge.properties.len(), 2); + assert_eq!(edge.properties.get("age"), Some(&FalkorValue::I64(20))); + assert_eq!( + edge.properties.get("is_boring"), + Some(&FalkorValue::Bool(false)) + ); + } + + #[test] + fn test_parse_node() { + let mut graph = open_readonly_graph_with_modified_schema(); + + let res = parse_type( + 8, + FalkorValue::Array(vec![ + FalkorValue::I64(51), // node id + FalkorValue::Array(vec![FalkorValue::I64(0), FalkorValue::I64(1)]), // node type + FalkorValue::Array(vec![ + FalkorValue::Array(vec![ + FalkorValue::I64(0), + FalkorValue::I64(3), + FalkorValue::I64(15), + ]), + FalkorValue::Array(vec![ + FalkorValue::I64(2), + FalkorValue::I64(2), + FalkorValue::String("the something".to_string()), + ]), + FalkorValue::Array(vec![ + FalkorValue::I64(3), + FalkorValue::I64(5), + FalkorValue::F64(105.5), + ]), + ]), + ]), + &mut graph.graph_schema, + ); + assert!(res.is_ok()); + + let falkor_node = res.unwrap(); + let FalkorValue::Node(node) = falkor_node else { + panic!("Was not of type node") + }; + + assert_eq!(node.entity_id, 51); + assert_eq!(node.labels, vec!["much".to_string(), "actor".to_string()]); + assert_eq!(node.properties.len(), 3); + assert_eq!(node.properties.get("age"), Some(&FalkorValue::I64(15))); + assert_eq!( + node.properties.get("something_else"), + Some(&FalkorValue::String("the something".to_string())) + ); + assert_eq!( + node.properties.get("secs_since_login"), + Some(&FalkorValue::F64(105.5)) + ); + } + + #[test] + fn test_parse_path() { + let mut graph = open_readonly_graph_with_modified_schema(); + + let res = parse_type( + 9, + FalkorValue::Array(vec![ + FalkorValue::Array(vec![ + FalkorValue::Array(vec![ + FalkorValue::I64(51), + FalkorValue::Array(vec![FalkorValue::I64(0)]), + FalkorValue::Array(vec![]), + ]), + FalkorValue::Array(vec![ + FalkorValue::I64(52), + FalkorValue::Array(vec![FalkorValue::I64(0)]), + FalkorValue::Array(vec![]), + ]), + FalkorValue::Array(vec![ + FalkorValue::I64(53), + FalkorValue::Array(vec![FalkorValue::I64(0)]), + FalkorValue::Array(vec![]), + ]), + ]), + FalkorValue::Array(vec![ + FalkorValue::Array(vec![ + FalkorValue::I64(100), + FalkorValue::I64(0), + FalkorValue::I64(51), + FalkorValue::I64(52), + FalkorValue::Array(vec![]), + ]), + FalkorValue::Array(vec![ + FalkorValue::I64(101), + FalkorValue::I64(1), + FalkorValue::I64(52), + FalkorValue::I64(53), + FalkorValue::Array(vec![]), + ]), + ]), + ]), + &mut graph.graph_schema, + ); + assert!(res.is_ok()); + + let falkor_path = res.unwrap(); + let FalkorValue::Path(path) = falkor_path else { + panic!("Is not of type path") + }; + + assert_eq!(path.nodes.len(), 3); + assert_eq!(path.nodes[0].entity_id, 51); + assert_eq!(path.nodes[1].entity_id, 52); + assert_eq!(path.nodes[2].entity_id, 53); + + assert_eq!(path.relationships.len(), 2); + assert_eq!(path.relationships[0].entity_id, 100); + assert_eq!(path.relationships[1].entity_id, 101); + + assert_eq!(path.relationships[0].src_node_id, 51); + assert_eq!(path.relationships[0].dst_node_id, 52); + + assert_eq!(path.relationships[1].src_node_id, 52); + assert_eq!(path.relationships[1].dst_node_id, 53); + } + + #[test] + fn test_parse_map() { + let mut graph = open_readonly_graph_with_modified_schema(); + + let res = parse_type( + 10, + FalkorValue::Array(vec![ + FalkorValue::String("key0".to_string()), + FalkorValue::Array(vec![ + FalkorValue::I64(2), + FalkorValue::String("val0".to_string()), + ]), + FalkorValue::String("key1".to_string()), + FalkorValue::Array(vec![FalkorValue::I64(3), FalkorValue::I64(1)]), + FalkorValue::String("key2".to_string()), + FalkorValue::Array(vec![FalkorValue::I64(4), FalkorValue::Bool(true)]), + ]), + &mut graph.graph_schema, + ); + assert!(res.is_ok()); + + let falkor_map = res.unwrap(); + let FalkorValue::Map(map) = falkor_map else { + panic!("Is not of type map") + }; + + assert_eq!(map.len(), 3); + assert_eq!( + map.get("key0"), + Some(&FalkorValue::String("val0".to_string())) + ); + assert_eq!(map.get("key1"), Some(&FalkorValue::I64(1))); + assert_eq!(map.get("key2"), Some(&FalkorValue::Bool(true))); + } + + #[test] + fn test_parse_point() { + let mut graph = open_readonly_graph_with_modified_schema(); + + let res = parse_type( + 11, + FalkorValue::Array(vec![FalkorValue::F64(102.0), FalkorValue::F64(15.2)]), + &mut graph.graph_schema, + ); + assert!(res.is_ok()); + + let falkor_point = res.unwrap(); + let FalkorValue::Point(point) = falkor_point else { + panic!("Is not of type point") + }; + assert_eq!(point.latitude, 102.0); + assert_eq!(point.longitude, 15.2); + } +}