Compare commits
191 commits
generic-au
...
master
Author | SHA1 | Date | |
---|---|---|---|
201ec39dd2 | |||
216f078d44 | |||
8ab11d3503 | |||
92eab3692f | |||
02ebaf5bae | |||
517b781da1 | |||
6b269a660d | |||
92f400a553 | |||
ff527ad220 | |||
73240913cc | |||
|
c10575e04d | ||
|
5fdb9a4970 | ||
|
8f226ca050 | ||
|
aa6b634e64 | ||
986b439fb9 | |||
|
0d55e6acba | ||
d39f8aad4e | |||
7cfff34602 | |||
|
8175b98a7c | ||
|
b19a994c68 | ||
|
9a4ba44060 | ||
|
124bf54385 | ||
|
3df00eb63a | ||
|
8041c704dc | ||
|
4aa70567b8 | ||
|
c9a46327bc | ||
|
d22bf451a4 | ||
|
e73effb7d2 | ||
|
eae76e6f43 | ||
|
bd818ba1f1 | ||
|
73feb622cd | ||
|
d3821432bc | ||
|
179c123061 | ||
|
1500d4348c | ||
|
479fbc4572 | ||
|
97628a251f | ||
|
903ac24589 | ||
|
1f6620bf87 | ||
|
e64e2cf2f2 | ||
|
08694dc45a | ||
|
64bb6284d0 | ||
|
5f376a8664 | ||
|
ad3d7157a5 | ||
|
46ddac347e | ||
|
fba46fb1f2 | ||
|
071a916981 | ||
|
d4736a83e4 | ||
|
15df38fc8f | ||
|
614ce63e2b | ||
|
5a7caf7488 | ||
|
a02570fd5e | ||
|
4ec3c3cddc | ||
|
8e8e7b9739 | ||
|
abcdad5976 | ||
|
9d4f328af1 | ||
|
f918dcd915 | ||
|
7185d5ee13 | ||
|
c8c7dda27a | ||
|
2f299fde6c | ||
|
3a62dbc122 | ||
|
0d79eeed8f | ||
|
ca9d8c0096 | ||
|
ffaba698e0 | ||
|
8446a1bc4a | ||
|
722f7fb782 | ||
|
75b5a66a91 | ||
|
ac532be358 | ||
|
44036160d0 | ||
|
c8c1363b14 | ||
|
32ce6d2129 | ||
|
380945a655 | ||
|
af9bb1fcfe | ||
|
c295d82872 | ||
|
c2e39eeb5c | ||
|
985fd0bbdf | ||
|
c02c06ce2b | ||
|
b138c7e257 | ||
|
88df3c4d8d | ||
|
feb9665299 | ||
|
19cb2870da | ||
|
5c2a7261a1 | ||
|
0f015b2f10 | ||
|
da89cfc797 | ||
|
9a6ae48d8e | ||
|
d97e882a4a | ||
5a65cd38c9 | |||
5e700133f5 | |||
4a63ca98df | |||
2e28e87af8 | |||
5998ef73fb | |||
9bcd0955ba | |||
39219af1ff | |||
6d88abfb44 | |||
|
31ec02afd3 | ||
|
1622978acf | ||
|
26cd399e3a | ||
949386a43f | |||
5b0cb80b32 | |||
8039183097 | |||
9c472eff41 | |||
324929a02a | |||
|
526c9c3928 | ||
4b1583473e | |||
125306e108 | |||
0e63167aab | |||
5899bf8624 | |||
bf1ac14d32 | |||
b428579865 | |||
9eb6bc6220 | |||
41f1ff2393 | |||
5979fa5844 | |||
dfb5866174 | |||
58e06742fd | |||
a8223d11c9 | |||
b41bcf40f3 | |||
18a0c15320 | |||
6b7343a893 | |||
|
4f81c4e15b | ||
8aec4048b9 | |||
|
ef1c196516 | ||
|
fcb3a9182f | ||
|
9421a5c3bb | ||
|
60c65033b2 | ||
|
fa3091d078 | ||
286155a18a | |||
|
d6b3d1818a | ||
|
b2f92b8461 | ||
|
c2122a210c | ||
|
bcb56b0561 | ||
|
be02b86bbe | ||
|
e6a2af3553 | ||
|
2b97a90b64 | ||
|
ae9f969e59 | ||
|
4d92e3eef9 | ||
|
3547cfcd16 | ||
|
4e9d6b3f58 | ||
|
76ae972e2e | ||
|
f4002f34f4 | ||
|
5596873e8f | ||
|
cdfd1f6b30 | ||
|
ff48f6369a | ||
|
95780c0ab5 | ||
|
e3bd1cb77e | ||
|
a25f476734 | ||
|
17112dbc4d | ||
|
b6b316953e | ||
|
3d965fdf6e | ||
|
e55376e98b | ||
|
b22b29679b | ||
|
64821079c2 | ||
|
bcec011ca5 | ||
|
fba9568d44 | ||
|
e3113783ae | ||
|
cd99774e2c | ||
|
66cc4e14ad | ||
|
22b17a6779 | ||
|
6160dd59f7 | ||
|
b8b2aef181 | ||
|
5bc6d76bb3 | ||
|
8c2245ff0b | ||
|
0b84a0802d | ||
|
8ddbd18f5f | ||
|
9e8292e68b | ||
|
fe1a034822 | ||
|
d61e070e08 | ||
|
db4350aab7 | ||
|
5976869b0a | ||
|
bf8c2c1516 | ||
|
dbb4aab071 | ||
|
1e488d720b | ||
|
e9398dd50d | ||
|
c00e14f5ff | ||
|
c8e6a511f4 | ||
|
9381af4289 | ||
|
68d0e17824 | ||
|
05ef149545 | ||
|
6312e89aba | ||
|
ccdd17551a | ||
|
9fd198f96a | ||
|
2ae8594d20 | ||
|
07beb68c7a | ||
|
11aaa193f5 | ||
|
cebb85a7fe | ||
|
d0214fad7d | ||
|
a57ba5d79f | ||
|
f8478835a2 | ||
|
79113498e7 | ||
|
b5d0e12a24 | ||
|
814ab19ecb | ||
|
0cb8227750 | ||
|
f747e2e550 |
190 changed files with 25411 additions and 15398 deletions
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
liberapay: svartalf
|
||||||
|
patreon: svartalf
|
||||||
|
custom: ["https://svartalf.info/donate/", "https://www.buymeacoffee.com/svartalf"]
|
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
|
# package ecosystems to update and where the package manifests are located.
|
||||||
|
# Please see the documentation for all configuration options:
|
||||||
|
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "cargo" # See documentation for possible values
|
||||||
|
directory: "/" # Location of package manifests
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
20
.github/workflows/audit.yml
vendored
Normal file
20
.github/workflows/audit.yml
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
name: Security audit
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 1 * *'
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- '**/Cargo.toml'
|
||||||
|
- '**/Cargo.lock'
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
audit:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions-rs/audit-check@issue-104
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
13
.github/workflows/clippy-ng.yml
vendored
Normal file
13
.github/workflows/clippy-ng.yml
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
on: [push, pull_request]
|
||||||
|
name: Clippy (new version test, don't use it!)
|
||||||
|
jobs:
|
||||||
|
clippy_check_ng:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: nightly
|
||||||
|
components: clippy
|
||||||
|
override: true
|
||||||
|
- uses: actions-rs/clippy@master
|
16
.github/workflows/clippy_check.yml
vendored
Normal file
16
.github/workflows/clippy_check.yml
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
on: [push, pull_request]
|
||||||
|
name: Clippy check
|
||||||
|
jobs:
|
||||||
|
clippy_check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: nightly
|
||||||
|
components: clippy
|
||||||
|
override: true
|
||||||
|
- uses: actions-rs/clippy-check@v1
|
||||||
|
with:
|
||||||
|
args: --all-targets --all-features -- -D warnings
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
28
.github/workflows/cross_compile.yml
vendored
Normal file
28
.github/workflows/cross_compile.yml
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# We could use `@actions-rs/cargo` Action ability to automatically install `cross` tool
|
||||||
|
# in order to compile our application for some unusual targets.
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
name: Cross-compile
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
target:
|
||||||
|
- armv7-unknown-linux-gnueabihf
|
||||||
|
- powerpc64-unknown-linux-gnu
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
target: ${{ matrix.target }}
|
||||||
|
override: true
|
||||||
|
- uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
use-cross: true
|
||||||
|
command: build
|
||||||
|
args: --release --target=${{ matrix.target }}
|
66
.github/workflows/grcov.yml
vendored
Normal file
66
.github/workflows/grcov.yml
vendored
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
name: Code coverage with grcov
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
grcov:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- ubuntu-latest
|
||||||
|
- macOS-latest
|
||||||
|
# - windows-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: nightly
|
||||||
|
override: true
|
||||||
|
profile: minimal
|
||||||
|
|
||||||
|
- name: Execute tests
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: test
|
||||||
|
args: --all
|
||||||
|
env:
|
||||||
|
CARGO_INCREMENTAL: 0
|
||||||
|
RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests"
|
||||||
|
|
||||||
|
# Note that `actions-rs/grcov` Action can install `grcov` too,
|
||||||
|
# but can't use faster installation methods yet.
|
||||||
|
# As a temporary experiment `actions-rs/install` Action plugged in here.
|
||||||
|
# Consider **NOT** to copy that into your workflow,
|
||||||
|
# but use `actions-rs/grcov` only
|
||||||
|
- name: Pre-installing grcov
|
||||||
|
uses: actions-rs/install@v0.1
|
||||||
|
with:
|
||||||
|
crate: grcov
|
||||||
|
use-tool-cache: true
|
||||||
|
|
||||||
|
- name: Gather coverage data
|
||||||
|
id: coverage
|
||||||
|
uses: actions-rs/grcov@v0.1
|
||||||
|
with:
|
||||||
|
coveralls-token: ${{ secrets.COVERALLS_TOKEN }}
|
||||||
|
|
||||||
|
- name: Coveralls upload
|
||||||
|
uses: coverallsapp/github-action@master
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
parallel: true
|
||||||
|
path-to-lcov: ${{ steps.coverage.outputs.report }}
|
||||||
|
|
||||||
|
grcov_finalize:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: grcov
|
||||||
|
steps:
|
||||||
|
- name: Coveralls finalization
|
||||||
|
uses: coverallsapp/github-action@master
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
parallel-finished: true
|
110
.github/workflows/msrv.yml
vendored
Normal file
110
.github/workflows/msrv.yml
vendored
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
# Based on https://github.com/actions-rs/meta/blob/master/recipes/msrv.md
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
name: MSRV
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
name: Check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
rust:
|
||||||
|
- stable
|
||||||
|
- 1.31.0
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: ${{ matrix.rust }}
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Run cargo check
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
continue-on-error: true # WARNING: only for this example, remove it!
|
||||||
|
with:
|
||||||
|
command: check
|
||||||
|
|
||||||
|
test:
|
||||||
|
name: Test Suite
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
rust:
|
||||||
|
- stable
|
||||||
|
- 1.31.0
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: ${{ matrix.rust }}
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Run cargo test
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
continue-on-error: true # WARNING: only for this example, remove it!
|
||||||
|
with:
|
||||||
|
command: test
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
name: Rustfmt
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
rust:
|
||||||
|
- stable
|
||||||
|
- 1.31.0
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: ${{ matrix.rust }}
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Install rustfmt
|
||||||
|
run: rustup component add rustfmt
|
||||||
|
|
||||||
|
- name: Run cargo fmt
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
continue-on-error: true # WARNING: only for this example, remove it!
|
||||||
|
with:
|
||||||
|
command: fmt
|
||||||
|
args: --all -- --check
|
||||||
|
|
||||||
|
clippy:
|
||||||
|
name: Clippy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
rust:
|
||||||
|
- stable
|
||||||
|
- 1.31.0
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: ${{ matrix.rust }}
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Install clippy
|
||||||
|
run: rustup component add clippy
|
||||||
|
|
||||||
|
- name: Run cargo clippy
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
continue-on-error: true # WARNING: only for this example, remove it!
|
||||||
|
with:
|
||||||
|
command: clippy
|
||||||
|
args: -- -D warnings
|
78
.github/workflows/nightly_lints.yml
vendored
Normal file
78
.github/workflows/nightly_lints.yml
vendored
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
name: Nightly lints
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
clippy:
|
||||||
|
name: Clippy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install nightly toolchain with clippy available
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: nightly
|
||||||
|
override: true
|
||||||
|
components: clippy
|
||||||
|
|
||||||
|
- name: Run cargo clippy
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
continue-on-error: true # WARNING: only for this example, remove it!
|
||||||
|
with:
|
||||||
|
command: clippy
|
||||||
|
args: -- -D warnings
|
||||||
|
|
||||||
|
rustfmt:
|
||||||
|
name: Format
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install nightly toolchain with rustfmt available
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: nightly
|
||||||
|
override: true
|
||||||
|
components: rustfmt
|
||||||
|
|
||||||
|
- name: Run cargo fmt
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
continue-on-error: true # WARNING: only for this example, remove it!
|
||||||
|
with:
|
||||||
|
command: fmt
|
||||||
|
args: --all -- --check
|
||||||
|
|
||||||
|
combo:
|
||||||
|
name: Clippy + rustfmt
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install nightly toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: nightly
|
||||||
|
override: true
|
||||||
|
components: rustfmt, clippy
|
||||||
|
|
||||||
|
- name: Run cargo fmt
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
continue-on-error: true # WARNING: only for this example, remove it!
|
||||||
|
with:
|
||||||
|
command: fmt
|
||||||
|
args: --all -- --check
|
||||||
|
|
||||||
|
- name: Run cargo clippy
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
continue-on-error: true # WARNING: only for this example, remove it!
|
||||||
|
with:
|
||||||
|
command: clippy
|
||||||
|
args: -- -D warnings
|
||||||
|
|
79
.github/workflows/quickstart.yml
vendored
Normal file
79
.github/workflows/quickstart.yml
vendored
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
# Based on https://github.com/actions-rs/meta/blob/master/recipes/quickstart.md
|
||||||
|
#
|
||||||
|
# While our "example" application has the platform-specific code,
|
||||||
|
# for simplicity we are compiling and testing everything on the Ubuntu environment only.
|
||||||
|
# For multi-OS testing see the `cross.yml` workflow.
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
name: Quickstart
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
name: Check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install stable toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Run cargo check
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
continue-on-error: true # WARNING: only for this example, remove it!
|
||||||
|
with:
|
||||||
|
command: check
|
||||||
|
|
||||||
|
test:
|
||||||
|
name: Test Suite
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install stable toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Run cargo test
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
continue-on-error: true # WARNING: only for this example, remove it!
|
||||||
|
with:
|
||||||
|
command: test
|
||||||
|
|
||||||
|
lints:
|
||||||
|
name: Lints
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install stable toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
components: rustfmt, clippy
|
||||||
|
|
||||||
|
- name: Run cargo fmt
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
continue-on-error: true # WARNING: only for this example, remove it!
|
||||||
|
with:
|
||||||
|
command: fmt
|
||||||
|
args: --all -- --check
|
||||||
|
|
||||||
|
- name: Run cargo clippy
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
continue-on-error: true # WARNING: only for this example, remove it!
|
||||||
|
with:
|
||||||
|
command: clippy
|
||||||
|
args: -- -D warnings
|
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -3,7 +3,7 @@
|
||||||
*.jar
|
*.jar
|
||||||
*jar
|
*jar
|
||||||
*~
|
*~
|
||||||
*.rs.bk
|
**/*.rs.bk
|
||||||
.s*
|
.s*
|
||||||
.*.sw*
|
.*.sw*
|
||||||
*.rs.bak
|
*.rs.bak
|
||||||
|
@ -15,6 +15,8 @@
|
||||||
.lein-plugins/
|
.lein-plugins/
|
||||||
.lein-repl-history
|
.lein-repl-history
|
||||||
.nrepl-port
|
.nrepl-port
|
||||||
|
.bundle/
|
||||||
|
docs/vendor/
|
||||||
/.lein-*
|
/.lein-*
|
||||||
/.nrepl-port
|
/.nrepl-port
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
@ -22,7 +24,7 @@ Cargo.lock
|
||||||
/classes/
|
/classes/
|
||||||
/node_modules/
|
/node_modules/
|
||||||
/out/
|
/out/
|
||||||
target/
|
/target
|
||||||
pom.xml
|
pom.xml
|
||||||
pom.xml.asc
|
pom.xml.asc
|
||||||
/.cljs_node_repl/
|
/.cljs_node_repl/
|
||||||
|
|
1
.ignore
Normal file
1
.ignore
Normal file
|
@ -0,0 +1 @@
|
||||||
|
docs/
|
|
@ -25,7 +25,7 @@ tasks:
|
||||||
payload:
|
payload:
|
||||||
maxRunTime: 3600
|
maxRunTime: 3600
|
||||||
deadline: "{{ '2 hours' | $fromNow }}"
|
deadline: "{{ '2 hours' | $fromNow }}"
|
||||||
image: 'mozillamobile/mentat:1.1'
|
image: 'mozillamobile/android-components:1.4'
|
||||||
command:
|
command:
|
||||||
- /bin/bash
|
- /bin/bash
|
||||||
- '--login'
|
- '--login'
|
||||||
|
@ -57,7 +57,7 @@ tasks:
|
||||||
payload:
|
payload:
|
||||||
maxRunTime: 3600
|
maxRunTime: 3600
|
||||||
deadline: "{{ '2 hours' | $fromNow }}"
|
deadline: "{{ '2 hours' | $fromNow }}"
|
||||||
image: 'mozillamobile/mentat:1.1'
|
image: 'mozillamobile/mentat:1.2'
|
||||||
command:
|
command:
|
||||||
- /bin/bash
|
- /bin/bash
|
||||||
- '--login'
|
- '--login'
|
||||||
|
|
71
.travis.yml
71
.travis.yml
|
@ -1,11 +1,53 @@
|
||||||
language: rust
|
language: rust
|
||||||
|
env:
|
||||||
|
- CARGO_INCREMENTAL=0
|
||||||
|
# https://bheisler.github.io/post/efficient-use-of-travis-ci-cache-for-rust/
|
||||||
|
before_cache:
|
||||||
|
# Delete loose files in the debug directory
|
||||||
|
- find ./target/debug -maxdepth 1 -type f -delete
|
||||||
|
# Delete the test and benchmark executables. Finding these all might take some
|
||||||
|
# experimentation.
|
||||||
|
- rm -rf ./target/debug/deps/criterion*
|
||||||
|
- rm -rf ./target/debug/deps/bench*
|
||||||
|
# Delete the associated metadata files for those executables
|
||||||
|
- rm -rf ./target/debug/.fingerprint/criterion*
|
||||||
|
- rm -rf ./target/debug/.fingerprint/bench*
|
||||||
|
# Note that all of the above need to be repeated for `release/` instead of
|
||||||
|
# `debug/` if your build script builds artifacts in release mode.
|
||||||
|
# This is just more metadata
|
||||||
|
- rm -f ./target/.rustc_info.json
|
||||||
|
# Also delete the saved benchmark data from the test benchmarks. If you
|
||||||
|
# have Criterion.rs benchmarks, you'll probably want to do this as well, or set
|
||||||
|
# the CRITERION_HOME environment variable to move that data out of the
|
||||||
|
# `target/` directory.
|
||||||
|
- rm -rf ./target/criterion
|
||||||
|
# Also delete cargo's registry index. This is updated on every build, but it's
|
||||||
|
# way cheaper to re-download than the whole cache is.
|
||||||
|
- rm -rf "$TRAVIS_HOME/.cargo/registry/index/"
|
||||||
|
- rm -rf "$TRAVIS_HOME/.cargo/registry/src"
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- ./target
|
||||||
|
- $TRAVIS_HOME/.cache/sccache
|
||||||
|
- $TRAVIS_HOME/.cargo/
|
||||||
|
- $TRAVIS_HOME/.rustup/
|
||||||
|
before_script:
|
||||||
|
- cargo install --force cargo-audit
|
||||||
|
- cargo generate-lockfile
|
||||||
|
- rustup component add clippy-preview
|
||||||
|
script:
|
||||||
|
- cargo audit
|
||||||
# We use OSX so that we can get a reasonably up to date version of SQLCipher.
|
# We use OSX so that we can get a reasonably up to date version of SQLCipher.
|
||||||
# (The version in Travis's default Ubuntu Trusty is much too old).
|
# (The version in Travis's default Ubuntu Trusty is much too old).
|
||||||
os: osx
|
os: osx
|
||||||
before_install:
|
before_install:
|
||||||
- brew install sqlcipher --with-fts
|
- brew install sqlcipher
|
||||||
rust:
|
rust:
|
||||||
- 1.25.0 # Must align with `build/version.rs`.
|
- 1.43.0
|
||||||
|
- 1.44.0
|
||||||
|
- 1.45.0
|
||||||
|
- 1.46.0
|
||||||
|
- 1.47.0
|
||||||
- stable
|
- stable
|
||||||
- beta
|
- beta
|
||||||
- nightly
|
- nightly
|
||||||
|
@ -13,23 +55,24 @@ matrix:
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- rust: nightly
|
- rust: nightly
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
|
jobs:
|
||||||
|
include:
|
||||||
|
- stage: "Test iOS"
|
||||||
|
rust: 1.47.0
|
||||||
|
script: ./scripts/test-ios.sh
|
||||||
|
- stage: "Docs"
|
||||||
|
rust: 1.47.0
|
||||||
|
script: ./scripts/cargo-doc.sh
|
||||||
script:
|
script:
|
||||||
|
- cargo build --verbose --all
|
||||||
|
- cargo clippy --all-targets --all-features -- -D warnings -A clippy::comparison-chain -A clippy::many-single-char-names # Check tests and non-default crate features.
|
||||||
- cargo test --verbose --all
|
- cargo test --verbose --all
|
||||||
- cargo test --features edn/serde_support --verbose --all
|
- cargo test --features edn/serde_support --verbose --all
|
||||||
# We can't pick individual features out with `cargo test --all` (At the time of this writing, this
|
# We can't pick individual features out with `cargo test --all` (At the time of this writing, this
|
||||||
# works but does the wrong thing because of a bug in cargo, but its fix will be to disallow doing
|
# works but does the wrong thing because of a bug in cargo, but its fix will be to disallow doing
|
||||||
# this all-together, see https://github.com/rust-lang/cargo/issues/5364 for more information). To
|
# this all-together, see https://github.com/rust-lang/cargo/issues/5364 for more information). To
|
||||||
# work around this, we run individual tests for each subcrate individually.
|
# work around this, we run tests individually for sub-crates that rely on `rusqlite`.
|
||||||
- |
|
- |
|
||||||
for manifest in $(find . -type f -name Cargo.toml); do
|
for crate in "" "db" "db-traits" "ffi" "public-traits" "query-projector" "query-projector-traits" "query-pull" "sql" "tolstoy" "tolstoy-traits" "transaction" "tools/cli"; do
|
||||||
cargo test --manifest-path $manifest --verbose --no-default-features --features sqlcipher
|
cargo test --manifest-path ./$crate/Cargo.toml --verbose --no-default-features --features sqlcipher
|
||||||
done
|
done
|
||||||
after_success:
|
|
||||||
- |
|
|
||||||
if [[ "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" ]]; then
|
|
||||||
cargo doc &&
|
|
||||||
echo "<meta http-equiv=refresh content=0;url=mentat/index.html>" > target/doc/index.html &&
|
|
||||||
git clone https://github.com/davisp/ghp-import.git &&
|
|
||||||
./ghp-import/ghp_import.py -n -p -f -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc
|
|
||||||
fi
|
|
||||||
cache: cargo
|
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
# 0.11.1 (2018-08-09)
|
||||||
|
|
||||||
|
* sdks/android compiled against:
|
||||||
|
* Kotlin standard library 1.2.41
|
||||||
|
|
||||||
|
* **API changes**: Changed wording of MentatError::ConflictingAttributeDefinitions, MentatError::ExistingVocabularyTooNew, MentatError::UnexpectedCoreSchema.
|
||||||
|
|
||||||
|
* [Commits](https://github.com/mozilla/mentat/compare/v0.11.0...v0.11.1)
|
||||||
|
|
||||||
# 0.11 (2018-07-31)
|
# 0.11 (2018-07-31)
|
||||||
|
|
||||||
* sdks/android compiled against:
|
* sdks/android compiled against:
|
||||||
|
|
72
Cargo.toml
72
Cargo.toml
|
@ -1,4 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
|
edition = "2021"
|
||||||
authors = [
|
authors = [
|
||||||
"Richard Newman <rnewman@twinql.com>",
|
"Richard Newman <rnewman@twinql.com>",
|
||||||
"Nicholas Alexander <nalexander@mozilla.com>",
|
"Nicholas Alexander <nalexander@mozilla.com>",
|
||||||
|
@ -10,39 +11,57 @@ authors = [
|
||||||
"Kit Cambridge <kit@yakshaving.ninja>",
|
"Kit Cambridge <kit@yakshaving.ninja>",
|
||||||
"Edouard Oger <eoger@fastmail.com>",
|
"Edouard Oger <eoger@fastmail.com>",
|
||||||
"Thom Chiovoloni <tchiovoloni@mozilla.com>",
|
"Thom Chiovoloni <tchiovoloni@mozilla.com>",
|
||||||
|
"Gregory Burd <greg@burd.me>",
|
||||||
]
|
]
|
||||||
name = "mentat"
|
name = "mentat"
|
||||||
version = "0.11.0"
|
version = "0.14.0"
|
||||||
build = "build/version.rs"
|
build = "build/version.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["bundled_sqlite3", "syncable"]
|
default = ["bundled_sqlite3", "syncable"]
|
||||||
bundled_sqlite3 = ["rusqlite/bundled"]
|
bundled_sqlite3 = ["rusqlite/bundled"]
|
||||||
sqlcipher = ["rusqlite/sqlcipher", "mentat_db/sqlcipher"]
|
sqlcipher = ["rusqlite/sqlcipher", "mentat_db/sqlcipher"]
|
||||||
syncable = ["mentat_tolstoy", "mentat_db/syncable"]
|
syncable = ["mentat_tolstoy", "tolstoy_traits", "mentat_db/syncable"]
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["tools/cli", "ffi"]
|
members = [
|
||||||
|
"tools/cli",
|
||||||
|
"ffi", "core", "core-traits","db", "db-traits", "edn", "public-traits", "query-algebrizer",
|
||||||
|
"query-algebrizer-traits", "query-projector", "query-projector-traits","query-pull",
|
||||||
|
"query-sql", "sql", "sql-traits", "tolstoy-traits", "tolstoy", "transaction"
|
||||||
|
]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
rustc_version = "0.2"
|
rustc_version = "~0.4"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
assert_approx_eq = "~1.1"
|
||||||
|
|
||||||
|
#[dev-dependencies.cargo-husky]
|
||||||
|
#version = "1"
|
||||||
|
#default-features = false # Disable features which are enabled by default
|
||||||
|
#features = ["run-for-all", "precommit-hook", "run-cargo-fmt", "run-cargo-test", "run-cargo-check", "run-cargo-clippy"]
|
||||||
|
#cargo audit
|
||||||
|
#cargo outdated
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4"
|
chrono = "~0.4"
|
||||||
failure = "0.1.1"
|
failure = "~0.1"
|
||||||
failure_derive = "0.1.1"
|
lazy_static = "~1.4"
|
||||||
lazy_static = "0.2"
|
time = "0.3.1"
|
||||||
time = "0.1"
|
log = "~0.4"
|
||||||
uuid = { version = "0.5", features = ["v4", "serde"] }
|
uuid = { version = "~1", features = ["v4", "serde"] }
|
||||||
|
|
||||||
[dependencies.rusqlite]
|
[dependencies.rusqlite]
|
||||||
version = "0.13"
|
version = "~0.29"
|
||||||
# System sqlite might be very old.
|
features = ["limits", "bundled"]
|
||||||
features = ["limits"]
|
|
||||||
|
|
||||||
[dependencies.edn]
|
[dependencies.edn]
|
||||||
path = "edn"
|
path = "edn"
|
||||||
|
|
||||||
|
[dependencies.core_traits]
|
||||||
|
path = "core-traits"
|
||||||
|
|
||||||
[dependencies.mentat_core]
|
[dependencies.mentat_core]
|
||||||
path = "core"
|
path = "core"
|
||||||
|
|
||||||
|
@ -52,28 +71,47 @@ path = "sql"
|
||||||
[dependencies.mentat_db]
|
[dependencies.mentat_db]
|
||||||
path = "db"
|
path = "db"
|
||||||
|
|
||||||
[dependencies.mentat_query]
|
[dependencies.db_traits]
|
||||||
path = "query"
|
path = "db-traits"
|
||||||
|
|
||||||
[dependencies.mentat_query_algebrizer]
|
[dependencies.mentat_query_algebrizer]
|
||||||
path = "query-algebrizer"
|
path = "query-algebrizer"
|
||||||
|
|
||||||
|
[dependencies.query_algebrizer_traits]
|
||||||
|
path = "query-algebrizer-traits"
|
||||||
|
|
||||||
[dependencies.mentat_query_projector]
|
[dependencies.mentat_query_projector]
|
||||||
path = "query-projector"
|
path = "query-projector"
|
||||||
|
|
||||||
|
[dependencies.query_projector_traits]
|
||||||
|
path = "query-projector-traits"
|
||||||
|
|
||||||
[dependencies.mentat_query_pull]
|
[dependencies.mentat_query_pull]
|
||||||
path = "query-pull"
|
path = "query-pull"
|
||||||
|
|
||||||
|
[dependencies.query_pull_traits]
|
||||||
|
path = "query-pull-traits"
|
||||||
|
|
||||||
[dependencies.mentat_query_sql]
|
[dependencies.mentat_query_sql]
|
||||||
path = "query-sql"
|
path = "query-sql"
|
||||||
|
|
||||||
[dependencies.mentat_query_translator]
|
[dependencies.sql_traits]
|
||||||
path = "query-translator"
|
path = "sql-traits"
|
||||||
|
|
||||||
|
[dependencies.public_traits]
|
||||||
|
path = "public-traits"
|
||||||
|
|
||||||
|
[dependencies.mentat_transaction]
|
||||||
|
path = "transaction"
|
||||||
|
|
||||||
[dependencies.mentat_tolstoy]
|
[dependencies.mentat_tolstoy]
|
||||||
path = "tolstoy"
|
path = "tolstoy"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.tolstoy_traits]
|
||||||
|
path = "tolstoy-traits"
|
||||||
|
optional = true
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
debug = false
|
debug = false
|
||||||
|
|
11
Makefile
Normal file
11
Makefile
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
.PHONY: outdated fix
|
||||||
|
|
||||||
|
outdated:
|
||||||
|
for p in $(dirname $(ls Cargo.toml */Cargo.toml */*/Cargo.toml)); do echo $p; (cd $p; cargo outdated -R); done
|
||||||
|
|
||||||
|
|
||||||
|
fix:
|
||||||
|
$(for p in $(dirname $(ls Cargo.toml */Cargo.toml */*/Cargo.toml)); do echo $p; (cd $p; cargo fix --allow-dirty --broken-code --edition-idioms); done)
|
||||||
|
|
||||||
|
upgrades:
|
||||||
|
cargo upgrades
|
29
NOTES
Normal file
29
NOTES
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
* sqlite -> monetdb-lite-c + fts5 + bayesdb
|
||||||
|
* fts5 + regex + tre/fuzzy + codesearch/trigram filters, streaming bloom filters https://arxiv.org/abs/2001.03147
|
||||||
|
* datalog to "goblin relational engine" (gtk)
|
||||||
|
* branching distributed wal (chain replication) and CRDTs
|
||||||
|
* alf:fn query language
|
||||||
|
* datatypes via bit syntax+some code?
|
||||||
|
* pure lang?
|
||||||
|
|
||||||
|
* https://github.com/dahjelle/pouch-datalog
|
||||||
|
* https://github.com/edn-query-language/eql
|
||||||
|
* https://github.com/borkdude/jet
|
||||||
|
* https://github.com/walmartlabs/dyn-edn
|
||||||
|
* https://github.com/go-edn/edn
|
||||||
|
* https://github.com/smothers/cause
|
||||||
|
* https://github.com/oscaro/eq
|
||||||
|
* https://github.com/clojure-emacs/parseedn
|
||||||
|
* https://github.com/exoscale/seql
|
||||||
|
* https://github.com/axboe/liburing
|
||||||
|
|
||||||
|
* (EAVtf) - entity attribute value type flags
|
||||||
|
|
||||||
|
* distributed, replicated WAL
|
||||||
|
* https://github.com/mirage/irmin
|
||||||
|
|
||||||
|
* What if facts had "confidence" [0-1)?
|
||||||
|
* entity attribute value type flags
|
||||||
|
* https://github.com/probcomp/BayesDB
|
||||||
|
* https://github.com/probcomp/bayeslite
|
||||||
|
* http://probcomp.csail.mit.edu/software/bayesdb/
|
23
README.md
23
README.md
|
@ -1,20 +1,19 @@
|
||||||
# Project Mentat
|
# Project Mentat
|
||||||
|
[![Build Status](https://travis-ci.org/qpdb/mentat.svg?branch=master)](https://travis-ci.org/qpdb/mentat)
|
||||||
|
|
||||||
Project Mentat is a persistent, embedded knowledge base. It draws heavily on [DataScript](https://github.com/tonsky/datascript) and [Datomic](http://datomic.com).
|
Project Mentat is a persistent, embedded knowledge base. It draws heavily on [DataScript](https://github.com/tonsky/datascript) and [Datomic](http://datomic.com).
|
||||||
|
|
||||||
Mentat is implemented in Rust.
|
This project was started by Mozilla, but [is no longer being developed or actively maintained by them](https://mail.mozilla.org/pipermail/firefox-dev/2018-September/006780.html). [Their repository](https://github.com/mozilla/mentat) was marked read-only, [this fork](https://github.com/qpdb/mentat) is an attempt to revive and continue that interesting work. We owe the team at Mozilla more than words can express for inspiring us all and for this project in particular.
|
||||||
|
|
||||||
The first version of Project Mentat, named Datomish, [was written in ClojureScript](https://github.com/mozilla/mentat/tree/clojure), targeting both Node (on top of `promise_sqlite`) and Firefox (on top of `Sqlite.jsm`). It also worked in pure Clojure on the JVM on top of `jdbc-sqlite`. The name was changed to avoid confusion with [Datomic](http://datomic.com).
|
*Thank you*.
|
||||||
|
|
||||||
The Rust implementation gives us a smaller compiled output, better performance, more type safety, better tooling, and easier deployment into Firefox and mobile platforms.
|
[Documentation](https://docs.rs/mentat)
|
||||||
|
|
||||||
[Documentation](https://mozilla.github.io/mentat)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Motivation
|
## Motivation
|
||||||
|
|
||||||
Mentat is intended to be a flexible relational (not key-value, not document-oriented) store that makes it easy to describe, grow, and reuse your domain schema.
|
Mentat is a flexible relational (not key-value, not document-oriented) store that makes it easy to describe, grow, and reuse your domain schema.
|
||||||
|
|
||||||
By abstracting away the storage schema, and by exposing change listeners outside the database (not via triggers), we hope to make domain schemas stable, and allow both the data store itself and embedding applications to use better architectures, meeting performance goals in a way that allows future evolution.
|
By abstracting away the storage schema, and by exposing change listeners outside the database (not via triggers), we hope to make domain schemas stable, and allow both the data store itself and embedding applications to use better architectures, meeting performance goals in a way that allows future evolution.
|
||||||
|
|
||||||
|
@ -74,9 +73,11 @@ We've observed that data storage is a particular area of difficulty for software
|
||||||
|
|
||||||
DataScript asks the question: "What if creating a database were as cheap as creating a Hashmap?"
|
DataScript asks the question: "What if creating a database were as cheap as creating a Hashmap?"
|
||||||
|
|
||||||
Mentat is not interested in that. Instead, it's strongly interested in persistence and performance, with very little interest in immutable databases/databases as values or throwaway use.
|
Mentat is not interested in that. Instead, it's focused on persistence and performance, with very little interest in immutable databases/databases as values or throwaway use.
|
||||||
|
|
||||||
One might say that Mentat's question is: "What if an SQLite database could store arbitrary relations, for arbitrary consumers, without them having to coordinate an up-front storage-level schema?"
|
One might say that Mentat's question is: "What if a database could store arbitrary relations, for arbitrary consumers, without them having to coordinate an up-front storage-level schema?"
|
||||||
|
|
||||||
|
Consider this a practical approach to facts, to knowledge its storage and access, much like SQLite is a practical RDBMS.
|
||||||
|
|
||||||
(Note that [domain-level schemas are very valuable](http://martinfowler.com/articles/schemaless/).)
|
(Note that [domain-level schemas are very valuable](http://martinfowler.com/articles/schemaless/).)
|
||||||
|
|
||||||
|
@ -86,7 +87,7 @@ Some thought has been given to how databases as values — long-term references
|
||||||
|
|
||||||
Just like DataScript, Mentat speaks Datalog for querying and takes additions and retractions as input to a transaction.
|
Just like DataScript, Mentat speaks Datalog for querying and takes additions and retractions as input to a transaction.
|
||||||
|
|
||||||
Unlike DataScript, Mentat exposes free-text indexing, thanks to SQLite.
|
Unlike DataScript, Mentat exposes free-text indexing, thanks to SQLite/FTS.
|
||||||
|
|
||||||
|
|
||||||
## Comparison to Datomic
|
## Comparison to Datomic
|
||||||
|
@ -95,8 +96,6 @@ Datomic is a server-side, enterprise-grade data storage system. Datomic has a be
|
||||||
|
|
||||||
Many of these design decisions are inapplicable to deployed desktop software; indeed, the use of multiple JVM processes makes Datomic's use in a small desktop app, or a mobile device, prohibitive.
|
Many of these design decisions are inapplicable to deployed desktop software; indeed, the use of multiple JVM processes makes Datomic's use in a small desktop app, or a mobile device, prohibitive.
|
||||||
|
|
||||||
Mentat was designed for embedding, initially in an experimental Electron app ([Tofino](https://github.com/mozilla/tofino)). It is less concerned with exposing consistent database states outside transaction boundaries, because that's less important here, and dropping some of these requirements allows us to leverage SQLite itself.
|
|
||||||
|
|
||||||
|
|
||||||
## Comparison to SQLite
|
## Comparison to SQLite
|
||||||
|
|
||||||
|
@ -209,7 +208,7 @@ The top-level main crate of Mentat assembles these component crates into somethi
|
||||||
|
|
||||||
### Syncing
|
### Syncing
|
||||||
|
|
||||||
Sync code lives, for [referential reasons](https://engineering.linkedin.com/distributed-systems/log-what-every-software-engineer-should-know-about-real-time-datas-unifying), in a crate named `tolstoy`. This code is a work in progress.
|
Sync code lives, for [referential reasons](https://engineering.linkedin.com/distributed-systems/log-what-every-software-engineer-should-know-about-real-time-datas-unifying), in a crate named `tolstoy`. This code is a work in progress; current state is a proof-of-concept implementation which largely relies on the internal transactor to make progress in most cases and comes with a basic support for timelines. See [Tolstoy's documentation](https://github.com/mozilla/mentat/tree/master/tolstoy/README.md) for details.
|
||||||
|
|
||||||
### The command-line interface
|
### The command-line interface
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@ def create_task(name, description, command):
|
||||||
"payload": {
|
"payload": {
|
||||||
"features": {},
|
"features": {},
|
||||||
"maxRunTime": 7200,
|
"maxRunTime": 7200,
|
||||||
"image": "mozillamobile/mentat:1.1",
|
"image": "mozillamobile/mentat:1.2",
|
||||||
"command": [
|
"command": [
|
||||||
"/bin/bash",
|
"/bin/bash",
|
||||||
"--login",
|
"--login",
|
||||||
|
|
|
@ -8,24 +8,25 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
extern crate rustc_version;
|
use rustc_version::{version, Version};
|
||||||
|
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use rustc_version::{
|
|
||||||
Version,
|
|
||||||
version,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// MIN_VERSION should be changed when there's a new minimum version of rustc required
|
/// MIN_VERSION should be changed when there's a new minimum version of rustc required
|
||||||
/// to build the project.
|
/// to build the project.
|
||||||
static MIN_VERSION: &'static str = "1.25.0";
|
static MIN_VERSION: &str = "1.69.0";
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let ver = version().unwrap();
|
let ver = version().unwrap();
|
||||||
let min = Version::parse(MIN_VERSION).unwrap();
|
let min = Version::parse(MIN_VERSION).unwrap();
|
||||||
if ver < min {
|
if ver < min {
|
||||||
writeln!(&mut io::stderr(), "Mentat requires rustc {} or higher.", MIN_VERSION).unwrap();
|
writeln!(
|
||||||
|
&mut io::stderr(),
|
||||||
|
"Mentat requires rustc {} or higher, you were using version {}.",
|
||||||
|
MIN_VERSION,
|
||||||
|
ver
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
23
core-traits/Cargo.toml
Normal file
23
core-traits/Cargo.toml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
[package]
|
||||||
|
name = "core_traits"
|
||||||
|
version = "0.0.2"
|
||||||
|
workspace = ".."
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "core_traits"
|
||||||
|
path = "lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
chrono = { version = "~0.4", features = ["serde"] }
|
||||||
|
enum-set = "~0.0.8"
|
||||||
|
lazy_static = "~1.4"
|
||||||
|
indexmap = "~1.9"
|
||||||
|
ordered-float = { version = "~2.8", features = ["serde"] }
|
||||||
|
uuid = { version = "~1", features = ["v4", "serde"] }
|
||||||
|
serde = { version = "~1.0", features = ["rc"] }
|
||||||
|
serde_derive = "~1.0"
|
||||||
|
bytes = { version = "1.0.1", features = ["serde"] }
|
||||||
|
|
||||||
|
[dependencies.edn]
|
||||||
|
path = "../edn"
|
||||||
|
features = ["serde_support"]
|
1109
core-traits/lib.rs
Normal file
1109
core-traits/lib.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -8,13 +8,9 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
use ::enum_set::{
|
use enum_set::EnumSet;
|
||||||
EnumSet,
|
|
||||||
};
|
|
||||||
|
|
||||||
use ::types::{
|
use crate::ValueType;
|
||||||
ValueType,
|
|
||||||
};
|
|
||||||
|
|
||||||
trait EnumSetExtensions<T: ::enum_set::CLike + Clone> {
|
trait EnumSetExtensions<T: ::enum_set::CLike + Clone> {
|
||||||
/// Return a set containing both `x` and `y`.
|
/// Return a set containing both `x` and `y`.
|
||||||
|
@ -41,7 +37,6 @@ impl<T: ::enum_set::CLike + Clone> EnumSetExtensions<T> for EnumSet<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub struct ValueTypeSet(pub EnumSet<ValueType>);
|
pub struct ValueTypeSet(pub EnumSet<ValueType>);
|
||||||
|
|
||||||
|
@ -97,53 +92,53 @@ impl ValueTypeSet {
|
||||||
self.0.insert(vt)
|
self.0.insert(vt)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(self) -> usize {
|
||||||
self.0.len()
|
self.0.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a set containing all the types in this set and `other`.
|
/// Returns a set containing all the types in this set and `other`.
|
||||||
pub fn union(&self, other: &ValueTypeSet) -> ValueTypeSet {
|
pub fn union(self, other: ValueTypeSet) -> ValueTypeSet {
|
||||||
ValueTypeSet(self.0.union(other.0))
|
ValueTypeSet(self.0.union(other.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn intersection(&self, other: &ValueTypeSet) -> ValueTypeSet {
|
pub fn intersection(self, other: ValueTypeSet) -> ValueTypeSet {
|
||||||
ValueTypeSet(self.0.intersection(other.0))
|
ValueTypeSet(self.0.intersection(other.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the set difference between `self` and `other`, which is the
|
/// Returns the set difference between `self` and `other`, which is the
|
||||||
/// set of items in `self` that are not in `other`.
|
/// set of items in `self` that are not in `other`.
|
||||||
pub fn difference(&self, other: &ValueTypeSet) -> ValueTypeSet {
|
pub fn difference(self, other: ValueTypeSet) -> ValueTypeSet {
|
||||||
ValueTypeSet(self.0 - other.0)
|
ValueTypeSet(self.0 - other.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return an arbitrary type that's part of this set.
|
/// Return an arbitrary type that's part of this set.
|
||||||
/// For a set containing a single type, this will be that type.
|
/// For a set containing a single type, this will be that type.
|
||||||
pub fn exemplar(&self) -> Option<ValueType> {
|
pub fn exemplar(self) -> Option<ValueType> {
|
||||||
self.0.iter().next()
|
self.0.iter().next()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_subset(&self, other: &ValueTypeSet) -> bool {
|
pub fn is_subset(self, other: ValueTypeSet) -> bool {
|
||||||
self.0.is_subset(&other.0)
|
self.0.is_subset(&other.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if `self` and `other` contain no items in common.
|
/// Returns true if `self` and `other` contain no items in common.
|
||||||
pub fn is_disjoint(&self, other: &ValueTypeSet) -> bool {
|
pub fn is_disjoint(self, other: ValueTypeSet) -> bool {
|
||||||
self.0.is_disjoint(&other.0)
|
self.0.is_disjoint(&other.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains(&self, vt: ValueType) -> bool {
|
pub fn contains(self, vt: ValueType) -> bool {
|
||||||
self.0.contains(&vt)
|
self.0.contains(&vt)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(self) -> bool {
|
||||||
self.0.is_empty()
|
self.0.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_unit(&self) -> bool {
|
pub fn is_unit(self) -> bool {
|
||||||
self.0.len() == 1
|
self.0.len() == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter(&self) -> ::enum_set::Iter<ValueType> {
|
pub fn iter(self) -> ::enum_set::Iter<ValueType> {
|
||||||
self.0.iter()
|
self.0.iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,8 +150,8 @@ impl From<ValueType> for ValueTypeSet {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ValueTypeSet {
|
impl ValueTypeSet {
|
||||||
pub fn is_only_numeric(&self) -> bool {
|
pub fn is_only_numeric(self) -> bool {
|
||||||
self.is_subset(&ValueTypeSet::of_numeric_types())
|
self.is_subset(ValueTypeSet::of_numeric_types())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,21 +10,20 @@
|
||||||
|
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use edn::symbols;
|
||||||
/// Literal `Value` instances in the the "db" namespace.
|
/// Literal `Value` instances in the the "db" namespace.
|
||||||
///
|
///
|
||||||
/// Used through-out the transactor to match core DB constructs.
|
/// Used through-out the transactor to match core DB constructs.
|
||||||
|
|
||||||
use edn::types::Value;
|
use edn::types::Value;
|
||||||
use edn::symbols;
|
|
||||||
|
|
||||||
/// Declare a lazy static `ident` of type `Value::Keyword` with the given `namespace` and
|
/// Declare a lazy static `ident` of type `Value::Keyword` with the given `namespace` and
|
||||||
/// `name`.
|
/// `name`.
|
||||||
///
|
///
|
||||||
/// It may look surprising that we declare a new `lazy_static!` block rather than including
|
/// It may look surprising to declare a new `lazy_static!` block rather than including
|
||||||
/// invocations inside an existing `lazy_static!` block. The latter cannot be done, since macros
|
/// invocations inside an existing `lazy_static!` block. The latter cannot be done, since macros
|
||||||
/// are expanded outside-in. Looking at the `lazy_static!` source suggests that there is no harm in
|
/// will be expanded outside-in. Looking at the `lazy_static!` source suggests that there is no
|
||||||
/// repeating that macro, since internally a multi-`static` block is expanded into many
|
/// harm in repeating that macro, since internally a multi-`static` block will be expanded into
|
||||||
/// single-`static` blocks.
|
/// many single-`static` blocks.
|
||||||
///
|
///
|
||||||
/// TODO: take just ":db.part/db" and define DB_PART_DB using "db.part" and "db".
|
/// TODO: take just ":db.part/db" and define DB_PART_DB using "db.part" and "db".
|
||||||
macro_rules! lazy_static_namespaced_keyword_value (
|
macro_rules! lazy_static_namespaced_keyword_value (
|
||||||
|
@ -59,6 +58,7 @@ lazy_static_namespaced_keyword_value!(DB_TYPE_REF, "db.type", "ref");
|
||||||
lazy_static_namespaced_keyword_value!(DB_TYPE_STRING, "db.type", "string");
|
lazy_static_namespaced_keyword_value!(DB_TYPE_STRING, "db.type", "string");
|
||||||
lazy_static_namespaced_keyword_value!(DB_TYPE_URI, "db.type", "uri");
|
lazy_static_namespaced_keyword_value!(DB_TYPE_URI, "db.type", "uri");
|
||||||
lazy_static_namespaced_keyword_value!(DB_TYPE_UUID, "db.type", "uuid");
|
lazy_static_namespaced_keyword_value!(DB_TYPE_UUID, "db.type", "uuid");
|
||||||
|
lazy_static_namespaced_keyword_value!(DB_TYPE_BYTES, "db.type", "bytes");
|
||||||
lazy_static_namespaced_keyword_value!(DB_UNIQUE, "db", "unique");
|
lazy_static_namespaced_keyword_value!(DB_UNIQUE, "db", "unique");
|
||||||
lazy_static_namespaced_keyword_value!(DB_UNIQUE_IDENTITY, "db.unique", "identity");
|
lazy_static_namespaced_keyword_value!(DB_UNIQUE_IDENTITY, "db.unique", "identity");
|
||||||
lazy_static_namespaced_keyword_value!(DB_UNIQUE_VALUE, "db.unique", "value");
|
lazy_static_namespaced_keyword_value!(DB_UNIQUE_VALUE, "db.unique", "value");
|
|
@ -1,18 +1,18 @@
|
||||||
[package]
|
[package]
|
||||||
name = "mentat_core"
|
name = "mentat_core"
|
||||||
version = "0.0.1"
|
version = "0.0.2"
|
||||||
workspace = ".."
|
workspace = ".."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "~0.4", features = ["serde"] }
|
||||||
enum-set = { git = "https://github.com/rnewman/enum-set" }
|
enum-set = "~0.0"
|
||||||
failure = "0.1.1"
|
failure = "~0.1"
|
||||||
indexmap = "1"
|
indexmap = "~1.9"
|
||||||
lazy_static = "0.2"
|
ordered-float = { version = "~2.8", features = ["serde"] }
|
||||||
ordered-float = { version = "0.5", features = ["serde"] }
|
uuid = { version = "~1", features = ["v4", "serde"] }
|
||||||
uuid = { version = "0.5", features = ["v4", "serde"] }
|
|
||||||
serde = { version = "1.0", features = ["rc"] }
|
[dependencies.core_traits]
|
||||||
serde_derive = "1.0"
|
path = "../core-traits"
|
||||||
|
|
||||||
[dependencies.edn]
|
[dependencies.edn]
|
||||||
path = "../edn"
|
path = "../edn"
|
||||||
|
|
|
@ -9,31 +9,41 @@
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
/// Cache traits.
|
/// Cache traits.
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
use std::collections::{
|
use core_traits::{Entid, TypedValue};
|
||||||
BTreeSet,
|
|
||||||
};
|
|
||||||
|
|
||||||
use ::{
|
use crate::Schema;
|
||||||
Entid,
|
|
||||||
Schema,
|
|
||||||
TypedValue,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub trait CachedAttributes {
|
pub trait CachedAttributes {
|
||||||
fn is_attribute_cached_reverse(&self, entid: Entid) -> bool;
|
fn is_attribute_cached_reverse(&self, entid: Entid) -> bool;
|
||||||
fn is_attribute_cached_forward(&self, entid: Entid) -> bool;
|
fn is_attribute_cached_forward(&self, entid: Entid) -> bool;
|
||||||
fn has_cached_attributes(&self) -> bool;
|
fn has_cached_attributes(&self) -> bool;
|
||||||
|
|
||||||
fn get_values_for_entid(&self, schema: &Schema, attribute: Entid, entid: Entid) -> Option<&Vec<TypedValue>>;
|
fn get_values_for_entid(
|
||||||
fn get_value_for_entid(&self, schema: &Schema, attribute: Entid, entid: Entid) -> Option<&TypedValue>;
|
&self,
|
||||||
|
schema: &Schema,
|
||||||
|
attribute: Entid,
|
||||||
|
entid: Entid,
|
||||||
|
) -> Option<&Vec<TypedValue>>;
|
||||||
|
fn get_value_for_entid(
|
||||||
|
&self,
|
||||||
|
schema: &Schema,
|
||||||
|
attribute: Entid,
|
||||||
|
entid: Entid,
|
||||||
|
) -> Option<&TypedValue>;
|
||||||
|
|
||||||
/// Reverse lookup.
|
/// Reverse lookup.
|
||||||
fn get_entid_for_value(&self, attribute: Entid, value: &TypedValue) -> Option<Entid>;
|
fn get_entid_for_value(&self, attribute: Entid, value: &TypedValue) -> Option<Entid>;
|
||||||
fn get_entids_for_value(&self, attribute: Entid, value: &TypedValue) -> Option<&BTreeSet<Entid>>;
|
fn get_entids_for_value(
|
||||||
|
&self,
|
||||||
|
attribute: Entid,
|
||||||
|
value: &TypedValue,
|
||||||
|
) -> Option<&BTreeSet<Entid>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait UpdateableCache<E> {
|
pub trait UpdateableCache<E> {
|
||||||
fn update<I>(&mut self, schema: &Schema, retractions: I, assertions: I) -> Result<(), E>
|
fn update<I>(&mut self, schema: &Schema, retractions: I, assertions: I) -> Result<(), E>
|
||||||
where I: Iterator<Item=(Entid, Entid, TypedValue)>;
|
where
|
||||||
|
I: Iterator<Item = (Entid, Entid, TypedValue)>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Default)]
|
||||||
pub struct RcCounter {
|
pub struct RcCounter {
|
||||||
c: Rc<Cell<usize>>,
|
c: Rc<Cell<usize>>,
|
||||||
}
|
}
|
||||||
|
@ -19,11 +19,15 @@ pub struct RcCounter {
|
||||||
/// A simple shared counter.
|
/// A simple shared counter.
|
||||||
impl RcCounter {
|
impl RcCounter {
|
||||||
pub fn with_initial(value: usize) -> Self {
|
pub fn with_initial(value: usize) -> Self {
|
||||||
RcCounter { c: Rc::new(Cell::new(value)) }
|
RcCounter {
|
||||||
|
c: Rc::new(Cell::new(value)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
RcCounter { c: Rc::new(Cell::new(0)) }
|
RcCounter {
|
||||||
|
c: Rc::new(Cell::new(0)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the next value in the sequence.
|
/// Return the next value in the sequence.
|
||||||
|
|
341
core/src/lib.rs
341
core/src/lib.rs
|
@ -14,22 +14,16 @@ extern crate failure;
|
||||||
extern crate indexmap;
|
extern crate indexmap;
|
||||||
extern crate ordered_float;
|
extern crate ordered_float;
|
||||||
extern crate uuid;
|
extern crate uuid;
|
||||||
extern crate serde;
|
|
||||||
|
|
||||||
#[macro_use]
|
extern crate core_traits;
|
||||||
extern crate lazy_static;
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_derive;
|
|
||||||
|
|
||||||
extern crate edn;
|
extern crate edn;
|
||||||
|
|
||||||
pub mod values;
|
use core_traits::{Attribute, Entid, KnownEntid, ValueType};
|
||||||
|
|
||||||
mod cache;
|
mod cache;
|
||||||
|
|
||||||
use std::collections::{
|
use std::collections::BTreeMap;
|
||||||
BTreeMap,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use uuid::Uuid;
|
pub use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -38,209 +32,21 @@ pub use chrono::{
|
||||||
Timelike, // For truncation.
|
Timelike, // For truncation.
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use edn::{
|
pub use edn::parse::parse_query;
|
||||||
Cloned,
|
pub use edn::{Cloned, FromMicros, FromRc, Keyword, ToMicros, Utc, ValueRc};
|
||||||
FromMicros,
|
|
||||||
FromRc,
|
|
||||||
Keyword,
|
|
||||||
ToMicros,
|
|
||||||
Utc,
|
|
||||||
ValueRc,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use edn::parse::{
|
pub use crate::cache::{CachedAttributes, UpdateableCache};
|
||||||
parse_query,
|
|
||||||
ParseError as EdnParseError,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use cache::{
|
|
||||||
CachedAttributes,
|
|
||||||
UpdateableCache,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
mod sql_types;
|
||||||
|
mod tx_report;
|
||||||
/// Core types defining a Mentat knowledge base.
|
/// Core types defining a Mentat knowledge base.
|
||||||
mod types;
|
mod types;
|
||||||
mod tx_report;
|
|
||||||
mod value_type_set;
|
|
||||||
mod sql_types;
|
|
||||||
|
|
||||||
pub use tx_report::{
|
pub use crate::tx_report::TxReport;
|
||||||
TxReport,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use types::{
|
pub use crate::types::ValueTypeTag;
|
||||||
Binding,
|
|
||||||
Entid,
|
|
||||||
KnownEntid,
|
|
||||||
StructuredMap,
|
|
||||||
TypedValue,
|
|
||||||
ValueType,
|
|
||||||
ValueTypeTag,
|
|
||||||
now,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use value_type_set::{
|
pub use crate::sql_types::{SQLTypeAffinity, SQLValueType, SQLValueTypeSet};
|
||||||
ValueTypeSet,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use sql_types::{
|
|
||||||
SQLTypeAffinity,
|
|
||||||
SQLValueType,
|
|
||||||
SQLValueTypeSet,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Bit flags used in `flags0` column in temporary tables created during search,
|
|
||||||
/// such as the `search_results`, `inexact_searches` and `exact_searches` tables.
|
|
||||||
/// When moving to a more concrete table, such as `datoms`, they are expanded out
|
|
||||||
/// via these flags and put into their own column rather than a bit field.
|
|
||||||
pub enum AttributeBitFlags {
|
|
||||||
IndexAVET = 1 << 0,
|
|
||||||
IndexVAET = 1 << 1,
|
|
||||||
IndexFulltext = 1 << 2,
|
|
||||||
UniqueValue = 1 << 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod attribute {
|
|
||||||
use TypedValue;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
|
||||||
pub enum Unique {
|
|
||||||
Value,
|
|
||||||
Identity,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Unique {
|
|
||||||
// This is easier than rejigging DB_UNIQUE_VALUE to not be EDN.
|
|
||||||
pub fn into_typed_value(self) -> TypedValue {
|
|
||||||
match self {
|
|
||||||
Unique::Value => TypedValue::typed_ns_keyword("db.unique", "value"),
|
|
||||||
Unique::Identity => TypedValue::typed_ns_keyword("db.unique", "identity"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A Mentat schema attribute has a value type and several other flags determining how assertions
|
|
||||||
/// with the attribute are interpreted.
|
|
||||||
///
|
|
||||||
/// TODO: consider packing this into a bitfield or similar.
|
|
||||||
#[derive(Clone,Debug,Eq,Hash,Ord,PartialOrd,PartialEq)]
|
|
||||||
pub struct Attribute {
|
|
||||||
/// The associated value type, i.e., `:db/valueType`?
|
|
||||||
pub value_type: ValueType,
|
|
||||||
|
|
||||||
/// `true` if this attribute is multi-valued, i.e., it is `:db/cardinality
|
|
||||||
/// :db.cardinality/many`. `false` if this attribute is single-valued (the default), i.e., it
|
|
||||||
/// is `:db/cardinality :db.cardinality/one`.
|
|
||||||
pub multival: bool,
|
|
||||||
|
|
||||||
/// `None` if this attribute is neither unique-value nor unique-identity.
|
|
||||||
///
|
|
||||||
/// `Some(attribute::Unique::Value)` if this attribute is unique-value, i.e., it is `:db/unique
|
|
||||||
/// :db.unique/value`.
|
|
||||||
///
|
|
||||||
/// *Unique-value* means that there is at most one assertion with the attribute and a
|
|
||||||
/// particular value in the datom store. Unique-value attributes can be used in lookup-refs.
|
|
||||||
///
|
|
||||||
/// `Some(attribute::Unique::Identity)` if this attribute is unique-identity, i.e., it is `:db/unique
|
|
||||||
/// :db.unique/identity`.
|
|
||||||
///
|
|
||||||
/// Unique-identity attributes always have value type `Ref`.
|
|
||||||
///
|
|
||||||
/// *Unique-identity* means that the attribute is *unique-value* and that they can be used in
|
|
||||||
/// lookup-refs and will automatically upsert where appropriate.
|
|
||||||
pub unique: Option<attribute::Unique>,
|
|
||||||
|
|
||||||
/// `true` if this attribute is automatically indexed, i.e., it is `:db/indexing true`.
|
|
||||||
pub index: bool,
|
|
||||||
|
|
||||||
/// `true` if this attribute is automatically fulltext indexed, i.e., it is `:db/fulltext true`.
|
|
||||||
///
|
|
||||||
/// Fulltext attributes always have string values.
|
|
||||||
pub fulltext: bool,
|
|
||||||
|
|
||||||
/// `true` if this attribute is a component, i.e., it is `:db/isComponent true`.
|
|
||||||
///
|
|
||||||
/// Component attributes always have value type `Ref`.
|
|
||||||
///
|
|
||||||
/// They are used to compose entities from component sub-entities: they are fetched recursively
|
|
||||||
/// by pull expressions, and they are automatically recursively deleted where appropriate.
|
|
||||||
pub component: bool,
|
|
||||||
|
|
||||||
/// `true` if this attribute doesn't require history to be kept, i.e., it is `:db/noHistory true`.
|
|
||||||
pub no_history: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Attribute {
|
|
||||||
/// Combine several attribute flags into a bitfield used in temporary search tables.
|
|
||||||
pub fn flags(&self) -> u8 {
|
|
||||||
let mut flags: u8 = 0;
|
|
||||||
|
|
||||||
if self.index {
|
|
||||||
flags |= AttributeBitFlags::IndexAVET as u8;
|
|
||||||
}
|
|
||||||
if self.value_type == ValueType::Ref {
|
|
||||||
flags |= AttributeBitFlags::IndexVAET as u8;
|
|
||||||
}
|
|
||||||
if self.fulltext {
|
|
||||||
flags |= AttributeBitFlags::IndexFulltext as u8;
|
|
||||||
}
|
|
||||||
if self.unique.is_some() {
|
|
||||||
flags |= AttributeBitFlags::UniqueValue as u8;
|
|
||||||
}
|
|
||||||
flags
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_edn_value(&self, ident: Option<Keyword>) -> edn::Value {
|
|
||||||
let mut attribute_map: BTreeMap<edn::Value, edn::Value> = BTreeMap::default();
|
|
||||||
if let Some(ident) = ident {
|
|
||||||
attribute_map.insert(values::DB_IDENT.clone(), edn::Value::Keyword(ident));
|
|
||||||
}
|
|
||||||
|
|
||||||
attribute_map.insert(values::DB_VALUE_TYPE.clone(), self.value_type.into_edn_value());
|
|
||||||
|
|
||||||
attribute_map.insert(values::DB_CARDINALITY.clone(), if self.multival { values::DB_CARDINALITY_MANY.clone() } else { values::DB_CARDINALITY_ONE.clone() });
|
|
||||||
|
|
||||||
match self.unique {
|
|
||||||
Some(attribute::Unique::Value) => { attribute_map.insert(values::DB_UNIQUE.clone(), values::DB_UNIQUE_VALUE.clone()); },
|
|
||||||
Some(attribute::Unique::Identity) => { attribute_map.insert(values::DB_UNIQUE.clone(), values::DB_UNIQUE_IDENTITY.clone()); },
|
|
||||||
None => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.index {
|
|
||||||
attribute_map.insert(values::DB_INDEX.clone(), edn::Value::Boolean(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.fulltext {
|
|
||||||
attribute_map.insert(values::DB_FULLTEXT.clone(), edn::Value::Boolean(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.component {
|
|
||||||
attribute_map.insert(values::DB_IS_COMPONENT.clone(), edn::Value::Boolean(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.no_history {
|
|
||||||
attribute_map.insert(values::DB_NO_HISTORY.clone(), edn::Value::Boolean(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
edn::Value::Map(attribute_map)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Attribute {
|
|
||||||
fn default() -> Attribute {
|
|
||||||
Attribute {
|
|
||||||
// There's no particular reason to favour one value type, so Ref it is.
|
|
||||||
value_type: ValueType::Ref,
|
|
||||||
fulltext: false,
|
|
||||||
index: false,
|
|
||||||
multival: false,
|
|
||||||
unique: None,
|
|
||||||
component: false,
|
|
||||||
no_history: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Map `Keyword` idents (`:db/ident`) to positive integer entids (`1`).
|
/// Map `Keyword` idents (`:db/ident`) to positive integer entids (`1`).
|
||||||
pub type IdentMap = BTreeMap<Keyword, Entid>;
|
pub type IdentMap = BTreeMap<Keyword, Entid>;
|
||||||
|
@ -284,15 +90,21 @@ pub struct Schema {
|
||||||
pub trait HasSchema {
|
pub trait HasSchema {
|
||||||
fn entid_for_type(&self, t: ValueType) -> Option<KnownEntid>;
|
fn entid_for_type(&self, t: ValueType) -> Option<KnownEntid>;
|
||||||
|
|
||||||
fn get_ident<T>(&self, x: T) -> Option<&Keyword> where T: Into<Entid>;
|
fn get_ident<T>(&self, x: T) -> Option<&Keyword>
|
||||||
|
where
|
||||||
|
T: Into<Entid>;
|
||||||
fn get_entid(&self, x: &Keyword) -> Option<KnownEntid>;
|
fn get_entid(&self, x: &Keyword) -> Option<KnownEntid>;
|
||||||
fn attribute_for_entid<T>(&self, x: T) -> Option<&Attribute> where T: Into<Entid>;
|
fn attribute_for_entid<T>(&self, x: T) -> Option<&Attribute>
|
||||||
|
where
|
||||||
|
T: Into<Entid>;
|
||||||
|
|
||||||
// Returns the attribute and the entid named by the provided ident.
|
// Returns the attribute and the entid named by the provided ident.
|
||||||
fn attribute_for_ident(&self, ident: &Keyword) -> Option<(&Attribute, KnownEntid)>;
|
fn attribute_for_ident(&self, ident: &Keyword) -> Option<(&Attribute, KnownEntid)>;
|
||||||
|
|
||||||
/// Return true if the provided entid identifies an attribute in this schema.
|
/// Return true if the provided entid identifies an attribute in this schema.
|
||||||
fn is_attribute<T>(&self, x: T) -> bool where T: Into<Entid>;
|
fn is_attribute<T>(&self, x: T) -> bool
|
||||||
|
where
|
||||||
|
T: Into<Entid>;
|
||||||
|
|
||||||
/// Return true if the provided ident identifies an attribute in this schema.
|
/// Return true if the provided ident identifies an attribute in this schema.
|
||||||
fn identifies_attribute(&self, x: &Keyword) -> bool;
|
fn identifies_attribute(&self, x: &Keyword) -> bool;
|
||||||
|
@ -302,26 +114,34 @@ pub trait HasSchema {
|
||||||
|
|
||||||
impl Schema {
|
impl Schema {
|
||||||
pub fn new(ident_map: IdentMap, entid_map: EntidMap, attribute_map: AttributeMap) -> Schema {
|
pub fn new(ident_map: IdentMap, entid_map: EntidMap, attribute_map: AttributeMap) -> Schema {
|
||||||
let mut s = Schema { ident_map, entid_map, attribute_map, component_attributes: Vec::new() };
|
let mut s = Schema {
|
||||||
|
ident_map,
|
||||||
|
entid_map,
|
||||||
|
attribute_map,
|
||||||
|
component_attributes: Vec::new(),
|
||||||
|
};
|
||||||
s.update_component_attributes();
|
s.update_component_attributes();
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an symbolic representation of the schema suitable for applying across Mentat stores.
|
/// Returns an symbolic representation of the schema suitable for applying across Mentat stores.
|
||||||
pub fn to_edn_value(&self) -> edn::Value {
|
pub fn to_edn_value(&self) -> edn::Value {
|
||||||
edn::Value::Vector((&self.attribute_map).iter()
|
edn::Value::Vector(
|
||||||
.map(|(entid, attribute)|
|
(&self.attribute_map)
|
||||||
attribute.to_edn_value(self.get_ident(*entid).cloned()))
|
.iter()
|
||||||
.collect())
|
.map(|(entid, attribute)| attribute.to_edn_value(self.get_ident(*entid).cloned()))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_raw_entid(&self, x: &Keyword) -> Option<Entid> {
|
fn get_raw_entid(&self, x: &Keyword) -> Option<Entid> {
|
||||||
self.ident_map.get(x).map(|x| *x)
|
self.ident_map.get(x).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_component_attributes(&mut self) {
|
pub fn update_component_attributes(&mut self) {
|
||||||
let mut components: Vec<Entid>;
|
let mut components: Vec<Entid>;
|
||||||
components = self.attribute_map
|
components = self
|
||||||
|
.attribute_map
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(k, v)| if v.component { Some(*k) } else { None })
|
.filter_map(|(k, v)| if v.component { Some(*k) } else { None })
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -336,7 +156,10 @@ impl HasSchema for Schema {
|
||||||
self.get_entid(&t.into_keyword())
|
self.get_entid(&t.into_keyword())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_ident<T>(&self, x: T) -> Option<&Keyword> where T: Into<Entid> {
|
fn get_ident<T>(&self, x: T) -> Option<&Keyword>
|
||||||
|
where
|
||||||
|
T: Into<Entid>,
|
||||||
|
{
|
||||||
self.entid_map.get(&x.into())
|
self.entid_map.get(&x.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,25 +167,33 @@ impl HasSchema for Schema {
|
||||||
self.get_raw_entid(x).map(KnownEntid)
|
self.get_raw_entid(x).map(KnownEntid)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn attribute_for_entid<T>(&self, x: T) -> Option<&Attribute> where T: Into<Entid> {
|
fn attribute_for_entid<T>(&self, x: T) -> Option<&Attribute>
|
||||||
|
where
|
||||||
|
T: Into<Entid>,
|
||||||
|
{
|
||||||
self.attribute_map.get(&x.into())
|
self.attribute_map.get(&x.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn attribute_for_ident(&self, ident: &Keyword) -> Option<(&Attribute, KnownEntid)> {
|
fn attribute_for_ident(&self, ident: &Keyword) -> Option<(&Attribute, KnownEntid)> {
|
||||||
self.get_raw_entid(&ident)
|
self.get_raw_entid(&ident).and_then(|entid| {
|
||||||
.and_then(|entid| {
|
self.attribute_for_entid(entid)
|
||||||
self.attribute_for_entid(entid).map(|a| (a, KnownEntid(entid)))
|
.map(|a| (a, KnownEntid(entid)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return true if the provided entid identifies an attribute in this schema.
|
/// Return true if the provided entid identifies an attribute in this schema.
|
||||||
fn is_attribute<T>(&self, x: T) -> bool where T: Into<Entid> {
|
fn is_attribute<T>(&self, x: T) -> bool
|
||||||
|
where
|
||||||
|
T: Into<Entid>,
|
||||||
|
{
|
||||||
self.attribute_map.contains_key(&x.into())
|
self.attribute_map.contains_key(&x.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return true if the provided ident identifies an attribute in this schema.
|
/// Return true if the provided ident identifies an attribute in this schema.
|
||||||
fn identifies_attribute(&self, x: &Keyword) -> bool {
|
fn identifies_attribute(&self, x: &Keyword) -> bool {
|
||||||
self.get_raw_entid(x).map(|e| self.is_attribute(e)).unwrap_or(false)
|
self.get_raw_entid(x)
|
||||||
|
.map(|e| self.is_attribute(e))
|
||||||
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn component_attributes(&self) -> &[Entid] {
|
fn component_attributes(&self) -> &[Entid] {
|
||||||
|
@ -393,7 +224,7 @@ pub mod util;
|
||||||
macro_rules! interpose {
|
macro_rules! interpose {
|
||||||
( $name: pat, $across: expr, $body: block, $inter: block ) => {
|
( $name: pat, $across: expr, $body: block, $inter: block ) => {
|
||||||
interpose_iter!($name, $across.iter(), $body, $inter)
|
interpose_iter!($name, $across.iter(), $body, $inter)
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A helper to bind `name` to values in `across`, running `body` for each value,
|
/// A helper to bind `name` to values in `across`, running `body` for each value,
|
||||||
|
@ -409,7 +240,7 @@ macro_rules! interpose_iter {
|
||||||
$body;
|
$body;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -418,6 +249,8 @@ mod test {
|
||||||
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use core_traits::{attribute, TypedValue};
|
||||||
|
|
||||||
fn associate_ident(schema: &mut Schema, i: Keyword, e: Entid) {
|
fn associate_ident(schema: &mut Schema, i: Keyword, e: Entid) {
|
||||||
schema.entid_map.insert(e, i.clone());
|
schema.entid_map.insert(e, i.clone());
|
||||||
schema.ident_map.insert(i, e);
|
schema.ident_map.insert(i, e);
|
||||||
|
@ -427,58 +260,12 @@ mod test {
|
||||||
schema.attribute_map.insert(e, a);
|
schema.attribute_map.insert(e, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_attribute_flags() {
|
|
||||||
let attr1 = Attribute {
|
|
||||||
index: true,
|
|
||||||
value_type: ValueType::Ref,
|
|
||||||
fulltext: false,
|
|
||||||
unique: None,
|
|
||||||
multival: false,
|
|
||||||
component: false,
|
|
||||||
no_history: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert!(attr1.flags() & AttributeBitFlags::IndexAVET as u8 != 0);
|
|
||||||
assert!(attr1.flags() & AttributeBitFlags::IndexVAET as u8 != 0);
|
|
||||||
assert!(attr1.flags() & AttributeBitFlags::IndexFulltext as u8 == 0);
|
|
||||||
assert!(attr1.flags() & AttributeBitFlags::UniqueValue as u8 == 0);
|
|
||||||
|
|
||||||
let attr2 = Attribute {
|
|
||||||
index: false,
|
|
||||||
value_type: ValueType::Boolean,
|
|
||||||
fulltext: true,
|
|
||||||
unique: Some(attribute::Unique::Value),
|
|
||||||
multival: false,
|
|
||||||
component: false,
|
|
||||||
no_history: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert!(attr2.flags() & AttributeBitFlags::IndexAVET as u8 == 0);
|
|
||||||
assert!(attr2.flags() & AttributeBitFlags::IndexVAET as u8 == 0);
|
|
||||||
assert!(attr2.flags() & AttributeBitFlags::IndexFulltext as u8 != 0);
|
|
||||||
assert!(attr2.flags() & AttributeBitFlags::UniqueValue as u8 != 0);
|
|
||||||
|
|
||||||
let attr3 = Attribute {
|
|
||||||
index: false,
|
|
||||||
value_type: ValueType::Boolean,
|
|
||||||
fulltext: true,
|
|
||||||
unique: Some(attribute::Unique::Identity),
|
|
||||||
multival: false,
|
|
||||||
component: false,
|
|
||||||
no_history: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert!(attr3.flags() & AttributeBitFlags::IndexAVET as u8 == 0);
|
|
||||||
assert!(attr3.flags() & AttributeBitFlags::IndexVAET as u8 == 0);
|
|
||||||
assert!(attr3.flags() & AttributeBitFlags::IndexFulltext as u8 != 0);
|
|
||||||
assert!(attr3.flags() & AttributeBitFlags::UniqueValue as u8 != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_datetime_truncation() {
|
fn test_datetime_truncation() {
|
||||||
let dt: DateTime<Utc> = DateTime::from_str("2018-01-11T00:34:09.273457004Z").expect("parsed");
|
let dt: DateTime<Utc> =
|
||||||
let expected: DateTime<Utc> = DateTime::from_str("2018-01-11T00:34:09.273457Z").expect("parsed");
|
DateTime::from_str("2018-01-11T00:34:09.273457004Z").expect("parsed");
|
||||||
|
let expected: DateTime<Utc> =
|
||||||
|
DateTime::from_str("2018-01-11T00:34:09.273457Z").expect("parsed");
|
||||||
|
|
||||||
let tv: TypedValue = dt.into();
|
let tv: TypedValue = dt.into();
|
||||||
if let TypedValue::Instant(roundtripped) = tv {
|
if let TypedValue::Instant(roundtripped) = tv {
|
||||||
|
@ -546,7 +333,9 @@ mod test {
|
||||||
:db/cardinality :db.cardinality/one
|
:db/cardinality :db.cardinality/one
|
||||||
:db/unique :db.unique/identity
|
:db/unique :db.unique/identity
|
||||||
:db/isComponent true }, ]"#;
|
:db/isComponent true }, ]"#;
|
||||||
let expected_value = edn::parse::value(&expected_output).expect("to be able to parse").without_spans();
|
let expected_value = edn::parse::value(&expected_output)
|
||||||
|
.expect("to be able to parse")
|
||||||
|
.without_spans();
|
||||||
assert_eq!(expected_value, value);
|
assert_eq!(expected_value, value);
|
||||||
|
|
||||||
// let's compare the whole thing again, just to make sure we are not changing anything when we convert to edn.
|
// let's compare the whole thing again, just to make sure we are not changing anything when we convert to edn.
|
||||||
|
|
|
@ -8,18 +8,11 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
use std::collections::{
|
use std::collections::BTreeSet;
|
||||||
BTreeSet,
|
|
||||||
};
|
|
||||||
|
|
||||||
use types::{
|
use core_traits::{ValueType, ValueTypeSet};
|
||||||
ValueType,
|
|
||||||
ValueTypeTag,
|
|
||||||
};
|
|
||||||
|
|
||||||
use value_type_set::{
|
use crate::types::ValueTypeTag;
|
||||||
ValueTypeSet,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Type safe representation of the possible return values from SQLite's `typeof`
|
/// Type safe representation of the possible return values from SQLite's `typeof`
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||||
|
@ -58,6 +51,7 @@ impl SQLValueType for ValueType {
|
||||||
ValueType::String => (10, None),
|
ValueType::String => (10, None),
|
||||||
ValueType::Uuid => (11, None),
|
ValueType::Uuid => (11, None),
|
||||||
ValueType::Keyword => (13, None),
|
ValueType::Keyword => (13, None),
|
||||||
|
ValueType::Bytes => (15, Some(SQLTypeAffinity::Blob)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,18 +62,8 @@ impl SQLValueType for ValueType {
|
||||||
|
|
||||||
/// Returns true if the provided integer is in the SQLite value space of this type. For
|
/// Returns true if the provided integer is in the SQLite value space of this type. For
|
||||||
/// example, `1` is how we encode `true`.
|
/// example, `1` is how we encode `true`.
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use mentat_core::{ValueType, SQLValueType};
|
|
||||||
/// assert!(!ValueType::Instant.accommodates_integer(1493399581314));
|
|
||||||
/// assert!(!ValueType::Instant.accommodates_integer(1493399581314000));
|
|
||||||
/// assert!(ValueType::Boolean.accommodates_integer(1));
|
|
||||||
/// assert!(!ValueType::Boolean.accommodates_integer(-1));
|
|
||||||
/// assert!(!ValueType::Boolean.accommodates_integer(10));
|
|
||||||
/// assert!(!ValueType::String.accommodates_integer(10));
|
|
||||||
/// ```
|
|
||||||
fn accommodates_integer(&self, int: i64) -> bool {
|
fn accommodates_integer(&self, int: i64) -> bool {
|
||||||
use ValueType::*;
|
use crate::ValueType::*;
|
||||||
match *self {
|
match *self {
|
||||||
Instant => false, // Always use #inst.
|
Instant => false, // Always use #inst.
|
||||||
Long | Double => true,
|
Long | Double => true,
|
||||||
|
@ -88,6 +72,7 @@ impl SQLValueType for ValueType {
|
||||||
ValueType::String => false,
|
ValueType::String => false,
|
||||||
Keyword => false,
|
Keyword => false,
|
||||||
Uuid => false,
|
Uuid => false,
|
||||||
|
Bytes => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,3 +122,19 @@ impl SQLValueTypeSet for ValueTypeSet {
|
||||||
!acc.is_empty()
|
!acc.is_empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::sql_types::SQLValueType;
|
||||||
|
use core_traits::ValueType;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_accommodates_integer() {
|
||||||
|
assert!(!ValueType::Instant.accommodates_integer(1493399581314));
|
||||||
|
assert!(!ValueType::Instant.accommodates_integer(1493399581314000));
|
||||||
|
assert!(ValueType::Boolean.accommodates_integer(1));
|
||||||
|
assert!(!ValueType::Boolean.accommodates_integer(-1));
|
||||||
|
assert!(!ValueType::Boolean.accommodates_integer(10));
|
||||||
|
assert!(!ValueType::String.accommodates_integer(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,15 +10,11 @@
|
||||||
|
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use std::collections::{
|
use std::collections::BTreeMap;
|
||||||
BTreeMap,
|
|
||||||
};
|
|
||||||
|
|
||||||
use ::{
|
use core_traits::Entid;
|
||||||
DateTime,
|
|
||||||
Entid,
|
use crate::{DateTime, Utc};
|
||||||
Utc,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A transaction report summarizes an applied transaction.
|
/// A transaction report summarizes an applied transaction.
|
||||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||||
|
|
|
@ -8,849 +8,4 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
use ::std::convert::{
|
|
||||||
AsRef,
|
|
||||||
};
|
|
||||||
|
|
||||||
use ::std::ffi::{
|
|
||||||
CString,
|
|
||||||
};
|
|
||||||
|
|
||||||
use ::std::ops::{
|
|
||||||
Deref,
|
|
||||||
};
|
|
||||||
|
|
||||||
use ::std::os::raw::c_char;
|
|
||||||
|
|
||||||
use ::std::rc::{
|
|
||||||
Rc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use ::std::sync::{
|
|
||||||
Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
use ::enum_set::EnumSet;
|
|
||||||
|
|
||||||
use ::ordered_float::OrderedFloat;
|
|
||||||
|
|
||||||
use ::uuid::Uuid;
|
|
||||||
|
|
||||||
use ::chrono::{
|
|
||||||
DateTime,
|
|
||||||
Timelike, // For truncation.
|
|
||||||
};
|
|
||||||
|
|
||||||
use ::indexmap::{
|
|
||||||
IndexMap,
|
|
||||||
};
|
|
||||||
|
|
||||||
use ::edn::{
|
|
||||||
self,
|
|
||||||
Cloned,
|
|
||||||
FromMicros,
|
|
||||||
FromRc,
|
|
||||||
Keyword,
|
|
||||||
Utc,
|
|
||||||
ValueRc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use ::edn::entities::{
|
|
||||||
AttributePlace,
|
|
||||||
EntidOrIdent,
|
|
||||||
EntityPlace,
|
|
||||||
ValuePlace,
|
|
||||||
TransactableValueMarker,
|
|
||||||
};
|
|
||||||
|
|
||||||
use values;
|
|
||||||
|
|
||||||
/// Represents one entid in the entid space.
|
|
||||||
///
|
|
||||||
/// Per https://www.sqlite.org/datatype3.html (see also http://stackoverflow.com/a/8499544), SQLite
|
|
||||||
/// stores signed integers up to 64 bits in size. Since u32 is not appropriate for our use case, we
|
|
||||||
/// use i64 rather than manually truncating u64 to u63 and casting to i64 throughout the codebase.
|
|
||||||
pub type Entid = i64;
|
|
||||||
|
|
||||||
/// An entid that's either already in the store, or newly allocated to a tempid.
|
|
||||||
/// TODO: we'd like to link this in some way to the lifetime of a particular PartitionMap.
|
|
||||||
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
|
|
||||||
pub struct KnownEntid(pub Entid);
|
|
||||||
|
|
||||||
impl From<KnownEntid> for Entid {
|
|
||||||
fn from(k: KnownEntid) -> Entid {
|
|
||||||
k.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<KnownEntid> for TypedValue {
|
|
||||||
fn from(k: KnownEntid) -> TypedValue {
|
|
||||||
TypedValue::Ref(k.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: TransactableValueMarker> Into<EntityPlace<V>> for KnownEntid {
|
|
||||||
fn into(self) -> EntityPlace<V> {
|
|
||||||
EntityPlace::Entid(EntidOrIdent::Entid(self.0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<AttributePlace> for KnownEntid {
|
|
||||||
fn into(self) -> AttributePlace {
|
|
||||||
AttributePlace::Entid(EntidOrIdent::Entid(self.0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: TransactableValueMarker> Into<ValuePlace<V>> for KnownEntid {
|
|
||||||
fn into(self) -> ValuePlace<V> {
|
|
||||||
ValuePlace::Entid(EntidOrIdent::Entid(self.0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The attribute of each Mentat assertion has a :db/valueType constraining the value to a
|
|
||||||
/// particular set. Mentat recognizes the following :db/valueType values.
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
|
||||||
#[repr(u32)]
|
|
||||||
pub enum ValueType {
|
|
||||||
Ref,
|
|
||||||
Boolean,
|
|
||||||
Instant,
|
|
||||||
Long,
|
|
||||||
Double,
|
|
||||||
String,
|
|
||||||
Keyword,
|
|
||||||
Uuid,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type ValueTypeTag = i32;
|
pub type ValueTypeTag = i32;
|
||||||
|
|
||||||
impl ValueType {
|
|
||||||
pub fn all_enums() -> EnumSet<ValueType> {
|
|
||||||
// TODO: lazy_static.
|
|
||||||
let mut s = EnumSet::new();
|
|
||||||
s.insert(ValueType::Ref);
|
|
||||||
s.insert(ValueType::Boolean);
|
|
||||||
s.insert(ValueType::Instant);
|
|
||||||
s.insert(ValueType::Long);
|
|
||||||
s.insert(ValueType::Double);
|
|
||||||
s.insert(ValueType::String);
|
|
||||||
s.insert(ValueType::Keyword);
|
|
||||||
s.insert(ValueType::Uuid);
|
|
||||||
s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl ::enum_set::CLike for ValueType {
|
|
||||||
fn to_u32(&self) -> u32 {
|
|
||||||
*self as u32
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn from_u32(v: u32) -> ValueType {
|
|
||||||
::std::mem::transmute(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ValueType {
|
|
||||||
pub fn into_keyword(self) -> Keyword {
|
|
||||||
Keyword::namespaced("db.type", match self {
|
|
||||||
ValueType::Ref => "ref",
|
|
||||||
ValueType::Boolean => "boolean",
|
|
||||||
ValueType::Instant => "instant",
|
|
||||||
ValueType::Long => "long",
|
|
||||||
ValueType::Double => "double",
|
|
||||||
ValueType::String => "string",
|
|
||||||
ValueType::Keyword => "keyword",
|
|
||||||
ValueType::Uuid => "uuid",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_keyword(keyword: &Keyword) -> Option<Self> {
|
|
||||||
if keyword.namespace() != Some("db.type") {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
return match keyword.name() {
|
|
||||||
"ref" => Some(ValueType::Ref),
|
|
||||||
"boolean" => Some(ValueType::Boolean),
|
|
||||||
"instant" => Some(ValueType::Instant),
|
|
||||||
"long" => Some(ValueType::Long),
|
|
||||||
"double" => Some(ValueType::Double),
|
|
||||||
"string" => Some(ValueType::String),
|
|
||||||
"keyword" => Some(ValueType::Keyword),
|
|
||||||
"uuid" => Some(ValueType::Uuid),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_typed_value(self) -> TypedValue {
|
|
||||||
TypedValue::typed_ns_keyword("db.type", match self {
|
|
||||||
ValueType::Ref => "ref",
|
|
||||||
ValueType::Boolean => "boolean",
|
|
||||||
ValueType::Instant => "instant",
|
|
||||||
ValueType::Long => "long",
|
|
||||||
ValueType::Double => "double",
|
|
||||||
ValueType::String => "string",
|
|
||||||
ValueType::Keyword => "keyword",
|
|
||||||
ValueType::Uuid => "uuid",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_edn_value(self) -> edn::Value {
|
|
||||||
match self {
|
|
||||||
ValueType::Ref => values::DB_TYPE_REF.clone(),
|
|
||||||
ValueType::Boolean => values::DB_TYPE_BOOLEAN.clone(),
|
|
||||||
ValueType::Instant => values::DB_TYPE_INSTANT.clone(),
|
|
||||||
ValueType::Long => values::DB_TYPE_LONG.clone(),
|
|
||||||
ValueType::Double => values::DB_TYPE_DOUBLE.clone(),
|
|
||||||
ValueType::String => values::DB_TYPE_STRING.clone(),
|
|
||||||
ValueType::Keyword => values::DB_TYPE_KEYWORD.clone(),
|
|
||||||
ValueType::Uuid => values::DB_TYPE_UUID.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_numeric(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
&ValueType::Long | &ValueType::Double => true,
|
|
||||||
_ => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for ValueType {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "{}", match *self {
|
|
||||||
ValueType::Ref => ":db.type/ref",
|
|
||||||
ValueType::Boolean => ":db.type/boolean",
|
|
||||||
ValueType::Instant => ":db.type/instant",
|
|
||||||
ValueType::Long => ":db.type/long",
|
|
||||||
ValueType::Double => ":db.type/double",
|
|
||||||
ValueType::String => ":db.type/string",
|
|
||||||
ValueType::Keyword => ":db.type/keyword",
|
|
||||||
ValueType::Uuid => ":db.type/uuid",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a value that can be stored in a Mentat store.
|
|
||||||
// TODO: expand to include :db.type/uri. https://github.com/mozilla/mentat/issues/201
|
|
||||||
// TODO: JSON data type? https://github.com/mozilla/mentat/issues/31
|
|
||||||
// TODO: BigInt? Bytes?
|
|
||||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub enum TypedValue {
|
|
||||||
Ref(Entid),
|
|
||||||
Boolean(bool),
|
|
||||||
Long(i64),
|
|
||||||
Double(OrderedFloat<f64>),
|
|
||||||
Instant(DateTime<Utc>), // Use `into()` to ensure truncation.
|
|
||||||
// TODO: &str throughout?
|
|
||||||
String(ValueRc<String>),
|
|
||||||
Keyword(ValueRc<Keyword>),
|
|
||||||
Uuid(Uuid), // It's only 128 bits, so this should be acceptable to clone.
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `TypedValue` is the value type for programmatic use in transaction builders.
|
|
||||||
impl TransactableValueMarker for TypedValue {}
|
|
||||||
|
|
||||||
/// The values bound in a query specification can be:
|
|
||||||
///
|
|
||||||
/// * Vecs of structured values, for multi-valued component attributes or nested expressions.
|
|
||||||
/// * Single structured values, for single-valued component attributes or nested expressions.
|
|
||||||
/// * Single typed values, for simple attributes.
|
|
||||||
///
|
|
||||||
/// The `Binding` enum defines these three options.
|
|
||||||
///
|
|
||||||
/// Datomic also supports structured inputs; at present Mentat does not, but this type
|
|
||||||
/// would also serve that purpose.
|
|
||||||
///
|
|
||||||
/// Note that maps are not ordered, and so `Binding` is neither `Ord` nor `PartialOrd`.
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub enum Binding {
|
|
||||||
Scalar(TypedValue),
|
|
||||||
Vec(ValueRc<Vec<Binding>>),
|
|
||||||
Map(ValueRc<StructuredMap>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> From<T> for Binding where T: Into<TypedValue> {
|
|
||||||
fn from(value: T) -> Self {
|
|
||||||
Binding::Scalar(value.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<StructuredMap> for Binding {
|
|
||||||
fn from(value: StructuredMap) -> Self {
|
|
||||||
Binding::Map(ValueRc::new(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Vec<Binding>> for Binding {
|
|
||||||
fn from(value: Vec<Binding>) -> Self {
|
|
||||||
Binding::Vec(ValueRc::new(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Binding {
|
|
||||||
pub fn into_scalar(self) -> Option<TypedValue> {
|
|
||||||
match self {
|
|
||||||
Binding::Scalar(v) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_vec(self) -> Option<ValueRc<Vec<Binding>>> {
|
|
||||||
match self {
|
|
||||||
Binding::Vec(v) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_map(self) -> Option<ValueRc<StructuredMap>> {
|
|
||||||
match self {
|
|
||||||
Binding::Map(v) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_scalar(&self) -> Option<&TypedValue> {
|
|
||||||
match self {
|
|
||||||
&Binding::Scalar(ref v) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_vec(&self) -> Option<&Vec<Binding>> {
|
|
||||||
match self {
|
|
||||||
&Binding::Vec(ref v) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_map(&self) -> Option<&StructuredMap> {
|
|
||||||
match self {
|
|
||||||
&Binding::Map(ref v) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A pull expression expands a binding into a structure. The returned structure
|
|
||||||
/// associates attributes named in the input or retrieved from the store with values.
|
|
||||||
/// This association is a `StructuredMap`.
|
|
||||||
///
|
|
||||||
/// Note that 'attributes' in Datomic's case can mean:
|
|
||||||
/// - Reversed attribute keywords (:artist/_country).
|
|
||||||
/// - An alias using `:as` (:artist/name :as "Band name").
|
|
||||||
///
|
|
||||||
/// We entirely support the former, and partially support the latter -- you can alias
|
|
||||||
/// using a different keyword only.
|
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
|
||||||
pub struct StructuredMap(pub IndexMap<ValueRc<Keyword>, Binding>);
|
|
||||||
|
|
||||||
impl Deref for StructuredMap {
|
|
||||||
type Target = IndexMap<ValueRc<Keyword>, Binding>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StructuredMap {
|
|
||||||
pub fn insert<N, B>(&mut self, name: N, value: B) where N: Into<ValueRc<Keyword>>, B: Into<Binding> {
|
|
||||||
self.0.insert(name.into(), value.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<IndexMap<ValueRc<Keyword>, Binding>> for StructuredMap {
|
|
||||||
fn from(src: IndexMap<ValueRc<Keyword>, Binding>) -> Self {
|
|
||||||
StructuredMap(src)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mostly for testing.
|
|
||||||
impl<T> From<Vec<(Keyword, T)>> for StructuredMap where T: Into<Binding> {
|
|
||||||
fn from(value: Vec<(Keyword, T)>) -> Self {
|
|
||||||
let mut sm = StructuredMap::default();
|
|
||||||
for (k, v) in value.into_iter() {
|
|
||||||
sm.insert(k, v);
|
|
||||||
}
|
|
||||||
sm
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Binding {
|
|
||||||
/// Returns true if the provided type is `Some` and matches this value's type, or if the
|
|
||||||
/// provided type is `None`.
|
|
||||||
#[inline]
|
|
||||||
pub fn is_congruent_with<T: Into<Option<ValueType>>>(&self, t: T) -> bool {
|
|
||||||
t.into().map_or(true, |x| self.matches_type(x))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn matches_type(&self, t: ValueType) -> bool {
|
|
||||||
self.value_type() == Some(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn value_type(&self) -> Option<ValueType> {
|
|
||||||
match self {
|
|
||||||
&Binding::Scalar(ref v) => Some(v.value_type()),
|
|
||||||
|
|
||||||
&Binding::Map(_) => None,
|
|
||||||
&Binding::Vec(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl TypedValue {
|
|
||||||
/// Returns true if the provided type is `Some` and matches this value's type, or if the
|
|
||||||
/// provided type is `None`.
|
|
||||||
#[inline]
|
|
||||||
pub fn is_congruent_with<T: Into<Option<ValueType>>>(&self, t: T) -> bool {
|
|
||||||
t.into().map_or(true, |x| self.matches_type(x))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn matches_type(&self, t: ValueType) -> bool {
|
|
||||||
self.value_type() == t
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn value_type(&self) -> ValueType {
|
|
||||||
match self {
|
|
||||||
&TypedValue::Ref(_) => ValueType::Ref,
|
|
||||||
&TypedValue::Boolean(_) => ValueType::Boolean,
|
|
||||||
&TypedValue::Long(_) => ValueType::Long,
|
|
||||||
&TypedValue::Instant(_) => ValueType::Instant,
|
|
||||||
&TypedValue::Double(_) => ValueType::Double,
|
|
||||||
&TypedValue::String(_) => ValueType::String,
|
|
||||||
&TypedValue::Keyword(_) => ValueType::Keyword,
|
|
||||||
&TypedValue::Uuid(_) => ValueType::Uuid,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Construct a new `TypedValue::Keyword` instance by cloning the provided
|
|
||||||
/// values and wrapping them in a new `ValueRc`. This is expensive, so this might
|
|
||||||
/// be best limited to tests.
|
|
||||||
pub fn typed_ns_keyword<S: AsRef<str>, T: AsRef<str>>(ns: S, name: T) -> TypedValue {
|
|
||||||
Keyword::namespaced(ns.as_ref(), name.as_ref()).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Construct a new `TypedValue::String` instance by cloning the provided
|
|
||||||
/// value and wrapping it in a new `ValueRc`. This is expensive, so this might
|
|
||||||
/// be best limited to tests.
|
|
||||||
pub fn typed_string<S: AsRef<str>>(s: S) -> TypedValue {
|
|
||||||
s.as_ref().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn current_instant() -> TypedValue {
|
|
||||||
Utc::now().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Construct a new `TypedValue::Instant` instance from the provided
|
|
||||||
/// microsecond timestamp.
|
|
||||||
pub fn instant(micros: i64) -> TypedValue {
|
|
||||||
DateTime::<Utc>::from_micros(micros).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait MicrosecondPrecision {
|
|
||||||
/// Truncate the provided `DateTime` to microsecond precision.
|
|
||||||
fn microsecond_precision(self) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MicrosecondPrecision for DateTime<Utc> {
|
|
||||||
fn microsecond_precision(self) -> DateTime<Utc> {
|
|
||||||
let nanoseconds = self.nanosecond();
|
|
||||||
if nanoseconds % 1000 == 0 {
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
let microseconds = nanoseconds / 1000;
|
|
||||||
let truncated = microseconds * 1000;
|
|
||||||
self.with_nanosecond(truncated).expect("valid timestamp")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the current time as a UTC `DateTime` instance with microsecond precision.
|
|
||||||
pub fn now() -> DateTime<Utc> {
|
|
||||||
Utc::now().microsecond_precision()
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't do From<i64> or From<Entid> 'cos it's ambiguous.
|
|
||||||
|
|
||||||
impl From<bool> for TypedValue {
|
|
||||||
fn from(value: bool) -> TypedValue {
|
|
||||||
TypedValue::Boolean(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Truncate the provided `DateTime` to microsecond precision, and return the corresponding
|
|
||||||
/// `TypedValue::Instant`.
|
|
||||||
impl From<DateTime<Utc>> for TypedValue {
|
|
||||||
fn from(value: DateTime<Utc>) -> TypedValue {
|
|
||||||
TypedValue::Instant(value.microsecond_precision())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Uuid> for TypedValue {
|
|
||||||
fn from(value: Uuid) -> TypedValue {
|
|
||||||
TypedValue::Uuid(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<&'a str> for TypedValue {
|
|
||||||
fn from(value: &'a str) -> TypedValue {
|
|
||||||
TypedValue::String(ValueRc::new(value.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Arc<String>> for TypedValue {
|
|
||||||
fn from(value: Arc<String>) -> TypedValue {
|
|
||||||
TypedValue::String(ValueRc::from_arc(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Rc<String>> for TypedValue {
|
|
||||||
fn from(value: Rc<String>) -> TypedValue {
|
|
||||||
TypedValue::String(ValueRc::from_rc(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Box<String>> for TypedValue {
|
|
||||||
fn from(value: Box<String>) -> TypedValue {
|
|
||||||
TypedValue::String(ValueRc::new(*value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for TypedValue {
|
|
||||||
fn from(value: String) -> TypedValue {
|
|
||||||
TypedValue::String(ValueRc::new(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Arc<Keyword>> for TypedValue {
|
|
||||||
fn from(value: Arc<Keyword>) -> TypedValue {
|
|
||||||
TypedValue::Keyword(ValueRc::from_arc(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Rc<Keyword>> for TypedValue {
|
|
||||||
fn from(value: Rc<Keyword>) -> TypedValue {
|
|
||||||
TypedValue::Keyword(ValueRc::from_rc(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Keyword> for TypedValue {
|
|
||||||
fn from(value: Keyword) -> TypedValue {
|
|
||||||
TypedValue::Keyword(ValueRc::new(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u32> for TypedValue {
|
|
||||||
fn from(value: u32) -> TypedValue {
|
|
||||||
TypedValue::Long(value as i64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<i32> for TypedValue {
|
|
||||||
fn from(value: i32) -> TypedValue {
|
|
||||||
TypedValue::Long(value as i64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<f64> for TypedValue {
|
|
||||||
fn from(value: f64) -> TypedValue {
|
|
||||||
TypedValue::Double(OrderedFloat(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TypedValue {
|
|
||||||
pub fn into_known_entid(self) -> Option<KnownEntid> {
|
|
||||||
match self {
|
|
||||||
TypedValue::Ref(v) => Some(KnownEntid(v)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_entid(self) -> Option<Entid> {
|
|
||||||
match self {
|
|
||||||
TypedValue::Ref(v) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_kw(self) -> Option<ValueRc<Keyword>> {
|
|
||||||
match self {
|
|
||||||
TypedValue::Keyword(v) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_boolean(self) -> Option<bool> {
|
|
||||||
match self {
|
|
||||||
TypedValue::Boolean(v) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_long(self) -> Option<i64> {
|
|
||||||
match self {
|
|
||||||
TypedValue::Long(v) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_double(self) -> Option<f64> {
|
|
||||||
match self {
|
|
||||||
TypedValue::Double(v) => Some(v.into_inner()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_instant(self) -> Option<DateTime<Utc>> {
|
|
||||||
match self {
|
|
||||||
TypedValue::Instant(v) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_timestamp(self) -> Option<i64> {
|
|
||||||
match self {
|
|
||||||
TypedValue::Instant(v) => Some(v.timestamp()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_string(self) -> Option<ValueRc<String>> {
|
|
||||||
match self {
|
|
||||||
TypedValue::String(v) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_c_string(self) -> Option<*mut c_char> {
|
|
||||||
match self {
|
|
||||||
TypedValue::String(v) => {
|
|
||||||
// Get an independent copy of the string.
|
|
||||||
let s: String = v.cloned();
|
|
||||||
|
|
||||||
// Make a CString out of the new bytes.
|
|
||||||
let c: CString = CString::new(s).expect("String conversion failed!");
|
|
||||||
|
|
||||||
// Return a C-owned pointer.
|
|
||||||
Some(c.into_raw())
|
|
||||||
},
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_kw_c_string(self) -> Option<*mut c_char> {
|
|
||||||
match self {
|
|
||||||
TypedValue::Keyword(v) => {
|
|
||||||
// Get an independent copy of the string.
|
|
||||||
let s: String = v.to_string();
|
|
||||||
|
|
||||||
// Make a CString out of the new bytes.
|
|
||||||
let c: CString = CString::new(s).expect("String conversion failed!");
|
|
||||||
|
|
||||||
// Return a C-owned pointer.
|
|
||||||
Some(c.into_raw())
|
|
||||||
},
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_uuid_c_string(self) -> Option<*mut c_char> {
|
|
||||||
match self {
|
|
||||||
TypedValue::Uuid(v) => {
|
|
||||||
// Get an independent copy of the string.
|
|
||||||
let s: String = v.hyphenated().to_string();
|
|
||||||
|
|
||||||
// Make a CString out of the new bytes.
|
|
||||||
let c: CString = CString::new(s).expect("String conversion failed!");
|
|
||||||
|
|
||||||
// Return a C-owned pointer.
|
|
||||||
Some(c.into_raw())
|
|
||||||
},
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_uuid(self) -> Option<Uuid> {
|
|
||||||
match self {
|
|
||||||
TypedValue::Uuid(v) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_uuid_string(self) -> Option<String> {
|
|
||||||
match self {
|
|
||||||
TypedValue::Uuid(v) => Some(v.hyphenated().to_string()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Binding {
|
|
||||||
pub fn into_known_entid(self) -> Option<KnownEntid> {
|
|
||||||
match self {
|
|
||||||
Binding::Scalar(TypedValue::Ref(v)) => Some(KnownEntid(v)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_entid(self) -> Option<Entid> {
|
|
||||||
match self {
|
|
||||||
Binding::Scalar(TypedValue::Ref(v)) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_kw(self) -> Option<ValueRc<Keyword>> {
|
|
||||||
match self {
|
|
||||||
Binding::Scalar(TypedValue::Keyword(v)) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_boolean(self) -> Option<bool> {
|
|
||||||
match self {
|
|
||||||
Binding::Scalar(TypedValue::Boolean(v)) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_long(self) -> Option<i64> {
|
|
||||||
match self {
|
|
||||||
Binding::Scalar(TypedValue::Long(v)) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_double(self) -> Option<f64> {
|
|
||||||
match self {
|
|
||||||
Binding::Scalar(TypedValue::Double(v)) => Some(v.into_inner()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_instant(self) -> Option<DateTime<Utc>> {
|
|
||||||
match self {
|
|
||||||
Binding::Scalar(TypedValue::Instant(v)) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_timestamp(self) -> Option<i64> {
|
|
||||||
match self {
|
|
||||||
Binding::Scalar(TypedValue::Instant(v)) => Some(v.timestamp()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_string(self) -> Option<ValueRc<String>> {
|
|
||||||
match self {
|
|
||||||
Binding::Scalar(TypedValue::String(v)) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_uuid(self) -> Option<Uuid> {
|
|
||||||
match self {
|
|
||||||
Binding::Scalar(TypedValue::Uuid(v)) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_uuid_string(self) -> Option<String> {
|
|
||||||
match self {
|
|
||||||
Binding::Scalar(TypedValue::Uuid(v)) => Some(v.hyphenated().to_string()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_c_string(self) -> Option<*mut c_char> {
|
|
||||||
match self {
|
|
||||||
Binding::Scalar(v) => v.into_c_string(),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_kw_c_string(self) -> Option<*mut c_char> {
|
|
||||||
match self {
|
|
||||||
Binding::Scalar(v) => v.into_kw_c_string(),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_uuid_c_string(self) -> Option<*mut c_char> {
|
|
||||||
match self {
|
|
||||||
Binding::Scalar(v) => v.into_uuid_c_string(),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_entid(&self) -> Option<&Entid> {
|
|
||||||
match self {
|
|
||||||
&Binding::Scalar(TypedValue::Ref(ref v)) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_kw(&self) -> Option<&ValueRc<Keyword>> {
|
|
||||||
match self {
|
|
||||||
&Binding::Scalar(TypedValue::Keyword(ref v)) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_boolean(&self) -> Option<&bool> {
|
|
||||||
match self {
|
|
||||||
&Binding::Scalar(TypedValue::Boolean(ref v)) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_long(&self) -> Option<&i64> {
|
|
||||||
match self {
|
|
||||||
&Binding::Scalar(TypedValue::Long(ref v)) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_double(&self) -> Option<&f64> {
|
|
||||||
match self {
|
|
||||||
&Binding::Scalar(TypedValue::Double(ref v)) => Some(&v.0),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_instant(&self) -> Option<&DateTime<Utc>> {
|
|
||||||
match self {
|
|
||||||
&Binding::Scalar(TypedValue::Instant(ref v)) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_string(&self) -> Option<&ValueRc<String>> {
|
|
||||||
match self {
|
|
||||||
&Binding::Scalar(TypedValue::String(ref v)) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_uuid(&self) -> Option<&Uuid> {
|
|
||||||
match self {
|
|
||||||
&Binding::Scalar(TypedValue::Uuid(ref v)) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_typed_value() {
|
|
||||||
assert!(TypedValue::Boolean(false).is_congruent_with(None));
|
|
||||||
assert!(TypedValue::Boolean(false).is_congruent_with(ValueType::Boolean));
|
|
||||||
assert!(!TypedValue::typed_string("foo").is_congruent_with(ValueType::Boolean));
|
|
||||||
assert!(TypedValue::typed_string("foo").is_congruent_with(ValueType::String));
|
|
||||||
assert!(TypedValue::typed_string("foo").is_congruent_with(None));
|
|
||||||
}
|
|
||||||
|
|
|
@ -67,7 +67,8 @@ pub enum Either<L, R> {
|
||||||
// Cribbed from https://github.com/bluss/either/blob/f793721f3fdeb694f009e731b23a2858286bc0d6/src/lib.rs#L219-L259.
|
// Cribbed from https://github.com/bluss/either/blob/f793721f3fdeb694f009e731b23a2858286bc0d6/src/lib.rs#L219-L259.
|
||||||
impl<L, R> Either<L, R> {
|
impl<L, R> Either<L, R> {
|
||||||
pub fn map_left<F, M>(self, f: F) -> Either<M, R>
|
pub fn map_left<F, M>(self, f: F) -> Either<M, R>
|
||||||
where F: FnOnce(L) -> M
|
where
|
||||||
|
F: FnOnce(L) -> M,
|
||||||
{
|
{
|
||||||
use self::Either::*;
|
use self::Either::*;
|
||||||
match self {
|
match self {
|
||||||
|
@ -77,7 +78,8 @@ impl<L, R> Either<L, R> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn map_right<F, S>(self, f: F) -> Either<L, S>
|
pub fn map_right<F, S>(self, f: F) -> Either<L, S>
|
||||||
where F: FnOnce(R) -> S
|
where
|
||||||
|
F: FnOnce(R) -> S,
|
||||||
{
|
{
|
||||||
use self::Either::*;
|
use self::Either::*;
|
||||||
match self {
|
match self {
|
||||||
|
|
25
db-traits/Cargo.toml
Normal file
25
db-traits/Cargo.toml
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
[package]
|
||||||
|
name = "db_traits"
|
||||||
|
version = "0.0.2"
|
||||||
|
workspace = ".."
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "db_traits"
|
||||||
|
path = "lib.rs"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
sqlcipher = ["rusqlite/sqlcipher"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
failure = "~0.1"
|
||||||
|
failure_derive = "~0.1"
|
||||||
|
|
||||||
|
[dependencies.edn]
|
||||||
|
path = "../edn"
|
||||||
|
|
||||||
|
[dependencies.core_traits]
|
||||||
|
path = "../core-traits"
|
||||||
|
|
||||||
|
[dependencies.rusqlite]
|
||||||
|
version = "~0.29"
|
||||||
|
features = ["limits", "bundled"]
|
|
@ -10,37 +10,15 @@
|
||||||
|
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use failure::{
|
use failure::{Backtrace, Context, Fail};
|
||||||
Backtrace,
|
|
||||||
Context,
|
|
||||||
Fail,
|
|
||||||
};
|
|
||||||
|
|
||||||
use std::collections::{
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
BTreeMap,
|
|
||||||
BTreeSet,
|
|
||||||
};
|
|
||||||
|
|
||||||
use rusqlite;
|
use rusqlite;
|
||||||
|
|
||||||
use edn::entities::{
|
use edn::entities::TempId;
|
||||||
TempId,
|
|
||||||
};
|
|
||||||
use mentat_core::{
|
|
||||||
KnownEntid,
|
|
||||||
};
|
|
||||||
use types::{
|
|
||||||
Entid,
|
|
||||||
TypedValue,
|
|
||||||
ValueType,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[macro_export]
|
use core_traits::{Entid, KnownEntid, TypedValue, ValueType};
|
||||||
macro_rules! bail {
|
|
||||||
($e:expr) => (
|
|
||||||
return Err($e.into());
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Result<T> = ::std::result::Result<T, DbError>;
|
pub type Result<T> = ::std::result::Result<T, DbError>;
|
||||||
|
|
||||||
|
@ -80,40 +58,46 @@ pub enum SchemaConstraintViolation {
|
||||||
/// A transaction tried to assert a datom or datoms with the wrong value `v` type(s).
|
/// A transaction tried to assert a datom or datoms with the wrong value `v` type(s).
|
||||||
TypeDisagreements {
|
TypeDisagreements {
|
||||||
/// The key (`[e a v]`) has an invalid value `v`: it is not of the expected value type.
|
/// The key (`[e a v]`) has an invalid value `v`: it is not of the expected value type.
|
||||||
conflicting_datoms: BTreeMap<(Entid, Entid, TypedValue), ValueType>
|
conflicting_datoms: BTreeMap<(Entid, Entid, TypedValue), ValueType>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// A transaction tried to assert datoms that don't observe the schema's cardinality constraints.
|
/// A transaction tried to assert datoms that don't observe the schema's cardinality constraints.
|
||||||
CardinalityConflicts {
|
CardinalityConflicts { conflicts: Vec<CardinalityConflict> },
|
||||||
conflicts: Vec<CardinalityConflict>,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ::std::fmt::Display for SchemaConstraintViolation {
|
impl ::std::fmt::Display for SchemaConstraintViolation {
|
||||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||||
use self::SchemaConstraintViolation::*;
|
use self::SchemaConstraintViolation::*;
|
||||||
match self {
|
match self {
|
||||||
&ConflictingUpserts { ref conflicting_upserts } => {
|
ConflictingUpserts {
|
||||||
|
ref conflicting_upserts,
|
||||||
|
} => {
|
||||||
writeln!(f, "conflicting upserts:")?;
|
writeln!(f, "conflicting upserts:")?;
|
||||||
for (tempid, entids) in conflicting_upserts {
|
for (tempid, entids) in conflicting_upserts {
|
||||||
writeln!(f, " tempid {:?} upserts to {:?}", tempid, entids)?;
|
writeln!(f, " tempid {:?} upserts to {:?}", tempid, entids)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
&TypeDisagreements { ref conflicting_datoms } => {
|
TypeDisagreements {
|
||||||
|
ref conflicting_datoms,
|
||||||
|
} => {
|
||||||
writeln!(f, "type disagreements:")?;
|
writeln!(f, "type disagreements:")?;
|
||||||
for (ref datom, expected_type) in conflicting_datoms {
|
for (ref datom, expected_type) in conflicting_datoms {
|
||||||
writeln!(f, " expected value of type {} but got datom [{} {} {:?}]", expected_type, datom.0, datom.1, datom.2)?;
|
writeln!(
|
||||||
|
f,
|
||||||
|
" expected value of type {} but got datom [{} {} {:?}]",
|
||||||
|
expected_type, datom.0, datom.1, datom.2
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
&CardinalityConflicts { ref conflicts } => {
|
CardinalityConflicts { ref conflicts } => {
|
||||||
writeln!(f, "cardinality conflicts:")?;
|
writeln!(f, "cardinality conflicts:")?;
|
||||||
for ref conflict in conflicts {
|
for conflict in conflicts {
|
||||||
writeln!(f, " {:?}", conflict)?;
|
writeln!(f, " {:?}", conflict)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,12 +116,12 @@ impl ::std::fmt::Display for InputError {
|
||||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||||
use self::InputError::*;
|
use self::InputError::*;
|
||||||
match self {
|
match self {
|
||||||
&BadDbId => {
|
BadDbId => {
|
||||||
writeln!(f, ":db/id in map notation must either not be present or be an entid, an ident, or a tempid")
|
writeln!(f, ":db/id in map notation must either not be present or be an entid, an ident, or a tempid")
|
||||||
},
|
}
|
||||||
&BadEntityPlace => {
|
BadEntityPlace => {
|
||||||
writeln!(f, "cannot convert value place into entity place")
|
writeln!(f, "cannot convert value place into entity place")
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,7 +138,7 @@ impl ::std::fmt::Display for DbError {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fail for DbError {
|
impl Fail for DbError {
|
||||||
fn cause(&self) -> Option<&Fail> {
|
fn cause(&self) -> Option<&dyn Fail> {
|
||||||
self.inner.cause()
|
self.inner.cause()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,32 +154,38 @@ impl DbError {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<DbErrorKind> for DbError {
|
impl From<DbErrorKind> for DbError {
|
||||||
fn from(kind: DbErrorKind) -> DbError {
|
fn from(kind: DbErrorKind) -> Self {
|
||||||
DbError { inner: Context::new(kind) }
|
DbError {
|
||||||
|
inner: Context::new(kind),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Context<DbErrorKind>> for DbError {
|
impl From<Context<DbErrorKind>> for DbError {
|
||||||
fn from(inner: Context<DbErrorKind>) -> DbError {
|
fn from(inner: Context<DbErrorKind>) -> Self {
|
||||||
DbError { inner: inner }
|
DbError { inner }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<rusqlite::Error> for DbError {
|
impl From<rusqlite::Error> for DbError {
|
||||||
fn from(error: rusqlite::Error) -> DbError {
|
fn from(error: rusqlite::Error) -> Self {
|
||||||
DbError { inner: Context::new(DbErrorKind::RusqliteError(error.to_string())) }
|
DbError {
|
||||||
|
inner: Context::new(DbErrorKind::RusqliteError(error.to_string())),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug, Fail)]
|
#[derive(Clone, PartialEq, Debug, Fail)]
|
||||||
pub enum DbErrorKind {
|
pub enum DbErrorKind {
|
||||||
/// We're just not done yet. Message that the feature is recognized but not yet
|
/// We're just not done yet. Recognized a feature that is not yet implemented.
|
||||||
/// implemented.
|
|
||||||
#[fail(display = "not yet implemented: {}", _0)]
|
#[fail(display = "not yet implemented: {}", _0)]
|
||||||
NotYetImplemented(String),
|
NotYetImplemented(String),
|
||||||
|
|
||||||
/// We've been given a value that isn't the correct Mentat type.
|
/// We've been given a value that isn't the correct Mentat type.
|
||||||
#[fail(display = "value '{}' is not the expected Mentat value type {:?}", _0, _1)]
|
#[fail(
|
||||||
|
display = "value '{}' is not the expected Mentat value type {:?}",
|
||||||
|
_0, _1
|
||||||
|
)]
|
||||||
BadValuePair(String, ValueType),
|
BadValuePair(String, ValueType),
|
||||||
|
|
||||||
/// We've got corrupt data in the SQL store: a value and value_type_tag don't line up.
|
/// We've got corrupt data in the SQL store: a value and value_type_tag don't line up.
|
||||||
|
@ -203,11 +193,10 @@ pub enum DbErrorKind {
|
||||||
#[fail(display = "bad SQL (value_type_tag, value) pair: ({:?}, {:?})", _0, _1)]
|
#[fail(display = "bad SQL (value_type_tag, value) pair: ({:?}, {:?})", _0, _1)]
|
||||||
BadSQLValuePair(rusqlite::types::Value, i32),
|
BadSQLValuePair(rusqlite::types::Value, i32),
|
||||||
|
|
||||||
// /// The SQLite store user_version isn't recognized. This could be an old version of Mentat
|
/// The SQLite store user_version isn't recognized. This could be an old version of Mentat
|
||||||
// /// trying to open a newer version SQLite store; or it could be a corrupt file; or ...
|
/// trying to open a newer version SQLite store; or it could be a corrupt file; or ...
|
||||||
// #[fail(display = "bad SQL store user_version: {}", _0)]
|
/// #[fail(display = "bad SQL store user_version: {}", _0)]
|
||||||
// BadSQLiteStoreVersion(i32),
|
/// BadSQLiteStoreVersion(i32),
|
||||||
|
|
||||||
/// A bootstrap definition couldn't be parsed or installed. This is a programmer error, not
|
/// A bootstrap definition couldn't be parsed or installed. This is a programmer error, not
|
||||||
/// a runtime error.
|
/// a runtime error.
|
||||||
#[fail(display = "bad bootstrap definition: {}", _0)]
|
#[fail(display = "bad bootstrap definition: {}", _0)]
|
||||||
|
@ -222,11 +211,13 @@ pub enum DbErrorKind {
|
||||||
UnrecognizedIdent(String),
|
UnrecognizedIdent(String),
|
||||||
|
|
||||||
/// An entid->ident mapping failed.
|
/// An entid->ident mapping failed.
|
||||||
/// We also use this error if you try to transact an entid that we didn't allocate,
|
#[fail(display = "no ident found for entid: {}", _0)]
|
||||||
/// in part because we blow the stack in error_chain if we define a new enum!
|
|
||||||
#[fail(display = "unrecognized or no ident found for entid: {}", _0)]
|
|
||||||
UnrecognizedEntid(Entid),
|
UnrecognizedEntid(Entid),
|
||||||
|
|
||||||
|
/// Tried to transact an entid that isn't allocated.
|
||||||
|
#[fail(display = "entid not allocated: {}", _0)]
|
||||||
|
UnallocatedEntid(Entid),
|
||||||
|
|
||||||
#[fail(display = "unknown attribute for entid: {}", _0)]
|
#[fail(display = "unknown attribute for entid: {}", _0)]
|
||||||
UnknownAttribute(Entid),
|
UnknownAttribute(Entid),
|
||||||
|
|
||||||
|
@ -245,7 +236,9 @@ pub enum DbErrorKind {
|
||||||
#[fail(display = "transaction input error: {}", _0)]
|
#[fail(display = "transaction input error: {}", _0)]
|
||||||
InputError(InputError),
|
InputError(InputError),
|
||||||
|
|
||||||
#[fail(display = "Cannot transact a fulltext assertion with a typed value that is not :db/valueType :db.type/string")]
|
#[fail(
|
||||||
|
display = "Cannot transact a fulltext assertion with a typed value that is not :db/valueType :db.type/string"
|
||||||
|
)]
|
||||||
WrongTypeValueForFtsAssertion,
|
WrongTypeValueForFtsAssertion,
|
||||||
|
|
||||||
// SQL errors.
|
// SQL errors.
|
18
db-traits/lib.rs
Normal file
18
db-traits/lib.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright 2018 Mozilla
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||||
|
// this file except in compliance with the License. You may obtain a copy of the
|
||||||
|
// License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
// Unless required by applicable law or agreed to in writing, software distributed
|
||||||
|
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||||
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
|
extern crate failure;
|
||||||
|
extern crate failure_derive;
|
||||||
|
extern crate rusqlite;
|
||||||
|
|
||||||
|
extern crate core_traits;
|
||||||
|
extern crate edn;
|
||||||
|
|
||||||
|
pub mod errors;
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "mentat_db"
|
name = "mentat_db"
|
||||||
version = "0.0.1"
|
version = "0.0.2"
|
||||||
workspace = ".."
|
workspace = ".."
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
@ -9,22 +9,21 @@ sqlcipher = ["rusqlite/sqlcipher"]
|
||||||
syncable = ["serde", "serde_json", "serde_derive"]
|
syncable = ["serde", "serde_json", "serde_derive"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
failure = "0.1.1"
|
failure = "~0.1"
|
||||||
failure_derive = "0.1.1"
|
indexmap = "~1.9"
|
||||||
indexmap = "1"
|
itertools = "~0.10"
|
||||||
itertools = "0.7"
|
lazy_static = "~1.4"
|
||||||
lazy_static = "0.2"
|
log = "~0.4"
|
||||||
log = "0.4"
|
ordered-float = "~2.8"
|
||||||
ordered-float = "0.5"
|
time = "~0.3"
|
||||||
time = "0.1"
|
petgraph = "~0.6"
|
||||||
petgraph = "0.4.12"
|
serde = { version = "~1.0", optional = true }
|
||||||
serde = { version = "1.0", optional = true }
|
serde_json = { version = "~1.0", optional = true }
|
||||||
serde_json = { version = "1.0", optional = true }
|
serde_derive = { version = "~1.0", optional = true }
|
||||||
serde_derive = { version = "1.0", optional = true }
|
|
||||||
|
|
||||||
[dependencies.rusqlite]
|
[dependencies.rusqlite]
|
||||||
version = "0.13"
|
version = "~0.29"
|
||||||
features = ["limits"]
|
features = ["limits", "bundled"]
|
||||||
|
|
||||||
[dependencies.edn]
|
[dependencies.edn]
|
||||||
path = "../edn"
|
path = "../edn"
|
||||||
|
@ -32,12 +31,19 @@ path = "../edn"
|
||||||
[dependencies.mentat_core]
|
[dependencies.mentat_core]
|
||||||
path = "../core"
|
path = "../core"
|
||||||
|
|
||||||
|
[dependencies.core_traits]
|
||||||
|
path = "../core-traits"
|
||||||
|
|
||||||
|
[dependencies.db_traits]
|
||||||
|
path = "../db-traits"
|
||||||
|
|
||||||
[dependencies.mentat_sql]
|
[dependencies.mentat_sql]
|
||||||
path = "../sql"
|
path = "../sql"
|
||||||
|
|
||||||
# Should be dev-dependencies.
|
# TODO: This should be in dev-dependencies.
|
||||||
[dependencies.tabwriter]
|
[dependencies.tabwriter]
|
||||||
version = "1.0.3"
|
version = "~1.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.5"
|
env_logger = "0.9"
|
||||||
|
#tabwriter = { version = "1.2.1" }
|
||||||
|
|
|
@ -24,7 +24,10 @@ pub struct AddRetractAlterSet<K, V> {
|
||||||
pub altered: BTreeMap<K, (V, V)>,
|
pub altered: BTreeMap<K, (V, V)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, V> Default for AddRetractAlterSet<K, V> where K: Ord {
|
impl<K, V> Default for AddRetractAlterSet<K, V>
|
||||||
|
where
|
||||||
|
K: Ord,
|
||||||
|
{
|
||||||
fn default() -> AddRetractAlterSet<K, V> {
|
fn default() -> AddRetractAlterSet<K, V> {
|
||||||
AddRetractAlterSet {
|
AddRetractAlterSet {
|
||||||
asserted: BTreeMap::default(),
|
asserted: BTreeMap::default(),
|
||||||
|
@ -34,7 +37,10 @@ impl<K, V> Default for AddRetractAlterSet<K, V> where K: Ord {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, V> AddRetractAlterSet<K, V> where K: Ord {
|
impl<K, V> AddRetractAlterSet<K, V>
|
||||||
|
where
|
||||||
|
K: Ord,
|
||||||
|
{
|
||||||
pub fn witness(&mut self, key: K, value: V, added: bool) {
|
pub fn witness(&mut self, key: K, value: V, added: bool) {
|
||||||
if added {
|
if added {
|
||||||
if let Some(retracted_value) = self.retracted.remove(&key) {
|
if let Some(retracted_value) = self.retracted.remove(&key) {
|
||||||
|
@ -42,15 +48,13 @@ impl<K, V> AddRetractAlterSet<K, V> where K: Ord {
|
||||||
} else {
|
} else {
|
||||||
self.asserted.insert(key, value);
|
self.asserted.insert(key, value);
|
||||||
}
|
}
|
||||||
} else {
|
} else if let Some(asserted_value) = self.asserted.remove(&key) {
|
||||||
if let Some(asserted_value) = self.asserted.remove(&key) {
|
|
||||||
self.altered.insert(key, (value, asserted_value));
|
self.altered.insert(key, (value, asserted_value));
|
||||||
} else {
|
} else {
|
||||||
self.retracted.insert(key, value);
|
self.retracted.insert(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
|
@ -10,29 +10,24 @@
|
||||||
|
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use crate::db::TypedSQLValue;
|
||||||
|
use crate::entids;
|
||||||
|
use db_traits::errors::{DbErrorKind, Result};
|
||||||
use edn;
|
use edn;
|
||||||
use errors::{
|
|
||||||
DbErrorKind,
|
|
||||||
Result,
|
|
||||||
};
|
|
||||||
use edn::types::Value;
|
|
||||||
use edn::symbols;
|
|
||||||
use entids;
|
|
||||||
use db::TypedSQLValue;
|
|
||||||
use edn::entities::Entity;
|
use edn::entities::Entity;
|
||||||
use mentat_core::{
|
use edn::symbols;
|
||||||
IdentMap,
|
use edn::types::Value;
|
||||||
Schema,
|
|
||||||
TypedValue,
|
use core_traits::{values, TypedValue};
|
||||||
values,
|
|
||||||
};
|
use crate::schema::SchemaBuilding;
|
||||||
use schema::SchemaBuilding;
|
use crate::types::{Partition, PartitionMap};
|
||||||
use types::{Partition, PartitionMap};
|
use mentat_core::{IdentMap, Schema};
|
||||||
|
|
||||||
/// The first transaction ID applied to the knowledge base.
|
/// The first transaction ID applied to the knowledge base.
|
||||||
///
|
///
|
||||||
/// This is the start of the :db.part/tx partition.
|
/// This is the start of the :db.part/tx partition.
|
||||||
pub const TX0: i64 = 0x10000000;
|
pub const TX0: i64 = 0x1000_0000;
|
||||||
|
|
||||||
/// This is the start of the :db.part/user partition.
|
/// This is the start of the :db.part/user partition.
|
||||||
pub const USER0: i64 = 0x10000;
|
pub const USER0: i64 = 0x10000;
|
||||||
|
@ -42,12 +37,22 @@ pub const CORE_SCHEMA_VERSION: u32 = 1;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref V1_IDENTS: [(symbols::Keyword, i64); 40] = {
|
static ref V1_IDENTS: [(symbols::Keyword, i64); 40] = {
|
||||||
[(ns_keyword!("db", "ident"), entids::DB_IDENT),
|
[
|
||||||
|
(ns_keyword!("db", "ident"), entids::DB_IDENT),
|
||||||
(ns_keyword!("db.part", "db"), entids::DB_PART_DB),
|
(ns_keyword!("db.part", "db"), entids::DB_PART_DB),
|
||||||
(ns_keyword!("db", "txInstant"), entids::DB_TX_INSTANT),
|
(ns_keyword!("db", "txInstant"), entids::DB_TX_INSTANT),
|
||||||
(ns_keyword!("db.install", "partition"), entids::DB_INSTALL_PARTITION),
|
(
|
||||||
(ns_keyword!("db.install", "valueType"), entids::DB_INSTALL_VALUE_TYPE),
|
ns_keyword!("db.install", "partition"),
|
||||||
(ns_keyword!("db.install", "attribute"), entids::DB_INSTALL_ATTRIBUTE),
|
entids::DB_INSTALL_PARTITION,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ns_keyword!("db.install", "valueType"),
|
||||||
|
entids::DB_INSTALL_VALUE_TYPE,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ns_keyword!("db.install", "attribute"),
|
||||||
|
entids::DB_INSTALL_ATTRIBUTE,
|
||||||
|
),
|
||||||
(ns_keyword!("db", "valueType"), entids::DB_VALUE_TYPE),
|
(ns_keyword!("db", "valueType"), entids::DB_VALUE_TYPE),
|
||||||
(ns_keyword!("db", "cardinality"), entids::DB_CARDINALITY),
|
(ns_keyword!("db", "cardinality"), entids::DB_CARDINALITY),
|
||||||
(ns_keyword!("db", "unique"), entids::DB_UNIQUE),
|
(ns_keyword!("db", "unique"), entids::DB_UNIQUE),
|
||||||
|
@ -61,9 +66,15 @@ lazy_static! {
|
||||||
(ns_keyword!("db.part", "tx"), entids::DB_PART_TX),
|
(ns_keyword!("db.part", "tx"), entids::DB_PART_TX),
|
||||||
(ns_keyword!("db", "excise"), entids::DB_EXCISE),
|
(ns_keyword!("db", "excise"), entids::DB_EXCISE),
|
||||||
(ns_keyword!("db.excise", "attrs"), entids::DB_EXCISE_ATTRS),
|
(ns_keyword!("db.excise", "attrs"), entids::DB_EXCISE_ATTRS),
|
||||||
(ns_keyword!("db.excise", "beforeT"), entids::DB_EXCISE_BEFORE_T),
|
(
|
||||||
|
ns_keyword!("db.excise", "beforeT"),
|
||||||
|
entids::DB_EXCISE_BEFORE_T,
|
||||||
|
),
|
||||||
(ns_keyword!("db.excise", "before"), entids::DB_EXCISE_BEFORE),
|
(ns_keyword!("db.excise", "before"), entids::DB_EXCISE_BEFORE),
|
||||||
(ns_keyword!("db.alter", "attribute"), entids::DB_ALTER_ATTRIBUTE),
|
(
|
||||||
|
ns_keyword!("db.alter", "attribute"),
|
||||||
|
entids::DB_ALTER_ATTRIBUTE,
|
||||||
|
),
|
||||||
(ns_keyword!("db.type", "ref"), entids::DB_TYPE_REF),
|
(ns_keyword!("db.type", "ref"), entids::DB_TYPE_REF),
|
||||||
(ns_keyword!("db.type", "keyword"), entids::DB_TYPE_KEYWORD),
|
(ns_keyword!("db.type", "keyword"), entids::DB_TYPE_KEYWORD),
|
||||||
(ns_keyword!("db.type", "long"), entids::DB_TYPE_LONG),
|
(ns_keyword!("db.type", "long"), entids::DB_TYPE_LONG),
|
||||||
|
@ -74,26 +85,53 @@ lazy_static! {
|
||||||
(ns_keyword!("db.type", "boolean"), entids::DB_TYPE_BOOLEAN),
|
(ns_keyword!("db.type", "boolean"), entids::DB_TYPE_BOOLEAN),
|
||||||
(ns_keyword!("db.type", "instant"), entids::DB_TYPE_INSTANT),
|
(ns_keyword!("db.type", "instant"), entids::DB_TYPE_INSTANT),
|
||||||
(ns_keyword!("db.type", "bytes"), entids::DB_TYPE_BYTES),
|
(ns_keyword!("db.type", "bytes"), entids::DB_TYPE_BYTES),
|
||||||
(ns_keyword!("db.cardinality", "one"), entids::DB_CARDINALITY_ONE),
|
(
|
||||||
(ns_keyword!("db.cardinality", "many"), entids::DB_CARDINALITY_MANY),
|
ns_keyword!("db.cardinality", "one"),
|
||||||
|
entids::DB_CARDINALITY_ONE,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ns_keyword!("db.cardinality", "many"),
|
||||||
|
entids::DB_CARDINALITY_MANY,
|
||||||
|
),
|
||||||
(ns_keyword!("db.unique", "value"), entids::DB_UNIQUE_VALUE),
|
(ns_keyword!("db.unique", "value"), entids::DB_UNIQUE_VALUE),
|
||||||
(ns_keyword!("db.unique", "identity"), entids::DB_UNIQUE_IDENTITY),
|
(
|
||||||
|
ns_keyword!("db.unique", "identity"),
|
||||||
|
entids::DB_UNIQUE_IDENTITY,
|
||||||
|
),
|
||||||
(ns_keyword!("db", "doc"), entids::DB_DOC),
|
(ns_keyword!("db", "doc"), entids::DB_DOC),
|
||||||
(ns_keyword!("db.schema", "version"), entids::DB_SCHEMA_VERSION),
|
(
|
||||||
(ns_keyword!("db.schema", "attribute"), entids::DB_SCHEMA_ATTRIBUTE),
|
ns_keyword!("db.schema", "version"),
|
||||||
|
entids::DB_SCHEMA_VERSION,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ns_keyword!("db.schema", "attribute"),
|
||||||
|
entids::DB_SCHEMA_ATTRIBUTE,
|
||||||
|
),
|
||||||
(ns_keyword!("db.schema", "core"), entids::DB_SCHEMA_CORE),
|
(ns_keyword!("db.schema", "core"), entids::DB_SCHEMA_CORE),
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
pub static ref V1_PARTS: [(symbols::Keyword, i64, i64, i64, bool); 3] = {
|
pub static ref V1_PARTS: [(symbols::Keyword, i64, i64, i64, bool); 3] = {
|
||||||
[(ns_keyword!("db.part", "db"), 0, USER0 - 1, (1 + V1_IDENTS.len()) as i64, false),
|
[
|
||||||
|
(
|
||||||
|
ns_keyword!("db.part", "db"),
|
||||||
|
0,
|
||||||
|
USER0 - 1,
|
||||||
|
(1 + V1_IDENTS.len()) as i64,
|
||||||
|
false,
|
||||||
|
),
|
||||||
(ns_keyword!("db.part", "user"), USER0, TX0 - 1, USER0, true),
|
(ns_keyword!("db.part", "user"), USER0, TX0 - 1, USER0, true),
|
||||||
(ns_keyword!("db.part", "tx"), TX0, i64::max_value(), TX0, false),
|
(
|
||||||
|
ns_keyword!("db.part", "tx"),
|
||||||
|
TX0,
|
||||||
|
i64::max_value(),
|
||||||
|
TX0,
|
||||||
|
false,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
static ref V1_CORE_SCHEMA: [symbols::Keyword; 16] = {
|
||||||
static ref V1_CORE_SCHEMA: [(symbols::Keyword); 16] = {
|
[
|
||||||
[(ns_keyword!("db", "ident")),
|
(ns_keyword!("db", "ident")),
|
||||||
(ns_keyword!("db.install", "partition")),
|
(ns_keyword!("db.install", "partition")),
|
||||||
(ns_keyword!("db.install", "valueType")),
|
(ns_keyword!("db.install", "valueType")),
|
||||||
(ns_keyword!("db.install", "attribute")),
|
(ns_keyword!("db.install", "attribute")),
|
||||||
|
@ -111,7 +149,6 @@ lazy_static! {
|
||||||
(ns_keyword!("db.schema", "attribute")),
|
(ns_keyword!("db.schema", "attribute")),
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
static ref V1_SYMBOLIC_SCHEMA: Value = {
|
static ref V1_SYMBOLIC_SCHEMA: Value = {
|
||||||
let s = r#"
|
let s = r#"
|
||||||
{:db/ident {:db/valueType :db.type/keyword
|
{:db/ident {:db/valueType :db.type/keyword
|
||||||
|
@ -159,7 +196,9 @@ lazy_static! {
|
||||||
:db/cardinality :db.cardinality/many}}"#;
|
:db/cardinality :db.cardinality/many}}"#;
|
||||||
edn::parse::value(s)
|
edn::parse::value(s)
|
||||||
.map(|v| v.without_spans())
|
.map(|v| v.without_spans())
|
||||||
.map_err(|_| DbErrorKind::BadBootstrapDefinition("Unable to parse V1_SYMBOLIC_SCHEMA".into()))
|
.map_err(|_| {
|
||||||
|
DbErrorKind::BadBootstrapDefinition("Unable to parse V1_SYMBOLIC_SCHEMA".into())
|
||||||
|
})
|
||||||
.unwrap()
|
.unwrap()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -167,10 +206,15 @@ lazy_static! {
|
||||||
/// Convert (ident, entid) pairs into [:db/add IDENT :db/ident IDENT] `Value` instances.
|
/// Convert (ident, entid) pairs into [:db/add IDENT :db/ident IDENT] `Value` instances.
|
||||||
fn idents_to_assertions(idents: &[(symbols::Keyword, i64)]) -> Vec<Value> {
|
fn idents_to_assertions(idents: &[(symbols::Keyword, i64)]) -> Vec<Value> {
|
||||||
idents
|
idents
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|&(ref ident, _)| {
|
.map(|&(ref ident, _)| {
|
||||||
let value = Value::Keyword(ident.clone());
|
let value = Value::Keyword(ident.clone());
|
||||||
Value::Vector(vec![values::DB_ADD.clone(), value.clone(), values::DB_IDENT.clone(), value.clone()])
|
Value::Vector(vec![
|
||||||
|
values::DB_ADD.clone(),
|
||||||
|
value.clone(),
|
||||||
|
values::DB_IDENT.clone(),
|
||||||
|
value,
|
||||||
|
])
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
@ -181,18 +225,22 @@ fn schema_attrs_to_assertions(version: u32, idents: &[symbols::Keyword]) -> Vec<
|
||||||
let schema_attr = Value::Keyword(ns_keyword!("db.schema", "attribute"));
|
let schema_attr = Value::Keyword(ns_keyword!("db.schema", "attribute"));
|
||||||
let schema_version = Value::Keyword(ns_keyword!("db.schema", "version"));
|
let schema_version = Value::Keyword(ns_keyword!("db.schema", "version"));
|
||||||
idents
|
idents
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|ident| {
|
.map(|ident| {
|
||||||
let value = Value::Keyword(ident.clone());
|
let value = Value::Keyword(ident.clone());
|
||||||
Value::Vector(vec![values::DB_ADD.clone(),
|
Value::Vector(vec![
|
||||||
|
values::DB_ADD.clone(),
|
||||||
schema_core.clone(),
|
schema_core.clone(),
|
||||||
schema_attr.clone(),
|
schema_attr.clone(),
|
||||||
value])
|
value,
|
||||||
|
])
|
||||||
})
|
})
|
||||||
.chain(::std::iter::once(Value::Vector(vec![values::DB_ADD.clone(),
|
.chain(::std::iter::once(Value::Vector(vec![
|
||||||
|
values::DB_ADD.clone(),
|
||||||
schema_core.clone(),
|
schema_core.clone(),
|
||||||
schema_version,
|
schema_version,
|
||||||
Value::Integer(version as i64)])))
|
Value::Integer(version as i64),
|
||||||
|
])))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,7 +249,10 @@ fn schema_attrs_to_assertions(version: u32, idents: &[symbols::Keyword]) -> Vec<
|
||||||
///
|
///
|
||||||
/// Such triples are closer to what the transactor will produce when processing attribute
|
/// Such triples are closer to what the transactor will produce when processing attribute
|
||||||
/// assertions.
|
/// assertions.
|
||||||
fn symbolic_schema_to_triples(ident_map: &IdentMap, symbolic_schema: &Value) -> Result<Vec<(symbols::Keyword, symbols::Keyword, TypedValue)>> {
|
fn symbolic_schema_to_triples(
|
||||||
|
ident_map: &IdentMap,
|
||||||
|
symbolic_schema: &Value,
|
||||||
|
) -> Result<Vec<(symbols::Keyword, symbols::Keyword, TypedValue)>> {
|
||||||
// Failure here is a coding error, not a runtime error.
|
// Failure here is a coding error, not a runtime error.
|
||||||
let mut triples: Vec<(symbols::Keyword, symbols::Keyword, TypedValue)> = vec![];
|
let mut triples: Vec<(symbols::Keyword, symbols::Keyword, TypedValue)> = vec![];
|
||||||
// TODO: Consider `flat_map` and `map` rather than loop.
|
// TODO: Consider `flat_map` and `map` rather than loop.
|
||||||
|
@ -209,15 +260,21 @@ fn symbolic_schema_to_triples(ident_map: &IdentMap, symbolic_schema: &Value) ->
|
||||||
Value::Map(ref m) => {
|
Value::Map(ref m) => {
|
||||||
for (ident, mp) in m {
|
for (ident, mp) in m {
|
||||||
let ident = match ident {
|
let ident = match ident {
|
||||||
&Value::Keyword(ref ident) => ident,
|
Value::Keyword(ref ident) => ident,
|
||||||
_ => bail!(DbErrorKind::BadBootstrapDefinition(format!("Expected namespaced keyword for ident but got '{:?}'", ident))),
|
_ => bail!(DbErrorKind::BadBootstrapDefinition(format!(
|
||||||
|
"Expected namespaced keyword for ident but got '{:?}'",
|
||||||
|
ident
|
||||||
|
))),
|
||||||
};
|
};
|
||||||
match *mp {
|
match *mp {
|
||||||
Value::Map(ref mpp) => {
|
Value::Map(ref mpp) => {
|
||||||
for (attr, value) in mpp {
|
for (attr, value) in mpp {
|
||||||
let attr = match attr {
|
let attr = match attr {
|
||||||
&Value::Keyword(ref attr) => attr,
|
Value::Keyword(ref attr) => attr,
|
||||||
_ => bail!(DbErrorKind::BadBootstrapDefinition(format!("Expected namespaced keyword for attr but got '{:?}'", attr))),
|
_ => bail!(DbErrorKind::BadBootstrapDefinition(format!(
|
||||||
|
"Expected namespaced keyword for attr but got '{:?}'",
|
||||||
|
attr
|
||||||
|
))),
|
||||||
};
|
};
|
||||||
|
|
||||||
// We have symbolic idents but the transactor handles entids. Ad-hoc
|
// We have symbolic idents but the transactor handles entids. Ad-hoc
|
||||||
|
@ -229,23 +286,27 @@ fn symbolic_schema_to_triples(ident_map: &IdentMap, symbolic_schema: &Value) ->
|
||||||
// bootstrap symbolic schema, or by representing the initial bootstrap
|
// bootstrap symbolic schema, or by representing the initial bootstrap
|
||||||
// schema directly as Rust data.
|
// schema directly as Rust data.
|
||||||
let typed_value = match TypedValue::from_edn_value(value) {
|
let typed_value = match TypedValue::from_edn_value(value) {
|
||||||
Some(TypedValue::Keyword(ref k)) => {
|
Some(TypedValue::Keyword(ref k)) => ident_map
|
||||||
ident_map.get(k)
|
.get(k)
|
||||||
.map(|entid| TypedValue::Ref(*entid))
|
.map(|entid| TypedValue::Ref(*entid))
|
||||||
.ok_or(DbErrorKind::UnrecognizedIdent(k.to_string()))?
|
.ok_or_else(|| DbErrorKind::UnrecognizedIdent(k.to_string()))?,
|
||||||
},
|
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
_ => bail!(DbErrorKind::BadBootstrapDefinition(format!("Expected Mentat typed value for value but got '{:?}'", value)))
|
_ => bail!(DbErrorKind::BadBootstrapDefinition(format!(
|
||||||
|
"Expected Mentat typed value for value but got '{:?}'",
|
||||||
|
value
|
||||||
|
))),
|
||||||
};
|
};
|
||||||
|
|
||||||
triples.push((ident.clone(), attr.clone(), typed_value));
|
triples.push((ident.clone(), attr.clone(), typed_value));
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
_ => bail!(DbErrorKind::BadBootstrapDefinition("Expected {:db/ident {:db/attr value ...} ...}".into()))
|
_ => bail!(DbErrorKind::BadBootstrapDefinition(
|
||||||
|
"Expected {:db/ident {:db/attr value ...} ...}".into()
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
_ => bail!(DbErrorKind::BadBootstrapDefinition("Expected {...}".into()))
|
_ => bail!(DbErrorKind::BadBootstrapDefinition("Expected {...}".into())),
|
||||||
}
|
}
|
||||||
Ok(triples)
|
Ok(triples)
|
||||||
}
|
}
|
||||||
|
@ -260,48 +321,62 @@ fn symbolic_schema_to_assertions(symbolic_schema: &Value) -> Result<Vec<Value>>
|
||||||
match *mp {
|
match *mp {
|
||||||
Value::Map(ref mpp) => {
|
Value::Map(ref mpp) => {
|
||||||
for (attr, value) in mpp {
|
for (attr, value) in mpp {
|
||||||
assertions.push(Value::Vector(vec![values::DB_ADD.clone(),
|
assertions.push(Value::Vector(vec![
|
||||||
|
values::DB_ADD.clone(),
|
||||||
ident.clone(),
|
ident.clone(),
|
||||||
attr.clone(),
|
attr.clone(),
|
||||||
value.clone()]));
|
value.clone(),
|
||||||
}
|
]));
|
||||||
},
|
|
||||||
_ => bail!(DbErrorKind::BadBootstrapDefinition("Expected {:db/ident {:db/attr value ...} ...}".into()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
_ => bail!(DbErrorKind::BadBootstrapDefinition(
|
||||||
_ => bail!(DbErrorKind::BadBootstrapDefinition("Expected {...}".into()))
|
"Expected {:db/ident {:db/attr value ...} ...}".into()
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => bail!(DbErrorKind::BadBootstrapDefinition("Expected {...}".into())),
|
||||||
}
|
}
|
||||||
Ok(assertions)
|
Ok(assertions)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn bootstrap_partition_map() -> PartitionMap {
|
pub(crate) fn bootstrap_partition_map() -> PartitionMap {
|
||||||
V1_PARTS.iter()
|
V1_PARTS
|
||||||
.map(|&(ref part, start, end, index, allow_excision)| (part.to_string(), Partition::new(start, end, index, allow_excision)))
|
.iter()
|
||||||
|
.map(|&(ref part, start, end, index, allow_excision)| {
|
||||||
|
(
|
||||||
|
part.to_string(),
|
||||||
|
Partition::new(start, end, index, allow_excision),
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn bootstrap_ident_map() -> IdentMap {
|
pub(crate) fn bootstrap_ident_map() -> IdentMap {
|
||||||
V1_IDENTS.iter()
|
V1_IDENTS
|
||||||
|
.iter()
|
||||||
.map(|&(ref ident, entid)| (ident.clone(), entid))
|
.map(|&(ref ident, entid)| (ident.clone(), entid))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn bootstrap_schema() -> Schema {
|
pub(crate) fn bootstrap_schema() -> Schema {
|
||||||
let ident_map = bootstrap_ident_map();
|
let ident_map = bootstrap_ident_map();
|
||||||
let bootstrap_triples = symbolic_schema_to_triples(&ident_map, &V1_SYMBOLIC_SCHEMA).expect("symbolic schema");
|
let bootstrap_triples =
|
||||||
|
symbolic_schema_to_triples(&ident_map, &V1_SYMBOLIC_SCHEMA).expect("symbolic schema");
|
||||||
Schema::from_ident_map_and_triples(ident_map, bootstrap_triples).unwrap()
|
Schema::from_ident_map_and_triples(ident_map, bootstrap_triples).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn bootstrap_entities() -> Vec<Entity<edn::ValueAndSpan>> {
|
pub(crate) fn bootstrap_entities() -> Vec<Entity<edn::ValueAndSpan>> {
|
||||||
let bootstrap_assertions: Value = Value::Vector([
|
let bootstrap_assertions: Value = Value::Vector(
|
||||||
|
[
|
||||||
symbolic_schema_to_assertions(&V1_SYMBOLIC_SCHEMA).expect("symbolic schema"),
|
symbolic_schema_to_assertions(&V1_SYMBOLIC_SCHEMA).expect("symbolic schema"),
|
||||||
idents_to_assertions(&V1_IDENTS[..]),
|
idents_to_assertions(&V1_IDENTS[..]),
|
||||||
schema_attrs_to_assertions(CORE_SCHEMA_VERSION, V1_CORE_SCHEMA.as_ref()),
|
schema_attrs_to_assertions(CORE_SCHEMA_VERSION, V1_CORE_SCHEMA.as_ref()),
|
||||||
].concat());
|
]
|
||||||
|
.concat(),
|
||||||
|
);
|
||||||
|
|
||||||
// Failure here is a coding error (since the inputs are fixed), not a runtime error.
|
// Failure here is a coding error (since the inputs are fixed), not a runtime error.
|
||||||
// TODO: represent these bootstrap data errors rather than just panicing.
|
// TODO: represent these bootstrap entity data errors rather than just panicing.
|
||||||
let bootstrap_entities: Vec<Entity<edn::ValueAndSpan>> = edn::parse::entities(&bootstrap_assertions.to_string()).expect("bootstrap assertions");
|
edn::parse::entities(&bootstrap_assertions.to_string()).expect("bootstrap assertions")
|
||||||
return bootstrap_entities;
|
|
||||||
}
|
}
|
||||||
|
|
1045
db/src/cache.rs
1045
db/src/cache.rs
File diff suppressed because it is too large
Load diff
1980
db/src/db.rs
1980
db/src/db.rs
File diff suppressed because it is too large
Load diff
267
db/src/debug.rs
267
db/src/debug.rs
|
@ -25,11 +25,13 @@ macro_rules! assert_matches {
|
||||||
.expect(format!("to be able to parse expected {}", $expected).as_str())
|
.expect(format!("to be able to parse expected {}", $expected).as_str())
|
||||||
.without_spans();
|
.without_spans();
|
||||||
let input_value = $input.to_edn();
|
let input_value = $input.to_edn();
|
||||||
assert!(input_value.matches(&pattern_value),
|
assert!(
|
||||||
|
input_value.matches(&pattern_value),
|
||||||
"Expected value:\n{}\nto match pattern:\n{}\n",
|
"Expected value:\n{}\nto match pattern:\n{}\n",
|
||||||
input_value.to_pretty(120).unwrap(),
|
input_value.to_pretty(120).unwrap(),
|
||||||
pattern_value.to_pretty(120).unwrap());
|
pattern_value.to_pretty(120).unwrap()
|
||||||
}}
|
);
|
||||||
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transact $input against the given $conn, expecting success or a `Result<TxReport, String>`.
|
// Transact $input against the given $conn, expecting success or a `Result<TxReport, String>`.
|
||||||
|
@ -45,53 +47,42 @@ macro_rules! assert_transact {
|
||||||
( $conn: expr, $input: expr ) => {{
|
( $conn: expr, $input: expr ) => {{
|
||||||
trace!("assert_transact: {}", $input);
|
trace!("assert_transact: {}", $input);
|
||||||
let result = $conn.transact($input);
|
let result = $conn.transact($input);
|
||||||
assert!(result.is_ok(), "Expected Ok(_), got `{}`", result.unwrap_err());
|
assert!(
|
||||||
|
result.is_ok(),
|
||||||
|
"Expected Ok(_), got `{}`",
|
||||||
|
result.unwrap_err()
|
||||||
|
);
|
||||||
result.unwrap()
|
result.unwrap()
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::io::{Write};
|
use std::io::Write;
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use rusqlite;
|
use rusqlite;
|
||||||
use rusqlite::{TransactionBehavior};
|
use rusqlite::types::ToSql;
|
||||||
use rusqlite::types::{ToSql};
|
use rusqlite::TransactionBehavior;
|
||||||
use tabwriter::TabWriter;
|
use tabwriter::TabWriter;
|
||||||
|
|
||||||
use bootstrap;
|
use crate::bootstrap;
|
||||||
use db::*;
|
use crate::db::*;
|
||||||
use db::{read_attribute_map,read_ident_map};
|
use crate::db::{read_attribute_map, read_ident_map};
|
||||||
|
use crate::entids;
|
||||||
|
use db_traits::errors::Result;
|
||||||
use edn;
|
use edn;
|
||||||
use entids;
|
|
||||||
use errors::Result;
|
use core_traits::{Entid, TypedValue, ValueType};
|
||||||
use mentat_core::{
|
|
||||||
HasSchema,
|
use crate::internal_types::TermWithTempIds;
|
||||||
SQLValueType,
|
use crate::schema::SchemaBuilding;
|
||||||
TxReport,
|
use crate::tx::{transact, transact_terms};
|
||||||
TypedValue,
|
use crate::types::*;
|
||||||
ValueType,
|
use crate::watcher::NullWatcher;
|
||||||
};
|
use edn::entities::{EntidOrIdent, TempId};
|
||||||
use edn::{
|
use edn::InternSet;
|
||||||
InternSet,
|
use mentat_core::{HasSchema, SQLValueType, TxReport};
|
||||||
};
|
|
||||||
use edn::entities::{
|
|
||||||
EntidOrIdent,
|
|
||||||
TempId,
|
|
||||||
};
|
|
||||||
use internal_types::{
|
|
||||||
TermWithTempIds,
|
|
||||||
};
|
|
||||||
use schema::{
|
|
||||||
SchemaBuilding,
|
|
||||||
};
|
|
||||||
use types::*;
|
|
||||||
use tx::{
|
|
||||||
transact,
|
|
||||||
transact_terms,
|
|
||||||
};
|
|
||||||
use watcher::NullWatcher;
|
|
||||||
|
|
||||||
/// Represents a *datom* (assertion) in the store.
|
/// Represents a *datom* (assertion) in the store.
|
||||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||||
|
@ -126,7 +117,7 @@ impl Datom {
|
||||||
pub fn to_edn(&self) -> edn::Value {
|
pub fn to_edn(&self) -> edn::Value {
|
||||||
let f = |entid: &EntidOrIdent| -> edn::Value {
|
let f = |entid: &EntidOrIdent| -> edn::Value {
|
||||||
match *entid {
|
match *entid {
|
||||||
EntidOrIdent::Entid(ref y) => edn::Value::Integer(y.clone()),
|
EntidOrIdent::Entid(ref y) => edn::Value::Integer(*y),
|
||||||
EntidOrIdent::Ident(ref y) => edn::Value::Keyword(y.clone()),
|
EntidOrIdent::Ident(ref y) => edn::Value::Keyword(y.clone()),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -143,19 +134,26 @@ impl Datom {
|
||||||
|
|
||||||
impl Datoms {
|
impl Datoms {
|
||||||
pub fn to_edn(&self) -> edn::Value {
|
pub fn to_edn(&self) -> edn::Value {
|
||||||
edn::Value::Vector((&self.0).into_iter().map(|x| x.to_edn()).collect())
|
edn::Value::Vector((&self.0).iter().map(|x| x.to_edn()).collect())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Transactions {
|
impl Transactions {
|
||||||
pub fn to_edn(&self) -> edn::Value {
|
pub fn to_edn(&self) -> edn::Value {
|
||||||
edn::Value::Vector((&self.0).into_iter().map(|x| x.to_edn()).collect())
|
edn::Value::Vector((&self.0).iter().map(|x| x.to_edn()).collect())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FulltextValues {
|
impl FulltextValues {
|
||||||
pub fn to_edn(&self) -> edn::Value {
|
pub fn to_edn(&self) -> edn::Value {
|
||||||
edn::Value::Vector((&self.0).into_iter().map(|&(x, ref y)| edn::Value::Vector(vec![edn::Value::Integer(x), edn::Value::Text(y.clone())])).collect())
|
edn::Value::Vector(
|
||||||
|
(&self.0)
|
||||||
|
.iter()
|
||||||
|
.map(|&(x, ref y)| {
|
||||||
|
edn::Value::Vector(vec![edn::Value::Integer(x), edn::Value::Text(y.clone())])
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,7 +165,11 @@ trait ToIdent {
|
||||||
impl ToIdent for TypedValue {
|
impl ToIdent for TypedValue {
|
||||||
fn map_ident(self, schema: &Schema) -> Self {
|
fn map_ident(self, schema: &Schema) -> Self {
|
||||||
if let TypedValue::Ref(e) = self {
|
if let TypedValue::Ref(e) = self {
|
||||||
schema.get_ident(e).cloned().map(|i| i.into()).unwrap_or(TypedValue::Ref(e))
|
schema
|
||||||
|
.get_ident(e)
|
||||||
|
.cloned()
|
||||||
|
.map(|i| i.into())
|
||||||
|
.unwrap_or(TypedValue::Ref(e))
|
||||||
} else {
|
} else {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -176,7 +178,11 @@ impl ToIdent for TypedValue {
|
||||||
|
|
||||||
/// Convert a numeric entid to an ident `Entid` if possible, otherwise a numeric `Entid`.
|
/// Convert a numeric entid to an ident `Entid` if possible, otherwise a numeric `Entid`.
|
||||||
pub fn to_entid(schema: &Schema, entid: i64) -> EntidOrIdent {
|
pub fn to_entid(schema: &Schema, entid: i64) -> EntidOrIdent {
|
||||||
schema.get_ident(entid).map_or(EntidOrIdent::Entid(entid), |ident| EntidOrIdent::Ident(ident.clone()))
|
schema
|
||||||
|
.get_ident(entid)
|
||||||
|
.map_or(EntidOrIdent::Entid(entid), |ident| {
|
||||||
|
EntidOrIdent::Ident(ident.clone())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// /// Convert a symbolic ident to an ident `Entid` if possible, otherwise a numeric `Entid`.
|
// /// Convert a symbolic ident to an ident `Entid` if possible, otherwise a numeric `Entid`.
|
||||||
|
@ -194,38 +200,49 @@ pub fn datoms<S: Borrow<Schema>>(conn: &rusqlite::Connection, schema: &S) -> Res
|
||||||
/// ordered by (e, a, v, tx).
|
/// ordered by (e, a, v, tx).
|
||||||
///
|
///
|
||||||
/// The datom set returned does not include any datoms of the form [... :db/txInstant ...].
|
/// The datom set returned does not include any datoms of the form [... :db/txInstant ...].
|
||||||
pub fn datoms_after<S: Borrow<Schema>>(conn: &rusqlite::Connection, schema: &S, tx: i64) -> Result<Datoms> {
|
pub fn datoms_after<S: Borrow<Schema>>(
|
||||||
|
conn: &rusqlite::Connection,
|
||||||
|
schema: &S,
|
||||||
|
tx: i64,
|
||||||
|
) -> Result<Datoms> {
|
||||||
let borrowed_schema = schema.borrow();
|
let borrowed_schema = schema.borrow();
|
||||||
|
|
||||||
let mut stmt: rusqlite::Statement = conn.prepare("SELECT e, a, v, value_type_tag, tx FROM datoms WHERE tx > ? ORDER BY e ASC, a ASC, value_type_tag ASC, v ASC, tx ASC")?;
|
let mut stmt: rusqlite::Statement = conn.prepare("SELECT e, a, v, value_type_tag, tx FROM datoms WHERE tx > ? ORDER BY e ASC, a ASC, value_type_tag ASC, v ASC, tx ASC")?;
|
||||||
|
|
||||||
let r: Result<Vec<_>> = stmt.query_and_then(&[&tx], |row| {
|
let r: Result<Vec<_>> = stmt
|
||||||
let e: i64 = row.get_checked(0)?;
|
.query_and_then(&[&tx], |row| {
|
||||||
let a: i64 = row.get_checked(1)?;
|
let e: i64 = row.get(0)?;
|
||||||
|
let a: i64 = row.get(1)?;
|
||||||
|
|
||||||
if a == entids::DB_TX_INSTANT {
|
if a == entids::DB_TX_INSTANT {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let v: rusqlite::types::Value = row.get_checked(2)?;
|
let v: rusqlite::types::Value = row.get(2)?;
|
||||||
let value_type_tag: i32 = row.get_checked(3)?;
|
let value_type_tag: i32 = row.get(3)?;
|
||||||
|
|
||||||
let attribute = borrowed_schema.require_attribute_for_entid(a)?;
|
let attribute = borrowed_schema.require_attribute_for_entid(a)?;
|
||||||
let value_type_tag = if !attribute.fulltext { value_type_tag } else { ValueType::Long.value_type_tag() };
|
let value_type_tag = if !attribute.fulltext {
|
||||||
|
value_type_tag
|
||||||
|
} else {
|
||||||
|
ValueType::Long.value_type_tag()
|
||||||
|
};
|
||||||
|
|
||||||
let typed_value = TypedValue::from_sql_value_pair(v, value_type_tag)?.map_ident(borrowed_schema);
|
let typed_value =
|
||||||
|
TypedValue::from_sql_value_pair(v, value_type_tag)?.map_ident(borrowed_schema);
|
||||||
let (value, _) = typed_value.to_edn_value_pair();
|
let (value, _) = typed_value.to_edn_value_pair();
|
||||||
|
|
||||||
let tx: i64 = row.get_checked(4)?;
|
let tx: i64 = row.get(4)?;
|
||||||
|
|
||||||
Ok(Some(Datom {
|
Ok(Some(Datom {
|
||||||
e: EntidOrIdent::Entid(e),
|
e: EntidOrIdent::Entid(e),
|
||||||
a: to_entid(borrowed_schema, a),
|
a: to_entid(borrowed_schema, a),
|
||||||
v: value,
|
v: value,
|
||||||
tx: tx,
|
tx,
|
||||||
added: None,
|
added: None,
|
||||||
}))
|
}))
|
||||||
})?.collect();
|
})?
|
||||||
|
.collect();
|
||||||
|
|
||||||
Ok(Datoms(r?.into_iter().filter_map(|x| x).collect()))
|
Ok(Datoms(r?.into_iter().filter_map(|x| x).collect()))
|
||||||
}
|
}
|
||||||
|
@ -234,50 +251,69 @@ pub fn datoms_after<S: Borrow<Schema>>(conn: &rusqlite::Connection, schema: &S,
|
||||||
/// given `tx`, ordered by (tx, e, a, v).
|
/// given `tx`, ordered by (tx, e, a, v).
|
||||||
///
|
///
|
||||||
/// Each transaction returned includes the [(transaction-tx) :db/txInstant ...] datom.
|
/// Each transaction returned includes the [(transaction-tx) :db/txInstant ...] datom.
|
||||||
pub fn transactions_after<S: Borrow<Schema>>(conn: &rusqlite::Connection, schema: &S, tx: i64) -> Result<Transactions> {
|
pub fn transactions_after<S: Borrow<Schema>>(
|
||||||
|
conn: &rusqlite::Connection,
|
||||||
|
schema: &S,
|
||||||
|
tx: i64,
|
||||||
|
) -> Result<Transactions> {
|
||||||
let borrowed_schema = schema.borrow();
|
let borrowed_schema = schema.borrow();
|
||||||
|
|
||||||
let mut stmt: rusqlite::Statement = conn.prepare("SELECT e, a, v, value_type_tag, tx, added FROM transactions WHERE tx > ? ORDER BY tx ASC, e ASC, a ASC, value_type_tag ASC, v ASC, added ASC")?;
|
let mut stmt: rusqlite::Statement = conn.prepare("SELECT e, a, v, value_type_tag, tx, added FROM transactions WHERE tx > ? ORDER BY tx ASC, e ASC, a ASC, value_type_tag ASC, v ASC, added ASC")?;
|
||||||
|
|
||||||
let r: Result<Vec<_>> = stmt.query_and_then(&[&tx], |row| {
|
let r: Result<Vec<_>> = stmt
|
||||||
let e: i64 = row.get_checked(0)?;
|
.query_and_then(&[&tx], |row| {
|
||||||
let a: i64 = row.get_checked(1)?;
|
let e: i64 = row.get(0)?;
|
||||||
|
let a: i64 = row.get(1)?;
|
||||||
|
|
||||||
let v: rusqlite::types::Value = row.get_checked(2)?;
|
let v: rusqlite::types::Value = row.get(2)?;
|
||||||
let value_type_tag: i32 = row.get_checked(3)?;
|
let value_type_tag: i32 = row.get(3)?;
|
||||||
|
|
||||||
let attribute = borrowed_schema.require_attribute_for_entid(a)?;
|
let attribute = borrowed_schema.require_attribute_for_entid(a)?;
|
||||||
let value_type_tag = if !attribute.fulltext { value_type_tag } else { ValueType::Long.value_type_tag() };
|
let value_type_tag = if !attribute.fulltext {
|
||||||
|
value_type_tag
|
||||||
|
} else {
|
||||||
|
ValueType::Long.value_type_tag()
|
||||||
|
};
|
||||||
|
|
||||||
let typed_value = TypedValue::from_sql_value_pair(v, value_type_tag)?.map_ident(borrowed_schema);
|
let typed_value =
|
||||||
|
TypedValue::from_sql_value_pair(v, value_type_tag)?.map_ident(borrowed_schema);
|
||||||
let (value, _) = typed_value.to_edn_value_pair();
|
let (value, _) = typed_value.to_edn_value_pair();
|
||||||
|
|
||||||
let tx: i64 = row.get_checked(4)?;
|
let tx: i64 = row.get(4)?;
|
||||||
let added: bool = row.get_checked(5)?;
|
let added: bool = row.get(5)?;
|
||||||
|
|
||||||
Ok(Datom {
|
Ok(Datom {
|
||||||
e: EntidOrIdent::Entid(e),
|
e: EntidOrIdent::Entid(e),
|
||||||
a: to_entid(borrowed_schema, a),
|
a: to_entid(borrowed_schema, a),
|
||||||
v: value,
|
v: value,
|
||||||
tx: tx,
|
tx,
|
||||||
added: Some(added),
|
added: Some(added),
|
||||||
})
|
})
|
||||||
})?.collect();
|
})?
|
||||||
|
.collect();
|
||||||
|
|
||||||
// Group by tx.
|
// Group by tx.
|
||||||
let r: Vec<Datoms> = r?.into_iter().group_by(|x| x.tx).into_iter().map(|(_key, group)| Datoms(group.collect())).collect();
|
let r: Vec<Datoms> = r?
|
||||||
|
.into_iter()
|
||||||
|
.group_by(|x| x.tx)
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_key, group)| Datoms(group.collect()))
|
||||||
|
.collect();
|
||||||
Ok(Transactions(r))
|
Ok(Transactions(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the set of fulltext values in the store, ordered by rowid.
|
/// Return the set of fulltext values in the store, ordered by rowid.
|
||||||
pub fn fulltext_values(conn: &rusqlite::Connection) -> Result<FulltextValues> {
|
pub fn fulltext_values(conn: &rusqlite::Connection) -> Result<FulltextValues> {
|
||||||
let mut stmt: rusqlite::Statement = conn.prepare("SELECT rowid, text FROM fulltext_values ORDER BY rowid")?;
|
let mut stmt: rusqlite::Statement =
|
||||||
|
conn.prepare("SELECT rowid, text FROM fulltext_values ORDER BY rowid")?;
|
||||||
|
|
||||||
let r: Result<Vec<_>> = stmt.query_and_then(&[], |row| {
|
let r: Result<Vec<_>> = stmt
|
||||||
let rowid: i64 = row.get_checked(0)?;
|
.query_and_then([], |row| {
|
||||||
let text: String = row.get_checked(1)?;
|
let rowid: i64 = row.get(0)?;
|
||||||
|
let text: String = row.get(1)?;
|
||||||
Ok((rowid, text))
|
Ok((rowid, text))
|
||||||
})?.collect();
|
})?
|
||||||
|
.collect();
|
||||||
|
|
||||||
r.map(FulltextValues)
|
r.map(FulltextValues)
|
||||||
}
|
}
|
||||||
|
@ -287,25 +323,31 @@ pub fn fulltext_values(conn: &rusqlite::Connection) -> Result<FulltextValues> {
|
||||||
///
|
///
|
||||||
/// The query is printed followed by a newline, then the returned columns followed by a newline, and
|
/// The query is printed followed by a newline, then the returned columns followed by a newline, and
|
||||||
/// then the data rows and columns. All columns are aligned.
|
/// then the data rows and columns. All columns are aligned.
|
||||||
pub fn dump_sql_query(conn: &rusqlite::Connection, sql: &str, params: &[&ToSql]) -> Result<String> {
|
pub fn dump_sql_query(
|
||||||
|
conn: &rusqlite::Connection,
|
||||||
|
sql: &str,
|
||||||
|
params: &[&dyn ToSql],
|
||||||
|
) -> Result<String> {
|
||||||
let mut stmt: rusqlite::Statement = conn.prepare(sql)?;
|
let mut stmt: rusqlite::Statement = conn.prepare(sql)?;
|
||||||
|
|
||||||
let mut tw = TabWriter::new(Vec::new()).padding(2);
|
let mut tw = TabWriter::new(Vec::new()).padding(2);
|
||||||
write!(&mut tw, "{}\n", sql).unwrap();
|
writeln!(&mut tw, "{}", sql).unwrap();
|
||||||
|
|
||||||
for column_name in stmt.column_names() {
|
for column_name in stmt.column_names() {
|
||||||
write!(&mut tw, "{}\t", column_name).unwrap();
|
write!(&mut tw, "{}\t", column_name).unwrap();
|
||||||
}
|
}
|
||||||
write!(&mut tw, "\n").unwrap();
|
writeln!(&mut tw).unwrap();
|
||||||
|
|
||||||
let r: Result<Vec<_>> = stmt.query_and_then(params, |row| {
|
let r: Result<Vec<_>> = stmt
|
||||||
for i in 0..row.column_count() {
|
.query_and_then(params, |row| {
|
||||||
let value: rusqlite::types::Value = row.get_checked(i)?;
|
for i in 0..row.as_ref().column_count() {
|
||||||
|
let value: rusqlite::types::Value = row.get(i)?;
|
||||||
write!(&mut tw, "{:?}\t", value).unwrap();
|
write!(&mut tw, "{:?}\t", value).unwrap();
|
||||||
}
|
}
|
||||||
write!(&mut tw, "\n").unwrap();
|
writeln!(&mut tw).unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
})?.collect();
|
})?
|
||||||
|
.collect();
|
||||||
r?;
|
r?;
|
||||||
|
|
||||||
let dump = String::from_utf8(tw.into_inner().unwrap()).unwrap();
|
let dump = String::from_utf8(tw.into_inner().unwrap()).unwrap();
|
||||||
|
@ -325,20 +367,38 @@ impl TestConn {
|
||||||
let materialized_ident_map = read_ident_map(&self.sqlite).expect("ident map");
|
let materialized_ident_map = read_ident_map(&self.sqlite).expect("ident map");
|
||||||
let materialized_attribute_map = read_attribute_map(&self.sqlite).expect("schema map");
|
let materialized_attribute_map = read_attribute_map(&self.sqlite).expect("schema map");
|
||||||
|
|
||||||
let materialized_schema = Schema::from_ident_map_and_attribute_map(materialized_ident_map, materialized_attribute_map).expect("schema");
|
let materialized_schema = Schema::from_ident_map_and_attribute_map(
|
||||||
|
materialized_ident_map,
|
||||||
|
materialized_attribute_map,
|
||||||
|
)
|
||||||
|
.expect("schema");
|
||||||
assert_eq!(materialized_schema, self.schema);
|
assert_eq!(materialized_schema, self.schema);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transact<I>(&mut self, transaction: I) -> Result<TxReport> where I: Borrow<str> {
|
pub fn transact<I>(&mut self, transaction: I) -> Result<TxReport>
|
||||||
|
where
|
||||||
|
I: Borrow<str>,
|
||||||
|
{
|
||||||
// Failure to parse the transaction is a coding error, so we unwrap.
|
// Failure to parse the transaction is a coding error, so we unwrap.
|
||||||
let entities = edn::parse::entities(transaction.borrow()).expect(format!("to be able to parse {} into entities", transaction.borrow()).as_str());
|
let entities = edn::parse::entities(transaction.borrow()).unwrap_or_else(|_| {
|
||||||
|
panic!("to be able to parse {} into entities", transaction.borrow())
|
||||||
|
});
|
||||||
|
|
||||||
let details = {
|
let details = {
|
||||||
// The block scopes the borrow of self.sqlite.
|
// The block scopes the borrow of self.sqlite.
|
||||||
// We're about to write, so go straight ahead and get an IMMEDIATE transaction.
|
// We're about to write, so go straight ahead and get an IMMEDIATE transaction.
|
||||||
let tx = self.sqlite.transaction_with_behavior(TransactionBehavior::Immediate)?;
|
let tx = self
|
||||||
|
.sqlite
|
||||||
|
.transaction_with_behavior(TransactionBehavior::Immediate)?;
|
||||||
// Applying the transaction can fail, so we don't unwrap.
|
// Applying the transaction can fail, so we don't unwrap.
|
||||||
let details = transact(&tx, self.partition_map.clone(), &self.schema, &self.schema, NullWatcher(), entities)?;
|
let details = transact(
|
||||||
|
&tx,
|
||||||
|
self.partition_map.clone(),
|
||||||
|
&self.schema,
|
||||||
|
&self.schema,
|
||||||
|
NullWatcher(),
|
||||||
|
entities,
|
||||||
|
)?;
|
||||||
tx.commit()?;
|
tx.commit()?;
|
||||||
details
|
details
|
||||||
};
|
};
|
||||||
|
@ -355,13 +415,30 @@ impl TestConn {
|
||||||
Ok(report)
|
Ok(report)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transact_simple_terms<I>(&mut self, terms: I, tempid_set: InternSet<TempId>) -> Result<TxReport> where I: IntoIterator<Item=TermWithTempIds> {
|
pub fn transact_simple_terms<I>(
|
||||||
|
&mut self,
|
||||||
|
terms: I,
|
||||||
|
tempid_set: InternSet<TempId>,
|
||||||
|
) -> Result<TxReport>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = TermWithTempIds>,
|
||||||
|
{
|
||||||
let details = {
|
let details = {
|
||||||
// The block scopes the borrow of self.sqlite.
|
// The block scopes the borrow of self.sqlite.
|
||||||
// We're about to write, so go straight ahead and get an IMMEDIATE transaction.
|
// We're about to write, so go straight ahead and get an IMMEDIATE transaction.
|
||||||
let tx = self.sqlite.transaction_with_behavior(TransactionBehavior::Immediate)?;
|
let tx = self
|
||||||
|
.sqlite
|
||||||
|
.transaction_with_behavior(TransactionBehavior::Immediate)?;
|
||||||
// Applying the transaction can fail, so we don't unwrap.
|
// Applying the transaction can fail, so we don't unwrap.
|
||||||
let details = transact_terms(&tx, self.partition_map.clone(), &self.schema, &self.schema, NullWatcher(), terms, tempid_set)?;
|
let details = transact_terms(
|
||||||
|
&tx,
|
||||||
|
self.partition_map.clone(),
|
||||||
|
&self.schema,
|
||||||
|
&self.schema,
|
||||||
|
NullWatcher(),
|
||||||
|
terms,
|
||||||
|
tempid_set,
|
||||||
|
)?;
|
||||||
tx.commit()?;
|
tx.commit()?;
|
||||||
details
|
details
|
||||||
};
|
};
|
||||||
|
@ -379,11 +456,19 @@ impl TestConn {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn last_tx_id(&self) -> Entid {
|
pub fn last_tx_id(&self) -> Entid {
|
||||||
self.partition_map.get(&":db.part/tx".to_string()).unwrap().next_entid() - 1
|
self.partition_map
|
||||||
|
.get(&":db.part/tx".to_string())
|
||||||
|
.unwrap()
|
||||||
|
.next_entid()
|
||||||
|
- 1
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn last_transaction(&self) -> Datoms {
|
pub fn last_transaction(&self) -> Datoms {
|
||||||
transactions_after(&self.sqlite, &self.schema, self.last_tx_id() - 1).expect("last_transaction").0.pop().unwrap()
|
transactions_after(&self.sqlite, &self.schema, self.last_tx_id() - 1)
|
||||||
|
.expect("last_transaction")
|
||||||
|
.0
|
||||||
|
.pop()
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transactions(&self) -> Transactions {
|
pub fn transactions(&self) -> Transactions {
|
||||||
|
|
|
@ -13,8 +13,7 @@
|
||||||
/// Literal `Entid` values in the the "db" namespace.
|
/// Literal `Entid` values in the the "db" namespace.
|
||||||
///
|
///
|
||||||
/// Used through-out the transactor to match core DB constructs.
|
/// Used through-out the transactor to match core DB constructs.
|
||||||
|
use core_traits::Entid;
|
||||||
use types::{Entid};
|
|
||||||
|
|
||||||
// Added in SQL schema v1.
|
// Added in SQL schema v1.
|
||||||
pub const DB_IDENT: Entid = 1;
|
pub const DB_IDENT: Entid = 1;
|
||||||
|
@ -62,9 +61,10 @@ pub const DB_SCHEMA_CORE: Entid = 40;
|
||||||
/// partitions in the partition map.
|
/// partitions in the partition map.
|
||||||
pub fn might_update_metadata(attribute: Entid) -> bool {
|
pub fn might_update_metadata(attribute: Entid) -> bool {
|
||||||
if attribute >= DB_DOC {
|
if attribute >= DB_DOC {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
match attribute {
|
matches!(
|
||||||
|
attribute,
|
||||||
// Idents.
|
// Idents.
|
||||||
DB_IDENT |
|
DB_IDENT |
|
||||||
// Schema.
|
// Schema.
|
||||||
|
@ -73,25 +73,22 @@ pub fn might_update_metadata(attribute: Entid) -> bool {
|
||||||
DB_INDEX |
|
DB_INDEX |
|
||||||
DB_IS_COMPONENT |
|
DB_IS_COMPONENT |
|
||||||
DB_UNIQUE |
|
DB_UNIQUE |
|
||||||
DB_VALUE_TYPE =>
|
DB_VALUE_TYPE
|
||||||
true,
|
)
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return 'false' if the given attribute might be used to describe a schema attribute.
|
/// Return 'false' if the given attribute might be used to describe a schema attribute.
|
||||||
pub fn is_a_schema_attribute(attribute: Entid) -> bool {
|
pub fn is_a_schema_attribute(attribute: Entid) -> bool {
|
||||||
match attribute {
|
matches!(
|
||||||
DB_IDENT |
|
attribute,
|
||||||
DB_CARDINALITY |
|
DB_IDENT
|
||||||
DB_FULLTEXT |
|
| DB_CARDINALITY
|
||||||
DB_INDEX |
|
| DB_FULLTEXT
|
||||||
DB_IS_COMPONENT |
|
| DB_INDEX
|
||||||
DB_UNIQUE |
|
| DB_IS_COMPONENT
|
||||||
DB_VALUE_TYPE =>
|
| DB_UNIQUE
|
||||||
true,
|
| DB_VALUE_TYPE
|
||||||
_ => false,
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
|
|
@ -12,48 +12,21 @@
|
||||||
|
|
||||||
//! Types used only within the transactor. These should not be exposed outside of this crate.
|
//! Types used only within the transactor. These should not be exposed outside of this crate.
|
||||||
|
|
||||||
use std::collections::{
|
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||||
BTreeMap,
|
|
||||||
BTreeSet,
|
|
||||||
HashMap,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mentat_core::KnownEntid;
|
use core_traits::{Attribute, Entid, KnownEntid, TypedValue, ValueType};
|
||||||
|
|
||||||
use mentat_core::util::Either;
|
use mentat_core::util::Either;
|
||||||
|
|
||||||
use edn;
|
use edn;
|
||||||
use edn::{
|
|
||||||
SpannedValue,
|
|
||||||
ValueAndSpan,
|
|
||||||
ValueRc,
|
|
||||||
};
|
|
||||||
use edn::entities;
|
use edn::entities;
|
||||||
use edn::entities::{
|
use edn::entities::{EntityPlace, OpType, TempId, TxFunction};
|
||||||
EntityPlace,
|
use edn::{SpannedValue, ValueAndSpan, ValueRc};
|
||||||
OpType,
|
|
||||||
TempId,
|
|
||||||
TxFunction,
|
|
||||||
};
|
|
||||||
|
|
||||||
use errors;
|
use crate::schema::SchemaTypeChecking;
|
||||||
use errors::{
|
use crate::types::{AVMap, AVPair, Schema, TransactableValue};
|
||||||
DbErrorKind,
|
use db_traits::errors;
|
||||||
Result,
|
use db_traits::errors::{DbErrorKind, Result};
|
||||||
};
|
|
||||||
use schema::{
|
|
||||||
SchemaTypeChecking,
|
|
||||||
};
|
|
||||||
use types::{
|
|
||||||
Attribute,
|
|
||||||
AVMap,
|
|
||||||
AVPair,
|
|
||||||
Entid,
|
|
||||||
Schema,
|
|
||||||
TransactableValue,
|
|
||||||
TypedValue,
|
|
||||||
ValueType,
|
|
||||||
};
|
|
||||||
|
|
||||||
impl TransactableValue for ValueAndSpan {
|
impl TransactableValue for ValueAndSpan {
|
||||||
fn into_typed_value(self, schema: &Schema, value_type: ValueType) -> Result<TypedValue> {
|
fn into_typed_value(self, schema: &Schema, value_type: ValueType) -> Result<TypedValue> {
|
||||||
|
@ -71,7 +44,7 @@ impl TransactableValue for ValueAndSpan {
|
||||||
// We only allow namespaced idents.
|
// We only allow namespaced idents.
|
||||||
bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace))
|
bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace))
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Text(v) => Ok(EntityPlace::TempId(TempId::External(v).into())),
|
Text(v) => Ok(EntityPlace::TempId(TempId::External(v).into())),
|
||||||
List(ls) => {
|
List(ls) => {
|
||||||
let mut it = ls.iter();
|
let mut it = ls.iter();
|
||||||
|
@ -79,35 +52,37 @@ impl TransactableValue for ValueAndSpan {
|
||||||
// Like "(transaction-id)".
|
// Like "(transaction-id)".
|
||||||
(Some(&PlainSymbol(ref op)), None, None, None) => {
|
(Some(&PlainSymbol(ref op)), None, None, None) => {
|
||||||
Ok(EntityPlace::TxFunction(TxFunction { op: op.clone() }))
|
Ok(EntityPlace::TxFunction(TxFunction { op: op.clone() }))
|
||||||
},
|
|
||||||
// Like "(lookup-ref)".
|
|
||||||
(Some(&PlainSymbol(edn::PlainSymbol(ref s))), Some(a), Some(v), None) if s == "lookup-ref" => {
|
|
||||||
match a.clone().into_entity_place()? {
|
|
||||||
EntityPlace::Entid(a) => Ok(EntityPlace::LookupRef(entities::LookupRef { a: entities::AttributePlace::Entid(a), v: v.clone() })),
|
|
||||||
EntityPlace::TempId(_) |
|
|
||||||
EntityPlace::TxFunction(_) |
|
|
||||||
EntityPlace::LookupRef(_) => bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace)),
|
|
||||||
}
|
}
|
||||||
},
|
// Like "(lookup-ref)".
|
||||||
|
(Some(&PlainSymbol(edn::PlainSymbol(ref s))), Some(a), Some(v), None)
|
||||||
|
if s == "lookup-ref" =>
|
||||||
|
{
|
||||||
|
match a.clone().into_entity_place()? {
|
||||||
|
EntityPlace::Entid(a) => {
|
||||||
|
Ok(EntityPlace::LookupRef(entities::LookupRef {
|
||||||
|
a: entities::AttributePlace::Entid(a),
|
||||||
|
v: v.clone(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
EntityPlace::TempId(_)
|
||||||
|
| EntityPlace::TxFunction(_)
|
||||||
|
| EntityPlace::LookupRef(_) => {
|
||||||
|
bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace)),
|
_ => bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace)),
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Nil |
|
Nil | Boolean(_) | Instant(_) | BigInteger(_) | Float(_) | Uuid(_) | PlainSymbol(_)
|
||||||
Boolean(_) |
|
| NamespacedSymbol(_) | Vector(_) | Set(_) | Map(_) | Bytes(_) => {
|
||||||
Instant(_) |
|
bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace))
|
||||||
BigInteger(_) |
|
}
|
||||||
Float(_) |
|
|
||||||
Uuid(_) |
|
|
||||||
PlainSymbol(_) |
|
|
||||||
NamespacedSymbol(_) |
|
|
||||||
Vector(_) |
|
|
||||||
Set(_) |
|
|
||||||
Map(_) => bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_tempid(&self) -> Option<TempId> {
|
fn as_tempid(&self) -> Option<TempId> {
|
||||||
self.inner.as_text().cloned().map(TempId::External).map(|v| v.into())
|
self.inner.as_text().cloned().map(TempId::External)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,19 +97,24 @@ impl TransactableValue for TypedValue {
|
||||||
fn into_entity_place(self) -> Result<EntityPlace<Self>> {
|
fn into_entity_place(self) -> Result<EntityPlace<Self>> {
|
||||||
match self {
|
match self {
|
||||||
TypedValue::Ref(x) => Ok(EntityPlace::Entid(entities::EntidOrIdent::Entid(x))),
|
TypedValue::Ref(x) => Ok(EntityPlace::Entid(entities::EntidOrIdent::Entid(x))),
|
||||||
TypedValue::Keyword(x) => Ok(EntityPlace::Entid(entities::EntidOrIdent::Ident((*x).clone()))),
|
TypedValue::Keyword(x) => Ok(EntityPlace::Entid(entities::EntidOrIdent::Ident(
|
||||||
|
(*x).clone(),
|
||||||
|
))),
|
||||||
TypedValue::String(x) => Ok(EntityPlace::TempId(TempId::External((*x).clone()).into())),
|
TypedValue::String(x) => Ok(EntityPlace::TempId(TempId::External((*x).clone()).into())),
|
||||||
TypedValue::Boolean(_) |
|
TypedValue::Boolean(_)
|
||||||
TypedValue::Long(_) |
|
| TypedValue::Long(_)
|
||||||
TypedValue::Double(_) |
|
| TypedValue::Double(_)
|
||||||
TypedValue::Instant(_) |
|
| TypedValue::Instant(_)
|
||||||
TypedValue::Uuid(_) => bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace)),
|
| TypedValue::Uuid(_)
|
||||||
|
| TypedValue::Bytes(_) => {
|
||||||
|
bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_tempid(&self) -> Option<TempId> {
|
fn as_tempid(&self) -> Option<TempId> {
|
||||||
match self {
|
match self {
|
||||||
&TypedValue::String(ref s) => Some(TempId::External((**s).clone()).into()),
|
TypedValue::String(ref s) => Some(TempId::External((**s).clone())),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -161,10 +141,11 @@ pub type LookupRef = ValueRc<AVPair>;
|
||||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||||
pub enum LookupRefOrTempId {
|
pub enum LookupRefOrTempId {
|
||||||
LookupRef(LookupRef),
|
LookupRef(LookupRef),
|
||||||
TempId(TempIdHandle)
|
TempId(TempIdHandle),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type TermWithTempIdsAndLookupRefs = Term<KnownEntidOr<LookupRefOrTempId>, TypedValueOr<LookupRefOrTempId>>;
|
pub type TermWithTempIdsAndLookupRefs =
|
||||||
|
Term<KnownEntidOr<LookupRefOrTempId>, TypedValueOr<LookupRefOrTempId>>;
|
||||||
pub type TermWithTempIds = Term<KnownEntidOr<TempIdHandle>, TypedValueOr<TempIdHandle>>;
|
pub type TermWithTempIds = Term<KnownEntidOr<TempIdHandle>, TypedValueOr<TempIdHandle>>;
|
||||||
pub type TermWithoutTempIds = Term<KnownEntid, TypedValue>;
|
pub type TermWithoutTempIds = Term<KnownEntid, TypedValue>;
|
||||||
pub type Population = Vec<TermWithTempIds>;
|
pub type Population = Vec<TermWithTempIds>;
|
||||||
|
@ -184,7 +165,7 @@ impl TermWithTempIds {
|
||||||
impl TermWithoutTempIds {
|
impl TermWithoutTempIds {
|
||||||
pub(crate) fn rewrap<A, B>(self) -> Term<KnownEntidOr<A>, TypedValueOr<B>> {
|
pub(crate) fn rewrap<A, B>(self) -> Term<KnownEntidOr<A>, TypedValueOr<B>> {
|
||||||
match self {
|
match self {
|
||||||
Term::AddOrRetract(op, n, a, v) => Term::AddOrRetract(op, Left(n), a, Left(v))
|
Term::AddOrRetract(op, n, a, v) => Term::AddOrRetract(op, Left(n), a, Left(v)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -198,16 +179,31 @@ impl TermWithoutTempIds {
|
||||||
/// The reason for this awkward expression is that we're parameterizing over the _type constructor_
|
/// The reason for this awkward expression is that we're parameterizing over the _type constructor_
|
||||||
/// (`EntidOr` or `TypedValueOr`), which is not trivial to express in Rust. This only works because
|
/// (`EntidOr` or `TypedValueOr`), which is not trivial to express in Rust. This only works because
|
||||||
/// they're both the same `Result<...>` type with different parameterizations.
|
/// they're both the same `Result<...>` type with different parameterizations.
|
||||||
pub fn replace_lookup_ref<T, U>(lookup_map: &AVMap, desired_or: Either<T, LookupRefOrTempId>, lift: U) -> errors::Result<Either<T, TempIdHandle>> where U: FnOnce(Entid) -> T {
|
pub fn replace_lookup_ref<T, U>(
|
||||||
|
lookup_map: &AVMap,
|
||||||
|
desired_or: Either<T, LookupRefOrTempId>,
|
||||||
|
lift: U,
|
||||||
|
) -> errors::Result<Either<T, TempIdHandle>>
|
||||||
|
where
|
||||||
|
U: FnOnce(Entid) -> T,
|
||||||
|
{
|
||||||
match desired_or {
|
match desired_or {
|
||||||
Left(desired) => Ok(Left(desired)), // N.b., must unwrap here -- the ::Left types are different!
|
Left(desired) => Ok(Left(desired)), // N.b., must unwrap here -- the ::Left types are different!
|
||||||
Right(other) => {
|
Right(other) => {
|
||||||
match other {
|
match other {
|
||||||
LookupRefOrTempId::TempId(t) => Ok(Right(t)),
|
LookupRefOrTempId::TempId(t) => Ok(Right(t)),
|
||||||
LookupRefOrTempId::LookupRef(av) => lookup_map.get(&*av)
|
LookupRefOrTempId::LookupRef(av) => lookup_map
|
||||||
.map(|x| lift(*x)).map(Left)
|
.get(&*av)
|
||||||
|
.map(|x| lift(*x))
|
||||||
|
.map(Left)
|
||||||
// XXX TODO: fix this error kind!
|
// XXX TODO: fix this error kind!
|
||||||
.ok_or_else(|| DbErrorKind::UnrecognizedIdent(format!("couldn't lookup [a v]: {:?}", (*av).clone())).into()),
|
.ok_or_else(|| {
|
||||||
|
DbErrorKind::UnrecognizedIdent(format!(
|
||||||
|
"couldn't lookup [a v]: {:?}",
|
||||||
|
(*av).clone()
|
||||||
|
))
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,4 +217,5 @@ pub(crate) struct AddAndRetract {
|
||||||
|
|
||||||
// A trie-like structure mapping a -> e -> v that prefix compresses and makes uniqueness constraint
|
// A trie-like structure mapping a -> e -> v that prefix compresses and makes uniqueness constraint
|
||||||
// checking more efficient. BTree* for deterministic errors.
|
// checking more efficient. BTree* for deterministic errors.
|
||||||
pub(crate) type AEVTrie<'schema> = BTreeMap<(Entid, &'schema Attribute), BTreeMap<Entid, AddAndRetract>>;
|
pub(crate) type AEVTrie<'schema> =
|
||||||
|
BTreeMap<(Entid, &'schema Attribute), BTreeMap<Entid, AddAndRetract>>;
|
||||||
|
|
|
@ -9,120 +9,93 @@
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
extern crate failure;
|
extern crate failure;
|
||||||
#[macro_use] extern crate failure_derive;
|
|
||||||
extern crate indexmap;
|
extern crate indexmap;
|
||||||
extern crate itertools;
|
extern crate itertools;
|
||||||
#[macro_use] extern crate lazy_static;
|
#[macro_use]
|
||||||
#[macro_use] extern crate log;
|
extern crate lazy_static;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
#[cfg(feature = "syncable")]
|
#[cfg(feature = "syncable")]
|
||||||
#[macro_use] extern crate serde_derive;
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
|
||||||
extern crate petgraph;
|
extern crate petgraph;
|
||||||
extern crate rusqlite;
|
extern crate rusqlite;
|
||||||
extern crate tabwriter;
|
extern crate tabwriter;
|
||||||
extern crate time;
|
extern crate time;
|
||||||
|
|
||||||
#[macro_use] extern crate edn;
|
#[macro_use]
|
||||||
#[macro_use] extern crate mentat_core;
|
extern crate edn;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate mentat_core;
|
||||||
|
extern crate db_traits;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate core_traits;
|
||||||
extern crate mentat_sql;
|
extern crate mentat_sql;
|
||||||
|
|
||||||
use std::iter::repeat;
|
use std::iter::repeat;
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
pub use errors::{
|
use db_traits::errors::{DbErrorKind, Result};
|
||||||
DbError,
|
|
||||||
DbErrorKind,
|
|
||||||
Result,
|
|
||||||
SchemaConstraintViolation,
|
|
||||||
};
|
|
||||||
#[macro_use] pub mod errors;
|
|
||||||
|
|
||||||
#[macro_use] pub mod debug;
|
#[macro_use]
|
||||||
|
pub mod debug;
|
||||||
|
|
||||||
mod add_retract_alter_set;
|
mod add_retract_alter_set;
|
||||||
|
mod bootstrap;
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
pub mod db;
|
pub mod db;
|
||||||
mod bootstrap;
|
|
||||||
pub mod entids;
|
pub mod entids;
|
||||||
pub mod internal_types; // pub because we need them for building entities programmatically.
|
pub mod internal_types; // pub because we need them for building entities programmatically.
|
||||||
mod metadata;
|
mod metadata;
|
||||||
mod schema;
|
mod schema;
|
||||||
pub mod tx_observer;
|
|
||||||
mod watcher;
|
|
||||||
pub mod timelines;
|
pub mod timelines;
|
||||||
mod tx;
|
mod tx;
|
||||||
mod tx_checking;
|
mod tx_checking;
|
||||||
|
pub mod tx_observer;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
mod upsert_resolution;
|
mod upsert_resolution;
|
||||||
|
mod watcher;
|
||||||
|
|
||||||
// Export these for reference from tests. cfg(test) should work, but doesn't.
|
// Export these for reference from sync code and tests.
|
||||||
// #[cfg(test)]
|
pub use crate::bootstrap::{TX0, USER0, V1_PARTS};
|
||||||
pub use bootstrap::{
|
|
||||||
TX0,
|
|
||||||
USER0,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub static TIMELINE_MAIN: i64 = 0;
|
pub static TIMELINE_MAIN: i64 = 0;
|
||||||
|
|
||||||
pub use schema::{
|
pub use crate::schema::{AttributeBuilder, AttributeValidation};
|
||||||
AttributeBuilder,
|
|
||||||
AttributeValidation,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use bootstrap::{
|
pub use crate::bootstrap::CORE_SCHEMA_VERSION;
|
||||||
CORE_SCHEMA_VERSION,
|
|
||||||
};
|
|
||||||
|
|
||||||
use edn::symbols;
|
use edn::symbols;
|
||||||
|
|
||||||
pub use entids::{
|
pub use crate::entids::DB_SCHEMA_CORE;
|
||||||
DB_SCHEMA_CORE,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use db::{
|
pub use crate::db::{new_connection, TypedSQLValue};
|
||||||
TypedSQLValue,
|
|
||||||
new_connection,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "sqlcipher")]
|
#[cfg(feature = "sqlcipher")]
|
||||||
pub use db::{
|
pub use db::{change_encryption_key, new_connection_with_key};
|
||||||
new_connection_with_key,
|
|
||||||
change_encryption_key,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use watcher::{
|
pub use crate::watcher::TransactWatcher;
|
||||||
TransactWatcher,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use tx::{
|
pub use crate::tx::{transact, transact_terms};
|
||||||
transact,
|
|
||||||
transact_terms,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use tx_observer::{
|
pub use crate::tx_observer::{InProgressObserverTransactWatcher, TxObservationService, TxObserver};
|
||||||
InProgressObserverTransactWatcher,
|
|
||||||
TxObservationService,
|
|
||||||
TxObserver,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use types::{
|
pub use crate::types::{AttributeSet, Partition, PartitionMap, TransactableValue, DB};
|
||||||
AttributeSet,
|
|
||||||
DB,
|
|
||||||
PartitionMap,
|
|
||||||
TransactableValue,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn to_namespaced_keyword(s: &str) -> Result<symbols::Keyword> {
|
pub fn to_namespaced_keyword(s: &str) -> Result<symbols::Keyword> {
|
||||||
let splits = [':', '/'];
|
let splits = [':', '/'];
|
||||||
let mut i = s.split(&splits[..]);
|
let mut i = s.split(&splits[..]);
|
||||||
let nsk = match (i.next(), i.next(), i.next(), i.next()) {
|
let nsk = match (i.next(), i.next(), i.next(), i.next()) {
|
||||||
(Some(""), Some(namespace), Some(name), None) => Some(symbols::Keyword::namespaced(namespace, name)),
|
(Some(""), Some(namespace), Some(name), None) => {
|
||||||
|
Some(symbols::Keyword::namespaced(namespace, name))
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
nsk.ok_or(DbErrorKind::NotYetImplemented(format!("InvalidKeyword: {}", s)).into())
|
nsk.ok_or_else(|| DbErrorKind::NotYetImplemented(format!("InvalidKeyword: {}", s)).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepare an SQL `VALUES` block, like (?, ?, ?), (?, ?, ?).
|
/// Prepare an SQL `VALUES` block, like (?, ?, ?), (?, ?, ?).
|
||||||
|
|
|
@ -26,35 +26,21 @@
|
||||||
|
|
||||||
use failure::ResultExt;
|
use failure::ResultExt;
|
||||||
|
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
|
||||||
use std::collections::btree_map::Entry;
|
use std::collections::btree_map::Entry;
|
||||||
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
|
||||||
use add_retract_alter_set::{
|
use crate::add_retract_alter_set::AddRetractAlterSet;
|
||||||
AddRetractAlterSet,
|
use crate::entids;
|
||||||
};
|
use db_traits::errors::{DbErrorKind, Result};
|
||||||
use edn::symbols;
|
use edn::symbols;
|
||||||
use entids;
|
|
||||||
use errors::{
|
|
||||||
DbErrorKind,
|
|
||||||
Result,
|
|
||||||
};
|
|
||||||
use mentat_core::{
|
|
||||||
attribute,
|
|
||||||
Entid,
|
|
||||||
Schema,
|
|
||||||
AttributeMap,
|
|
||||||
TypedValue,
|
|
||||||
ValueType,
|
|
||||||
};
|
|
||||||
|
|
||||||
use schema::{
|
use core_traits::{attribute, Entid, TypedValue, ValueType};
|
||||||
AttributeBuilder,
|
|
||||||
AttributeValidation,
|
|
||||||
};
|
|
||||||
|
|
||||||
use types::{
|
use mentat_core::{AttributeMap, Schema};
|
||||||
EAV,
|
|
||||||
};
|
use crate::schema::{AttributeBuilder, AttributeValidation};
|
||||||
|
|
||||||
|
use crate::types::EAV;
|
||||||
|
|
||||||
/// An alteration to an attribute.
|
/// An alteration to an attribute.
|
||||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||||
|
@ -96,8 +82,7 @@ pub struct MetadataReport {
|
||||||
|
|
||||||
impl MetadataReport {
|
impl MetadataReport {
|
||||||
pub fn attributes_did_change(&self) -> bool {
|
pub fn attributes_did_change(&self) -> bool {
|
||||||
!(self.attributes_installed.is_empty() &&
|
!(self.attributes_installed.is_empty() && self.attributes_altered.is_empty())
|
||||||
self.attributes_altered.is_empty())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +96,11 @@ impl MetadataReport {
|
||||||
/// - we're allowing optional attributes to not be retracted and dangle afterwards
|
/// - we're allowing optional attributes to not be retracted and dangle afterwards
|
||||||
///
|
///
|
||||||
/// Returns a set of attribute retractions which do not involve schema-defining attributes.
|
/// Returns a set of attribute retractions which do not involve schema-defining attributes.
|
||||||
fn update_attribute_map_from_schema_retractions(attribute_map: &mut AttributeMap, retractions: Vec<EAV>, ident_retractions: &BTreeMap<Entid, symbols::Keyword>) -> Result<Vec<EAV>> {
|
fn update_attribute_map_from_schema_retractions(
|
||||||
|
attribute_map: &mut AttributeMap,
|
||||||
|
retractions: Vec<EAV>,
|
||||||
|
ident_retractions: &BTreeMap<Entid, symbols::Keyword>,
|
||||||
|
) -> Result<Vec<EAV>> {
|
||||||
// Process retractions of schema attributes first. It's allowed to retract a schema attribute
|
// Process retractions of schema attributes first. It's allowed to retract a schema attribute
|
||||||
// if all of the schema-defining schema attributes are being retracted.
|
// if all of the schema-defining schema attributes are being retracted.
|
||||||
// A defining set of attributes is :db/ident, :db/valueType, :db/cardinality.
|
// A defining set of attributes is :db/ident, :db/valueType, :db/cardinality.
|
||||||
|
@ -122,7 +111,7 @@ fn update_attribute_map_from_schema_retractions(attribute_map: &mut AttributeMap
|
||||||
let mut eas = BTreeMap::new();
|
let mut eas = BTreeMap::new();
|
||||||
for (e, a, v) in retractions.into_iter() {
|
for (e, a, v) in retractions.into_iter() {
|
||||||
if entids::is_a_schema_attribute(a) {
|
if entids::is_a_schema_attribute(a) {
|
||||||
eas.entry(e).or_insert(vec![]).push(a);
|
eas.entry(e).or_insert_with(Vec::new).push(a);
|
||||||
suspect_retractions.push((e, a, v));
|
suspect_retractions.push((e, a, v));
|
||||||
} else {
|
} else {
|
||||||
filtered_retractions.push((e, a, v));
|
filtered_retractions.push((e, a, v));
|
||||||
|
@ -148,13 +137,15 @@ fn update_attribute_map_from_schema_retractions(attribute_map: &mut AttributeMap
|
||||||
let attributes = eas.get(&e).unwrap();
|
let attributes = eas.get(&e).unwrap();
|
||||||
|
|
||||||
// Found a set of retractions which negate a schema.
|
// Found a set of retractions which negate a schema.
|
||||||
if attributes.contains(&entids::DB_CARDINALITY) && attributes.contains(&entids::DB_VALUE_TYPE) {
|
if attributes.contains(&entids::DB_CARDINALITY)
|
||||||
|
&& attributes.contains(&entids::DB_VALUE_TYPE)
|
||||||
|
{
|
||||||
// Ensure that corresponding :db/ident is also being retracted at the same time.
|
// Ensure that corresponding :db/ident is also being retracted at the same time.
|
||||||
if ident_retractions.contains_key(&e) {
|
if ident_retractions.contains_key(&e) {
|
||||||
// Remove attributes corresponding to retracted attribute.
|
// Remove attributes corresponding to retracted attribute.
|
||||||
attribute_map.remove(&e);
|
attribute_map.remove(&e);
|
||||||
} else {
|
} else {
|
||||||
bail!(DbErrorKind::BadSchemaAssertion(format!("Retracting defining attributes of a schema without retracting its :db/ident is not permitted.")));
|
bail!(DbErrorKind::BadSchemaAssertion("Retracting defining attributes of a schema without retracting its :db/ident is not permitted.".to_string()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
filtered_retractions.push((e, a, v));
|
filtered_retractions.push((e, a, v));
|
||||||
|
@ -170,10 +161,18 @@ fn update_attribute_map_from_schema_retractions(attribute_map: &mut AttributeMap
|
||||||
/// contain install and alter markers.
|
/// contain install and alter markers.
|
||||||
///
|
///
|
||||||
/// Returns a report summarizing the mutations that were applied.
|
/// Returns a report summarizing the mutations that were applied.
|
||||||
pub fn update_attribute_map_from_entid_triples(attribute_map: &mut AttributeMap, assertions: Vec<EAV>, retractions: Vec<EAV>) -> Result<MetadataReport> {
|
pub fn update_attribute_map_from_entid_triples(
|
||||||
fn attribute_builder_to_modify(attribute_id: Entid, existing: &AttributeMap) -> AttributeBuilder {
|
attribute_map: &mut AttributeMap,
|
||||||
existing.get(&attribute_id)
|
assertions: Vec<EAV>,
|
||||||
.map(AttributeBuilder::to_modify_attribute)
|
retractions: Vec<EAV>,
|
||||||
|
) -> Result<MetadataReport> {
|
||||||
|
fn attribute_builder_to_modify(
|
||||||
|
attribute_id: Entid,
|
||||||
|
existing: &AttributeMap,
|
||||||
|
) -> AttributeBuilder {
|
||||||
|
existing
|
||||||
|
.get(&attribute_id)
|
||||||
|
.map(AttributeBuilder::modify_attribute)
|
||||||
.unwrap_or_else(AttributeBuilder::default)
|
.unwrap_or_else(AttributeBuilder::default)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,7 +182,9 @@ pub fn update_attribute_map_from_entid_triples(attribute_map: &mut AttributeMap,
|
||||||
// For retractions, we start with an attribute builder that's pre-populated with the existing
|
// For retractions, we start with an attribute builder that's pre-populated with the existing
|
||||||
// attribute values. That allows us to check existing values and unset them.
|
// attribute values. That allows us to check existing values and unset them.
|
||||||
for (entid, attr, ref value) in retractions {
|
for (entid, attr, ref value) in retractions {
|
||||||
let builder = builders.entry(entid).or_insert_with(|| attribute_builder_to_modify(entid, attribute_map));
|
let builder = builders
|
||||||
|
.entry(entid)
|
||||||
|
.or_insert_with(|| attribute_builder_to_modify(entid, attribute_map));
|
||||||
match attr {
|
match attr {
|
||||||
// You can only retract :db/unique, :db/isComponent; all others must be altered instead
|
// You can only retract :db/unique, :db/isComponent; all others must be altered instead
|
||||||
// of retracted, or are not allowed to change.
|
// of retracted, or are not allowed to change.
|
||||||
|
@ -247,6 +248,7 @@ pub fn update_attribute_map_from_entid_triples(attribute_map: &mut AttributeMap,
|
||||||
TypedValue::Ref(entids::DB_TYPE_REF) => { builder.value_type(ValueType::Ref); },
|
TypedValue::Ref(entids::DB_TYPE_REF) => { builder.value_type(ValueType::Ref); },
|
||||||
TypedValue::Ref(entids::DB_TYPE_STRING) => { builder.value_type(ValueType::String); },
|
TypedValue::Ref(entids::DB_TYPE_STRING) => { builder.value_type(ValueType::String); },
|
||||||
TypedValue::Ref(entids::DB_TYPE_UUID) => { builder.value_type(ValueType::Uuid); },
|
TypedValue::Ref(entids::DB_TYPE_UUID) => { builder.value_type(ValueType::Uuid); },
|
||||||
|
TypedValue::Ref(entids::DB_TYPE_BYTES) => { builder.value_type(ValueType::Bytes); },
|
||||||
_ => bail!(DbErrorKind::BadSchemaAssertion(format!("Expected [... :db/valueType :db.type/*] but got [... :db/valueType {:?}] for entid {} and attribute {}", value, entid, attr)))
|
_ => bail!(DbErrorKind::BadSchemaAssertion(format!("Expected [... :db/valueType :db.type/*] but got [... :db/valueType {:?}] for entid {} and attribute {}", value, entid, attr)))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -299,7 +301,7 @@ pub fn update_attribute_map_from_entid_triples(attribute_map: &mut AttributeMap,
|
||||||
bail!(DbErrorKind::BadSchemaAssertion(format!("Do not recognize attribute {} for entid {}", attr, entid)))
|
bail!(DbErrorKind::BadSchemaAssertion(format!("Do not recognize attribute {} for entid {}", attr, entid)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
let mut attributes_installed: BTreeSet<Entid> = BTreeSet::default();
|
let mut attributes_installed: BTreeSet<Entid> = BTreeSet::default();
|
||||||
let mut attributes_altered: BTreeMap<Entid, Vec<AttributeAlteration>> = BTreeMap::default();
|
let mut attributes_altered: BTreeMap<Entid, Vec<AttributeAlteration>> = BTreeMap::default();
|
||||||
|
@ -308,26 +310,36 @@ pub fn update_attribute_map_from_entid_triples(attribute_map: &mut AttributeMap,
|
||||||
match attribute_map.entry(entid) {
|
match attribute_map.entry(entid) {
|
||||||
Entry::Vacant(entry) => {
|
Entry::Vacant(entry) => {
|
||||||
// Validate once…
|
// Validate once…
|
||||||
builder.validate_install_attribute().context(DbErrorKind::BadSchemaAssertion(format!("Schema alteration for new attribute with entid {} is not valid", entid)))?;
|
builder
|
||||||
|
.validate_install_attribute()
|
||||||
|
.context(DbErrorKind::BadSchemaAssertion(format!(
|
||||||
|
"Schema alteration for new attribute with entid {} is not valid",
|
||||||
|
entid
|
||||||
|
)))?;
|
||||||
|
|
||||||
// … and twice, now we have the Attribute.
|
// … and twice, now we have the Attribute.
|
||||||
let a = builder.build();
|
let a = builder.build();
|
||||||
a.validate(|| entid.to_string())?;
|
a.validate(|| entid.to_string())?;
|
||||||
entry.insert(a);
|
entry.insert(a);
|
||||||
attributes_installed.insert(entid);
|
attributes_installed.insert(entid);
|
||||||
},
|
}
|
||||||
|
|
||||||
Entry::Occupied(mut entry) => {
|
Entry::Occupied(mut entry) => {
|
||||||
builder.validate_alter_attribute().context(DbErrorKind::BadSchemaAssertion(format!("Schema alteration for existing attribute with entid {} is not valid", entid)))?;
|
builder
|
||||||
|
.validate_alter_attribute()
|
||||||
|
.context(DbErrorKind::BadSchemaAssertion(format!(
|
||||||
|
"Schema alteration for existing attribute with entid {} is not valid",
|
||||||
|
entid
|
||||||
|
)))?;
|
||||||
let mutations = builder.mutate(entry.get_mut());
|
let mutations = builder.mutate(entry.get_mut());
|
||||||
attributes_altered.insert(entid, mutations);
|
attributes_altered.insert(entid, mutations);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(MetadataReport {
|
Ok(MetadataReport {
|
||||||
attributes_installed: attributes_installed,
|
attributes_installed,
|
||||||
attributes_altered: attributes_altered,
|
attributes_altered,
|
||||||
idents_altered: BTreeMap::default(),
|
idents_altered: BTreeMap::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -340,14 +352,19 @@ pub fn update_attribute_map_from_entid_triples(attribute_map: &mut AttributeMap,
|
||||||
/// This is suitable for mutating a `Schema` from an applied transaction.
|
/// This is suitable for mutating a `Schema` from an applied transaction.
|
||||||
///
|
///
|
||||||
/// Returns a report summarizing the mutations that were applied.
|
/// Returns a report summarizing the mutations that were applied.
|
||||||
pub fn update_schema_from_entid_quadruples<U>(schema: &mut Schema, assertions: U) -> Result<MetadataReport>
|
pub fn update_schema_from_entid_quadruples<U>(
|
||||||
where U: IntoIterator<Item=(Entid, Entid, TypedValue, bool)> {
|
schema: &mut Schema,
|
||||||
|
assertions: U,
|
||||||
|
) -> Result<MetadataReport>
|
||||||
|
where
|
||||||
|
U: IntoIterator<Item = (Entid, Entid, TypedValue, bool)>,
|
||||||
|
{
|
||||||
// Group attribute assertions into asserted, retracted, and updated. We assume all our
|
// Group attribute assertions into asserted, retracted, and updated. We assume all our
|
||||||
// attribute assertions are :db/cardinality :db.cardinality/one (so they'll only be added or
|
// attribute assertions are :db/cardinality :db.cardinality/one (so they'll only be added or
|
||||||
// retracted at most once), which means all attribute alterations are simple changes from an old
|
// retracted at most once), which means all attribute alterations are simple changes from an old
|
||||||
// value to a new value.
|
// value to a new value.
|
||||||
let mut attribute_set: AddRetractAlterSet<(Entid, Entid), TypedValue> = AddRetractAlterSet::default();
|
let mut attribute_set: AddRetractAlterSet<(Entid, Entid), TypedValue> =
|
||||||
|
AddRetractAlterSet::default();
|
||||||
let mut ident_set: AddRetractAlterSet<Entid, symbols::Keyword> = AddRetractAlterSet::default();
|
let mut ident_set: AddRetractAlterSet<Entid, symbols::Keyword> = AddRetractAlterSet::default();
|
||||||
|
|
||||||
for (e, a, typed_value, added) in assertions.into_iter() {
|
for (e, a, typed_value, added) in assertions.into_iter() {
|
||||||
|
@ -355,7 +372,7 @@ pub fn update_schema_from_entid_quadruples<U>(schema: &mut Schema, assertions: U
|
||||||
if a == entids::DB_IDENT {
|
if a == entids::DB_IDENT {
|
||||||
if let TypedValue::Keyword(ref keyword) = typed_value {
|
if let TypedValue::Keyword(ref keyword) = typed_value {
|
||||||
ident_set.witness(e, keyword.as_ref().clone(), added);
|
ident_set.witness(e, keyword.as_ref().clone(), added);
|
||||||
continue
|
continue;
|
||||||
} else {
|
} else {
|
||||||
// Something is terribly wrong: the schema ensures we have a keyword.
|
// Something is terribly wrong: the schema ensures we have a keyword.
|
||||||
unreachable!();
|
unreachable!();
|
||||||
|
@ -366,20 +383,33 @@ pub fn update_schema_from_entid_quadruples<U>(schema: &mut Schema, assertions: U
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect triples.
|
// Collect triples.
|
||||||
let retracted_triples = attribute_set.retracted.into_iter().map(|((e, a), typed_value)| (e, a, typed_value));
|
let retracted_triples = attribute_set
|
||||||
let asserted_triples = attribute_set.asserted.into_iter().map(|((e, a), typed_value)| (e, a, typed_value));
|
.retracted
|
||||||
let altered_triples = attribute_set.altered.into_iter().map(|((e, a), (_old_value, new_value))| (e, a, new_value));
|
.into_iter()
|
||||||
|
.map(|((e, a), typed_value)| (e, a, typed_value));
|
||||||
|
let asserted_triples = attribute_set
|
||||||
|
.asserted
|
||||||
|
.into_iter()
|
||||||
|
.map(|((e, a), typed_value)| (e, a, typed_value));
|
||||||
|
let altered_triples = attribute_set
|
||||||
|
.altered
|
||||||
|
.into_iter()
|
||||||
|
.map(|((e, a), (_old_value, new_value))| (e, a, new_value));
|
||||||
|
|
||||||
// First we process retractions which remove schema.
|
// First we process retractions which remove schema.
|
||||||
// This operation consumes our current list of attribute retractions, producing a filtered one.
|
// This operation consumes our current list of attribute retractions, producing a filtered one.
|
||||||
let non_schema_retractions = update_attribute_map_from_schema_retractions(&mut schema.attribute_map,
|
let non_schema_retractions = update_attribute_map_from_schema_retractions(
|
||||||
|
&mut schema.attribute_map,
|
||||||
retracted_triples.collect(),
|
retracted_triples.collect(),
|
||||||
&ident_set.retracted)?;
|
&ident_set.retracted,
|
||||||
|
)?;
|
||||||
|
|
||||||
// Now we process all other retractions.
|
// Now we process all other retractions.
|
||||||
let report = update_attribute_map_from_entid_triples(&mut schema.attribute_map,
|
let report = update_attribute_map_from_entid_triples(
|
||||||
|
&mut schema.attribute_map,
|
||||||
asserted_triples.chain(altered_triples).collect(),
|
asserted_triples.chain(altered_triples).collect(),
|
||||||
non_schema_retractions)?;
|
non_schema_retractions,
|
||||||
|
)?;
|
||||||
|
|
||||||
let mut idents_altered: BTreeMap<Entid, IdentAlteration> = BTreeMap::new();
|
let mut idents_altered: BTreeMap<Entid, IdentAlteration> = BTreeMap::new();
|
||||||
|
|
||||||
|
@ -410,12 +440,12 @@ pub fn update_schema_from_entid_quadruples<U>(schema: &mut Schema, assertions: U
|
||||||
// component_attributes up-to-date: most of the time we'll rebuild it
|
// component_attributes up-to-date: most of the time we'll rebuild it
|
||||||
// even though it's not necessary (e.g. a schema attribute that's _not_
|
// even though it's not necessary (e.g. a schema attribute that's _not_
|
||||||
// a component was removed, or a non-component related attribute changed).
|
// a component was removed, or a non-component related attribute changed).
|
||||||
if report.attributes_did_change() || ident_set.retracted.len() > 0 {
|
if report.attributes_did_change() || !ident_set.retracted.is_empty() {
|
||||||
schema.update_component_attributes();
|
schema.update_component_attributes();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(MetadataReport {
|
Ok(MetadataReport {
|
||||||
idents_altered: idents_altered,
|
idents_altered,
|
||||||
..report
|
..report
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
360
db/src/schema.rs
360
db/src/schema.rs
|
@ -10,51 +10,57 @@
|
||||||
|
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use db::TypedSQLValue;
|
use crate::db::TypedSQLValue;
|
||||||
|
use db_traits::errors::{DbErrorKind, Result};
|
||||||
use edn;
|
use edn;
|
||||||
use errors::{
|
|
||||||
DbErrorKind,
|
|
||||||
Result,
|
|
||||||
};
|
|
||||||
use edn::symbols;
|
use edn::symbols;
|
||||||
use mentat_core::{
|
|
||||||
attribute,
|
use core_traits::{attribute, Attribute, Entid, KnownEntid, TypedValue, ValueType};
|
||||||
Attribute,
|
|
||||||
Entid,
|
use crate::metadata;
|
||||||
EntidMap,
|
use crate::metadata::AttributeAlteration;
|
||||||
HasSchema,
|
use mentat_core::{AttributeMap, EntidMap, HasSchema, IdentMap, Schema};
|
||||||
IdentMap,
|
|
||||||
KnownEntid,
|
|
||||||
Schema,
|
|
||||||
AttributeMap,
|
|
||||||
TypedValue,
|
|
||||||
ValueType,
|
|
||||||
};
|
|
||||||
use metadata;
|
|
||||||
use metadata::{
|
|
||||||
AttributeAlteration,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub trait AttributeValidation {
|
pub trait AttributeValidation {
|
||||||
fn validate<F>(&self, ident: F) -> Result<()> where F: Fn() -> String;
|
fn validate<F>(&self, ident: F) -> Result<()>
|
||||||
|
where
|
||||||
|
F: Fn() -> String;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AttributeValidation for Attribute {
|
impl AttributeValidation for Attribute {
|
||||||
fn validate<F>(&self, ident: F) -> Result<()> where F: Fn() -> String {
|
fn validate<F>(&self, ident: F) -> Result<()>
|
||||||
|
where
|
||||||
|
F: Fn() -> String,
|
||||||
|
{
|
||||||
if self.unique == Some(attribute::Unique::Value) && !self.index {
|
if self.unique == Some(attribute::Unique::Value) && !self.index {
|
||||||
bail!(DbErrorKind::BadSchemaAssertion(format!(":db/unique :db/unique_value without :db/index true for entid: {}", ident())))
|
bail!(DbErrorKind::BadSchemaAssertion(format!(
|
||||||
|
":db/unique :db/unique_value without :db/index true for entid: {}",
|
||||||
|
ident()
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
if self.unique == Some(attribute::Unique::Identity) && !self.index {
|
if self.unique == Some(attribute::Unique::Identity) && !self.index {
|
||||||
bail!(DbErrorKind::BadSchemaAssertion(format!(":db/unique :db/unique_identity without :db/index true for entid: {}", ident())))
|
bail!(DbErrorKind::BadSchemaAssertion(format!(
|
||||||
|
":db/unique :db/unique_identity without :db/index true for entid: {}",
|
||||||
|
ident()
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
if self.fulltext && self.value_type != ValueType::String {
|
if self.fulltext && self.value_type != ValueType::String {
|
||||||
bail!(DbErrorKind::BadSchemaAssertion(format!(":db/fulltext true without :db/valueType :db.type/string for entid: {}", ident())))
|
bail!(DbErrorKind::BadSchemaAssertion(format!(
|
||||||
|
":db/fulltext true without :db/valueType :db.type/string for entid: {}",
|
||||||
|
ident()
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
if self.fulltext && !self.index {
|
if self.fulltext && !self.index {
|
||||||
bail!(DbErrorKind::BadSchemaAssertion(format!(":db/fulltext true without :db/index true for entid: {}", ident())))
|
bail!(DbErrorKind::BadSchemaAssertion(format!(
|
||||||
|
":db/fulltext true without :db/index true for entid: {}",
|
||||||
|
ident()
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
if self.component && self.value_type != ValueType::Ref {
|
if self.component && self.value_type != ValueType::Ref {
|
||||||
bail!(DbErrorKind::BadSchemaAssertion(format!(":db/isComponent true without :db/valueType :db.type/ref for entid: {}", ident())))
|
bail!(DbErrorKind::BadSchemaAssertion(format!(
|
||||||
|
":db/isComponent true without :db/valueType :db.type/ref for entid: {}",
|
||||||
|
ident()
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
// TODO: consider warning if we have :db/index true for :db/valueType :db.type/string,
|
// TODO: consider warning if we have :db/index true for :db/valueType :db.type/string,
|
||||||
// since this may be inefficient. More generally, we should try to drive complex
|
// since this may be inefficient. More generally, we should try to drive complex
|
||||||
|
@ -67,7 +73,12 @@ impl AttributeValidation for Attribute {
|
||||||
/// Return `Ok(())` if `attribute_map` defines a valid Mentat schema.
|
/// Return `Ok(())` if `attribute_map` defines a valid Mentat schema.
|
||||||
fn validate_attribute_map(entid_map: &EntidMap, attribute_map: &AttributeMap) -> Result<()> {
|
fn validate_attribute_map(entid_map: &EntidMap, attribute_map: &AttributeMap) -> Result<()> {
|
||||||
for (entid, attribute) in attribute_map {
|
for (entid, attribute) in attribute_map {
|
||||||
let ident = || entid_map.get(entid).map(|ident| ident.to_string()).unwrap_or(entid.to_string());
|
let ident = || {
|
||||||
|
entid_map
|
||||||
|
.get(entid)
|
||||||
|
.map(|ident| ident.to_string())
|
||||||
|
.unwrap_or_else(|| entid.to_string())
|
||||||
|
};
|
||||||
attribute.validate(ident)?;
|
attribute.validate(ident)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -97,7 +108,7 @@ impl AttributeBuilder {
|
||||||
|
|
||||||
/// Make a new AttributeBuilder from an existing Attribute. This is important to allow
|
/// Make a new AttributeBuilder from an existing Attribute. This is important to allow
|
||||||
/// retraction. Only attributes that we allow to change are duplicated here.
|
/// retraction. Only attributes that we allow to change are duplicated here.
|
||||||
pub fn to_modify_attribute(attribute: &Attribute) -> Self {
|
pub fn modify_attribute(attribute: &Attribute) -> Self {
|
||||||
let mut ab = AttributeBuilder::default();
|
let mut ab = AttributeBuilder::default();
|
||||||
ab.multival = Some(attribute.multival);
|
ab.multival = Some(attribute.multival);
|
||||||
ab.unique = Some(attribute.unique);
|
ab.unique = Some(attribute.unique);
|
||||||
|
@ -105,22 +116,22 @@ impl AttributeBuilder {
|
||||||
ab
|
ab
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn value_type<'a>(&'a mut self, value_type: ValueType) -> &'a mut Self {
|
pub fn value_type(&mut self, value_type: ValueType) -> &mut Self {
|
||||||
self.value_type = Some(value_type);
|
self.value_type = Some(value_type);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn multival<'a>(&'a mut self, multival: bool) -> &'a mut Self {
|
pub fn multival(&mut self, multival: bool) -> &mut Self {
|
||||||
self.multival = Some(multival);
|
self.multival = Some(multival);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn non_unique<'a>(&'a mut self) -> &'a mut Self {
|
pub fn non_unique(&mut self) -> &mut Self {
|
||||||
self.unique = Some(None);
|
self.unique = Some(None);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unique<'a>(&'a mut self, unique: attribute::Unique) -> &'a mut Self {
|
pub fn unique(&mut self, unique: attribute::Unique) -> &mut Self {
|
||||||
if self.helpful && unique == attribute::Unique::Identity {
|
if self.helpful && unique == attribute::Unique::Identity {
|
||||||
self.index = Some(true);
|
self.index = Some(true);
|
||||||
}
|
}
|
||||||
|
@ -128,12 +139,12 @@ impl AttributeBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn index<'a>(&'a mut self, index: bool) -> &'a mut Self {
|
pub fn index(&mut self, index: bool) -> &mut Self {
|
||||||
self.index = Some(index);
|
self.index = Some(index);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fulltext<'a>(&'a mut self, fulltext: bool) -> &'a mut Self {
|
pub fn fulltext(&mut self, fulltext: bool) -> &mut Self {
|
||||||
self.fulltext = Some(fulltext);
|
self.fulltext = Some(fulltext);
|
||||||
if self.helpful && fulltext {
|
if self.helpful && fulltext {
|
||||||
self.index = Some(true);
|
self.index = Some(true);
|
||||||
|
@ -141,29 +152,35 @@ impl AttributeBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn component<'a>(&'a mut self, component: bool) -> &'a mut Self {
|
pub fn component(&mut self, component: bool) -> &mut Self {
|
||||||
self.component = Some(component);
|
self.component = Some(component);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn no_history<'a>(&'a mut self, no_history: bool) -> &'a mut Self {
|
pub fn no_history(&mut self, no_history: bool) -> &mut Self {
|
||||||
self.no_history = Some(no_history);
|
self.no_history = Some(no_history);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate_install_attribute(&self) -> Result<()> {
|
pub fn validate_install_attribute(&self) -> Result<()> {
|
||||||
if self.value_type.is_none() {
|
if self.value_type.is_none() {
|
||||||
bail!(DbErrorKind::BadSchemaAssertion("Schema attribute for new attribute does not set :db/valueType".into()));
|
bail!(DbErrorKind::BadSchemaAssertion(
|
||||||
|
"Schema attribute for new attribute does not set :db/valueType".into()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate_alter_attribute(&self) -> Result<()> {
|
pub fn validate_alter_attribute(&self) -> Result<()> {
|
||||||
if self.value_type.is_some() {
|
if self.value_type.is_some() {
|
||||||
bail!(DbErrorKind::BadSchemaAssertion("Schema alteration must not set :db/valueType".into()));
|
bail!(DbErrorKind::BadSchemaAssertion(
|
||||||
|
"Schema alteration must not set :db/valueType".into()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
if self.fulltext.is_some() {
|
if self.fulltext.is_some() {
|
||||||
bail!(DbErrorKind::BadSchemaAssertion("Schema alteration must not set :db/fulltext".into()));
|
bail!(DbErrorKind::BadSchemaAssertion(
|
||||||
|
"Schema alteration must not set :db/fulltext".into()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -180,7 +197,7 @@ impl AttributeBuilder {
|
||||||
attribute.multival = multival;
|
attribute.multival = multival;
|
||||||
}
|
}
|
||||||
if let Some(ref unique) = self.unique {
|
if let Some(ref unique) = self.unique {
|
||||||
attribute.unique = unique.clone();
|
attribute.unique = *unique;
|
||||||
}
|
}
|
||||||
if let Some(index) = self.index {
|
if let Some(index) = self.index {
|
||||||
attribute.index = index;
|
attribute.index = index;
|
||||||
|
@ -206,15 +223,13 @@ impl AttributeBuilder {
|
||||||
|
|
||||||
if let Some(ref unique) = self.unique {
|
if let Some(ref unique) = self.unique {
|
||||||
if *unique != attribute.unique {
|
if *unique != attribute.unique {
|
||||||
attribute.unique = unique.clone();
|
attribute.unique = *unique;
|
||||||
mutations.push(AttributeAlteration::Unique);
|
mutations.push(AttributeAlteration::Unique);
|
||||||
}
|
}
|
||||||
} else {
|
} else if attribute.unique != None {
|
||||||
if attribute.unique != None {
|
|
||||||
attribute.unique = None;
|
attribute.unique = None;
|
||||||
mutations.push(AttributeAlteration::Unique);
|
mutations.push(AttributeAlteration::Unique);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(index) = self.index {
|
if let Some(index) = self.index {
|
||||||
if index != attribute.index {
|
if index != attribute.index {
|
||||||
|
@ -243,27 +258,37 @@ pub trait SchemaBuilding {
|
||||||
fn require_ident(&self, entid: Entid) -> Result<&symbols::Keyword>;
|
fn require_ident(&self, entid: Entid) -> Result<&symbols::Keyword>;
|
||||||
fn require_entid(&self, ident: &symbols::Keyword) -> Result<KnownEntid>;
|
fn require_entid(&self, ident: &symbols::Keyword) -> Result<KnownEntid>;
|
||||||
fn require_attribute_for_entid(&self, entid: Entid) -> Result<&Attribute>;
|
fn require_attribute_for_entid(&self, entid: Entid) -> Result<&Attribute>;
|
||||||
fn from_ident_map_and_attribute_map(ident_map: IdentMap, attribute_map: AttributeMap) -> Result<Schema>;
|
fn from_ident_map_and_attribute_map(
|
||||||
|
ident_map: IdentMap,
|
||||||
|
attribute_map: AttributeMap,
|
||||||
|
) -> Result<Schema>;
|
||||||
fn from_ident_map_and_triples<U>(ident_map: IdentMap, assertions: U) -> Result<Schema>
|
fn from_ident_map_and_triples<U>(ident_map: IdentMap, assertions: U) -> Result<Schema>
|
||||||
where U: IntoIterator<Item=(symbols::Keyword, symbols::Keyword, TypedValue)>;
|
where
|
||||||
|
U: IntoIterator<Item = (symbols::Keyword, symbols::Keyword, TypedValue)>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SchemaBuilding for Schema {
|
impl SchemaBuilding for Schema {
|
||||||
fn require_ident(&self, entid: Entid) -> Result<&symbols::Keyword> {
|
fn require_ident(&self, entid: Entid) -> Result<&symbols::Keyword> {
|
||||||
self.get_ident(entid).ok_or(DbErrorKind::UnrecognizedEntid(entid).into())
|
self.get_ident(entid)
|
||||||
|
.ok_or_else(|| DbErrorKind::UnrecognizedEntid(entid).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn require_entid(&self, ident: &symbols::Keyword) -> Result<KnownEntid> {
|
fn require_entid(&self, ident: &symbols::Keyword) -> Result<KnownEntid> {
|
||||||
self.get_entid(&ident).ok_or(DbErrorKind::UnrecognizedIdent(ident.to_string()).into())
|
self.get_entid(&ident)
|
||||||
|
.ok_or_else(|| DbErrorKind::UnrecognizedIdent(ident.to_string()).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn require_attribute_for_entid(&self, entid: Entid) -> Result<&Attribute> {
|
fn require_attribute_for_entid(&self, entid: Entid) -> Result<&Attribute> {
|
||||||
self.attribute_for_entid(entid).ok_or(DbErrorKind::UnrecognizedEntid(entid).into())
|
self.attribute_for_entid(entid)
|
||||||
|
.ok_or_else(|| DbErrorKind::UnrecognizedEntid(entid).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a valid `Schema` from the constituent maps.
|
/// Create a valid `Schema` from the constituent maps.
|
||||||
fn from_ident_map_and_attribute_map(ident_map: IdentMap, attribute_map: AttributeMap) -> Result<Schema> {
|
fn from_ident_map_and_attribute_map(
|
||||||
let entid_map: EntidMap = ident_map.iter().map(|(k, v)| (v.clone(), k.clone())).collect();
|
ident_map: IdentMap,
|
||||||
|
attribute_map: AttributeMap,
|
||||||
|
) -> Result<Schema> {
|
||||||
|
let entid_map: EntidMap = ident_map.iter().map(|(k, v)| (*v, k.clone())).collect();
|
||||||
|
|
||||||
validate_attribute_map(&entid_map, &attribute_map)?;
|
validate_attribute_map(&entid_map, &attribute_map)?;
|
||||||
Ok(Schema::new(ident_map, entid_map, attribute_map))
|
Ok(Schema::new(ident_map, entid_map, attribute_map))
|
||||||
|
@ -271,19 +296,30 @@ impl SchemaBuilding for Schema {
|
||||||
|
|
||||||
/// Turn vec![(Keyword(:ident), Keyword(:key), TypedValue(:value)), ...] into a Mentat `Schema`.
|
/// Turn vec![(Keyword(:ident), Keyword(:key), TypedValue(:value)), ...] into a Mentat `Schema`.
|
||||||
fn from_ident_map_and_triples<U>(ident_map: IdentMap, assertions: U) -> Result<Schema>
|
fn from_ident_map_and_triples<U>(ident_map: IdentMap, assertions: U) -> Result<Schema>
|
||||||
where U: IntoIterator<Item=(symbols::Keyword, symbols::Keyword, TypedValue)>{
|
where
|
||||||
|
U: IntoIterator<Item = (symbols::Keyword, symbols::Keyword, TypedValue)>,
|
||||||
let entid_assertions: Result<Vec<(Entid, Entid, TypedValue)>> = assertions.into_iter().map(|(symbolic_ident, symbolic_attr, value)| {
|
{
|
||||||
let ident: i64 = *ident_map.get(&symbolic_ident).ok_or(DbErrorKind::UnrecognizedIdent(symbolic_ident.to_string()))?;
|
let entid_assertions: Result<Vec<(Entid, Entid, TypedValue)>> = assertions
|
||||||
let attr: i64 = *ident_map.get(&symbolic_attr).ok_or(DbErrorKind::UnrecognizedIdent(symbolic_attr.to_string()))?;
|
.into_iter()
|
||||||
|
.map(|(symbolic_ident, symbolic_attr, value)| {
|
||||||
|
let ident: i64 = *ident_map
|
||||||
|
.get(&symbolic_ident)
|
||||||
|
.ok_or_else(|| DbErrorKind::UnrecognizedIdent(symbolic_ident.to_string()))?;
|
||||||
|
let attr: i64 = *ident_map
|
||||||
|
.get(&symbolic_attr)
|
||||||
|
.ok_or_else(|| DbErrorKind::UnrecognizedIdent(symbolic_attr.to_string()))?;
|
||||||
Ok((ident, attr, value))
|
Ok((ident, attr, value))
|
||||||
}).collect();
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
let mut schema = Schema::from_ident_map_and_attribute_map(ident_map, AttributeMap::default())?;
|
let mut schema =
|
||||||
let metadata_report = metadata::update_attribute_map_from_entid_triples(&mut schema.attribute_map,
|
Schema::from_ident_map_and_attribute_map(ident_map, AttributeMap::default())?;
|
||||||
|
let metadata_report = metadata::update_attribute_map_from_entid_triples(
|
||||||
|
&mut schema.attribute_map,
|
||||||
entid_assertions?,
|
entid_assertions?,
|
||||||
// No retractions.
|
// No retractions.
|
||||||
vec![])?;
|
vec![],
|
||||||
|
)?;
|
||||||
|
|
||||||
// Rebuild the component attributes list if necessary.
|
// Rebuild the component attributes list if necessary.
|
||||||
if metadata_report.attributes_did_change() {
|
if metadata_report.attributes_did_change() {
|
||||||
|
@ -298,11 +334,19 @@ pub trait SchemaTypeChecking {
|
||||||
///
|
///
|
||||||
/// Either assert that the given value is in the value type's value set, or (in limited cases)
|
/// Either assert that the given value is in the value type's value set, or (in limited cases)
|
||||||
/// coerce the given value into the value type's value set.
|
/// coerce the given value into the value type's value set.
|
||||||
fn to_typed_value(&self, value: &edn::ValueAndSpan, value_type: ValueType) -> Result<TypedValue>;
|
fn to_typed_value(
|
||||||
|
&self,
|
||||||
|
value: &edn::ValueAndSpan,
|
||||||
|
value_type: ValueType,
|
||||||
|
) -> Result<TypedValue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SchemaTypeChecking for Schema {
|
impl SchemaTypeChecking for Schema {
|
||||||
fn to_typed_value(&self, value: &edn::ValueAndSpan, value_type: ValueType) -> Result<TypedValue> {
|
fn to_typed_value(
|
||||||
|
&self,
|
||||||
|
value: &edn::ValueAndSpan,
|
||||||
|
value_type: ValueType,
|
||||||
|
) -> Result<TypedValue> {
|
||||||
// TODO: encapsulate entid-ident-attribute for better error messages, perhaps by including
|
// TODO: encapsulate entid-ident-attribute for better error messages, perhaps by including
|
||||||
// the attribute (rather than just the attribute's value type) into this function or a
|
// the attribute (rather than just the attribute's value type) into this function or a
|
||||||
// wrapper function.
|
// wrapper function.
|
||||||
|
@ -318,42 +362,41 @@ impl SchemaTypeChecking for Schema {
|
||||||
(ValueType::Uuid, tv @ TypedValue::Uuid(_)) => Ok(tv),
|
(ValueType::Uuid, tv @ TypedValue::Uuid(_)) => Ok(tv),
|
||||||
(ValueType::Instant, tv @ TypedValue::Instant(_)) => Ok(tv),
|
(ValueType::Instant, tv @ TypedValue::Instant(_)) => Ok(tv),
|
||||||
(ValueType::Keyword, tv @ TypedValue::Keyword(_)) => Ok(tv),
|
(ValueType::Keyword, tv @ TypedValue::Keyword(_)) => Ok(tv),
|
||||||
|
(ValueType::Bytes, tv @ TypedValue::Bytes(_)) => Ok(tv),
|
||||||
// Ref coerces a little: we interpret some things depending on the schema as a Ref.
|
// Ref coerces a little: we interpret some things depending on the schema as a Ref.
|
||||||
(ValueType::Ref, TypedValue::Long(x)) => Ok(TypedValue::Ref(x)),
|
(ValueType::Ref, TypedValue::Long(x)) => Ok(TypedValue::Ref(x)),
|
||||||
(ValueType::Ref, TypedValue::Keyword(ref x)) => self.require_entid(&x).map(|entid| entid.into()),
|
(ValueType::Ref, TypedValue::Keyword(ref x)) => {
|
||||||
|
self.require_entid(&x).map(|entid| entid.into())
|
||||||
|
}
|
||||||
|
|
||||||
// Otherwise, we have a type mismatch.
|
// Otherwise, we have a type mismatch.
|
||||||
// Enumerate all of the types here to allow the compiler to help us.
|
// Enumerate all of the types here to allow the compiler to help us.
|
||||||
// We don't enumerate all `TypedValue` cases, though: that would multiply this
|
// We don't enumerate all `TypedValue` cases, though: that would multiply this
|
||||||
// collection by 8!
|
// collection by 8!
|
||||||
(vt @ ValueType::Boolean, _) |
|
(vt @ ValueType::Boolean, _)
|
||||||
(vt @ ValueType::Long, _) |
|
| (vt @ ValueType::Long, _)
|
||||||
(vt @ ValueType::Double, _) |
|
| (vt @ ValueType::Double, _)
|
||||||
(vt @ ValueType::String, _) |
|
| (vt @ ValueType::String, _)
|
||||||
(vt @ ValueType::Uuid, _) |
|
| (vt @ ValueType::Uuid, _)
|
||||||
(vt @ ValueType::Instant, _) |
|
| (vt @ ValueType::Instant, _)
|
||||||
(vt @ ValueType::Keyword, _) |
|
| (vt @ ValueType::Keyword, _)
|
||||||
(vt @ ValueType::Ref, _)
|
| (vt @ ValueType::Bytes, _)
|
||||||
=> bail!(DbErrorKind::BadValuePair(format!("{}", value), vt)),
|
| (vt @ ValueType::Ref, _) => {
|
||||||
|
bail!(DbErrorKind::BadValuePair(format!("{}", value), vt))
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
|
||||||
use self::edn::Keyword;
|
use self::edn::Keyword;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
fn add_attribute(schema: &mut Schema,
|
fn add_attribute(schema: &mut Schema, ident: Keyword, entid: Entid, attribute: Attribute) {
|
||||||
ident: Keyword,
|
|
||||||
entid: Entid,
|
|
||||||
attribute: Attribute) {
|
|
||||||
|
|
||||||
schema.entid_map.insert(entid, ident.clone());
|
schema.entid_map.insert(entid, ident.clone());
|
||||||
schema.ident_map.insert(ident.clone(), entid);
|
schema.ident_map.insert(ident, entid);
|
||||||
|
|
||||||
if attribute.component {
|
if attribute.component {
|
||||||
schema.component_attributes.push(entid);
|
schema.component_attributes.push(entid);
|
||||||
|
@ -366,7 +409,11 @@ mod test {
|
||||||
fn validate_attribute_map_success() {
|
fn validate_attribute_map_success() {
|
||||||
let mut schema = Schema::default();
|
let mut schema = Schema::default();
|
||||||
// attribute that is not an index has no uniqueness
|
// attribute that is not an index has no uniqueness
|
||||||
add_attribute(&mut schema, Keyword::namespaced("foo", "bar"), 97, Attribute {
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
|
Keyword::namespaced("foo", "bar"),
|
||||||
|
97,
|
||||||
|
Attribute {
|
||||||
index: false,
|
index: false,
|
||||||
value_type: ValueType::Boolean,
|
value_type: ValueType::Boolean,
|
||||||
fulltext: false,
|
fulltext: false,
|
||||||
|
@ -374,9 +421,14 @@ mod test {
|
||||||
multival: false,
|
multival: false,
|
||||||
component: false,
|
component: false,
|
||||||
no_history: false,
|
no_history: false,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
// attribute is unique by value and an index
|
// attribute is unique by value and an index
|
||||||
add_attribute(&mut schema, Keyword::namespaced("foo", "baz"), 98, Attribute {
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
|
Keyword::namespaced("foo", "baz"),
|
||||||
|
98,
|
||||||
|
Attribute {
|
||||||
index: true,
|
index: true,
|
||||||
value_type: ValueType::Long,
|
value_type: ValueType::Long,
|
||||||
fulltext: false,
|
fulltext: false,
|
||||||
|
@ -384,9 +436,14 @@ mod test {
|
||||||
multival: false,
|
multival: false,
|
||||||
component: false,
|
component: false,
|
||||||
no_history: false,
|
no_history: false,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
// attribue is unique by identity and an index
|
// attribue is unique by identity and an index
|
||||||
add_attribute(&mut schema, Keyword::namespaced("foo", "bat"), 99, Attribute {
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
|
Keyword::namespaced("foo", "bat"),
|
||||||
|
99,
|
||||||
|
Attribute {
|
||||||
index: true,
|
index: true,
|
||||||
value_type: ValueType::Ref,
|
value_type: ValueType::Ref,
|
||||||
fulltext: false,
|
fulltext: false,
|
||||||
|
@ -394,9 +451,14 @@ mod test {
|
||||||
multival: false,
|
multival: false,
|
||||||
component: false,
|
component: false,
|
||||||
no_history: false,
|
no_history: false,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
// attribute is a components and a `Ref`
|
// attribute is a components and a `Ref`
|
||||||
add_attribute(&mut schema, Keyword::namespaced("foo", "bak"), 100, Attribute {
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
|
Keyword::namespaced("foo", "bak"),
|
||||||
|
100,
|
||||||
|
Attribute {
|
||||||
index: false,
|
index: false,
|
||||||
value_type: ValueType::Ref,
|
value_type: ValueType::Ref,
|
||||||
fulltext: false,
|
fulltext: false,
|
||||||
|
@ -404,9 +466,14 @@ mod test {
|
||||||
multival: false,
|
multival: false,
|
||||||
component: true,
|
component: true,
|
||||||
no_history: false,
|
no_history: false,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
// fulltext attribute is a string and an index
|
// fulltext attribute is a string and an index
|
||||||
add_attribute(&mut schema, Keyword::namespaced("foo", "bap"), 101, Attribute {
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
|
Keyword::namespaced("foo", "bap"),
|
||||||
|
101,
|
||||||
|
Attribute {
|
||||||
index: true,
|
index: true,
|
||||||
value_type: ValueType::String,
|
value_type: ValueType::String,
|
||||||
fulltext: true,
|
fulltext: true,
|
||||||
|
@ -414,7 +481,8 @@ mod test {
|
||||||
multival: false,
|
multival: false,
|
||||||
component: false,
|
component: false,
|
||||||
no_history: false,
|
no_history: false,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
assert!(validate_attribute_map(&schema.entid_map, &schema.attribute_map).is_ok());
|
assert!(validate_attribute_map(&schema.entid_map, &schema.attribute_map).is_ok());
|
||||||
}
|
}
|
||||||
|
@ -424,7 +492,11 @@ mod test {
|
||||||
let mut schema = Schema::default();
|
let mut schema = Schema::default();
|
||||||
// attribute unique by value but not index
|
// attribute unique by value but not index
|
||||||
let ident = Keyword::namespaced("foo", "bar");
|
let ident = Keyword::namespaced("foo", "bar");
|
||||||
add_attribute(&mut schema, ident , 99, Attribute {
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
|
ident,
|
||||||
|
99,
|
||||||
|
Attribute {
|
||||||
index: false,
|
index: false,
|
||||||
value_type: ValueType::Boolean,
|
value_type: ValueType::Boolean,
|
||||||
fulltext: false,
|
fulltext: false,
|
||||||
|
@ -432,17 +504,29 @@ mod test {
|
||||||
multival: false,
|
multival: false,
|
||||||
component: false,
|
component: false,
|
||||||
no_history: false,
|
no_history: false,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err().map(|e| e.kind());
|
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map)
|
||||||
assert_eq!(err, Some(DbErrorKind::BadSchemaAssertion(":db/unique :db/unique_value without :db/index true for entid: :foo/bar".into())));
|
.err()
|
||||||
|
.map(|e| e.kind());
|
||||||
|
assert_eq!(
|
||||||
|
err,
|
||||||
|
Some(DbErrorKind::BadSchemaAssertion(
|
||||||
|
":db/unique :db/unique_value without :db/index true for entid: :foo/bar".into()
|
||||||
|
))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_schema_unique_identity_not_index() {
|
fn invalid_schema_unique_identity_not_index() {
|
||||||
let mut schema = Schema::default();
|
let mut schema = Schema::default();
|
||||||
// attribute is unique by identity but not index
|
// attribute is unique by identity but not index
|
||||||
add_attribute(&mut schema, Keyword::namespaced("foo", "bar"), 99, Attribute {
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
|
Keyword::namespaced("foo", "bar"),
|
||||||
|
99,
|
||||||
|
Attribute {
|
||||||
index: false,
|
index: false,
|
||||||
value_type: ValueType::Long,
|
value_type: ValueType::Long,
|
||||||
fulltext: false,
|
fulltext: false,
|
||||||
|
@ -450,17 +534,29 @@ mod test {
|
||||||
multival: false,
|
multival: false,
|
||||||
component: false,
|
component: false,
|
||||||
no_history: false,
|
no_history: false,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err().map(|e| e.kind());
|
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map)
|
||||||
assert_eq!(err, Some(DbErrorKind::BadSchemaAssertion(":db/unique :db/unique_identity without :db/index true for entid: :foo/bar".into())));
|
.err()
|
||||||
|
.map(|e| e.kind());
|
||||||
|
assert_eq!(
|
||||||
|
err,
|
||||||
|
Some(DbErrorKind::BadSchemaAssertion(
|
||||||
|
":db/unique :db/unique_identity without :db/index true for entid: :foo/bar".into()
|
||||||
|
))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_schema_component_not_ref() {
|
fn invalid_schema_component_not_ref() {
|
||||||
let mut schema = Schema::default();
|
let mut schema = Schema::default();
|
||||||
// attribute that is a component is not a `Ref`
|
// attribute that is a component is not a `Ref`
|
||||||
add_attribute(&mut schema, Keyword::namespaced("foo", "bar"), 99, Attribute {
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
|
Keyword::namespaced("foo", "bar"),
|
||||||
|
99,
|
||||||
|
Attribute {
|
||||||
index: false,
|
index: false,
|
||||||
value_type: ValueType::Boolean,
|
value_type: ValueType::Boolean,
|
||||||
fulltext: false,
|
fulltext: false,
|
||||||
|
@ -468,17 +564,30 @@ mod test {
|
||||||
multival: false,
|
multival: false,
|
||||||
component: true,
|
component: true,
|
||||||
no_history: false,
|
no_history: false,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err().map(|e| e.kind());
|
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map)
|
||||||
assert_eq!(err, Some(DbErrorKind::BadSchemaAssertion(":db/isComponent true without :db/valueType :db.type/ref for entid: :foo/bar".into())));
|
.err()
|
||||||
|
.map(|e| e.kind());
|
||||||
|
assert_eq!(
|
||||||
|
err,
|
||||||
|
Some(DbErrorKind::BadSchemaAssertion(
|
||||||
|
":db/isComponent true without :db/valueType :db.type/ref for entid: :foo/bar"
|
||||||
|
.into()
|
||||||
|
))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_schema_fulltext_not_index() {
|
fn invalid_schema_fulltext_not_index() {
|
||||||
let mut schema = Schema::default();
|
let mut schema = Schema::default();
|
||||||
// attribute that is fulltext is not an index
|
// attribute that is fulltext is not an index
|
||||||
add_attribute(&mut schema, Keyword::namespaced("foo", "bar"), 99, Attribute {
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
|
Keyword::namespaced("foo", "bar"),
|
||||||
|
99,
|
||||||
|
Attribute {
|
||||||
index: false,
|
index: false,
|
||||||
value_type: ValueType::String,
|
value_type: ValueType::String,
|
||||||
fulltext: true,
|
fulltext: true,
|
||||||
|
@ -486,16 +595,28 @@ mod test {
|
||||||
multival: false,
|
multival: false,
|
||||||
component: false,
|
component: false,
|
||||||
no_history: false,
|
no_history: false,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err().map(|e| e.kind());
|
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map)
|
||||||
assert_eq!(err, Some(DbErrorKind::BadSchemaAssertion(":db/fulltext true without :db/index true for entid: :foo/bar".into())));
|
.err()
|
||||||
|
.map(|e| e.kind());
|
||||||
|
assert_eq!(
|
||||||
|
err,
|
||||||
|
Some(DbErrorKind::BadSchemaAssertion(
|
||||||
|
":db/fulltext true without :db/index true for entid: :foo/bar".into()
|
||||||
|
))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn invalid_schema_fulltext_index_not_string() {
|
fn invalid_schema_fulltext_index_not_string() {
|
||||||
let mut schema = Schema::default();
|
let mut schema = Schema::default();
|
||||||
// attribute that is fulltext and not a `String`
|
// attribute that is fulltext and not a `String`
|
||||||
add_attribute(&mut schema, Keyword::namespaced("foo", "bar"), 99, Attribute {
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
|
Keyword::namespaced("foo", "bar"),
|
||||||
|
99,
|
||||||
|
Attribute {
|
||||||
index: true,
|
index: true,
|
||||||
value_type: ValueType::Long,
|
value_type: ValueType::Long,
|
||||||
fulltext: true,
|
fulltext: true,
|
||||||
|
@ -503,9 +624,18 @@ mod test {
|
||||||
multival: false,
|
multival: false,
|
||||||
component: false,
|
component: false,
|
||||||
no_history: false,
|
no_history: false,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err().map(|e| e.kind());
|
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map)
|
||||||
assert_eq!(err, Some(DbErrorKind::BadSchemaAssertion(":db/fulltext true without :db/valueType :db.type/string for entid: :foo/bar".into())));
|
.err()
|
||||||
|
.map(|e| e.kind());
|
||||||
|
assert_eq!(
|
||||||
|
err,
|
||||||
|
Some(DbErrorKind::BadSchemaAssertion(
|
||||||
|
":db/fulltext true without :db/valueType :db.type/string for entid: :foo/bar"
|
||||||
|
.into()
|
||||||
|
))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,56 +10,41 @@
|
||||||
|
|
||||||
use std::ops::RangeFrom;
|
use std::ops::RangeFrom;
|
||||||
|
|
||||||
use rusqlite;
|
use rusqlite::{self, params_from_iter};
|
||||||
|
|
||||||
use errors::{
|
use db_traits::errors::{DbErrorKind, Result};
|
||||||
DbErrorKind,
|
|
||||||
Result,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mentat_core::{
|
use core_traits::{Entid, KnownEntid, TypedValue};
|
||||||
Entid,
|
|
||||||
Schema,
|
|
||||||
TypedValue,
|
|
||||||
KnownEntid,
|
|
||||||
};
|
|
||||||
|
|
||||||
use edn::{
|
use mentat_core::Schema;
|
||||||
InternSet,
|
|
||||||
};
|
use edn::InternSet;
|
||||||
|
|
||||||
use edn::entities::OpType;
|
use edn::entities::OpType;
|
||||||
|
|
||||||
use db;
|
use crate::db;
|
||||||
use db::{
|
use crate::db::TypedSQLValue;
|
||||||
TypedSQLValue,
|
|
||||||
};
|
|
||||||
|
|
||||||
use tx::{
|
use crate::tx::{transact_terms_with_action, TransactorAction};
|
||||||
transact_terms_with_action,
|
|
||||||
TransactorAction,
|
|
||||||
};
|
|
||||||
|
|
||||||
use types::{
|
use crate::types::PartitionMap;
|
||||||
PartitionMap,
|
|
||||||
};
|
|
||||||
|
|
||||||
use internal_types::{
|
use crate::internal_types::{Term, TermWithoutTempIds};
|
||||||
Term,
|
|
||||||
TermWithoutTempIds,
|
|
||||||
};
|
|
||||||
|
|
||||||
use watcher::{
|
use crate::watcher::NullWatcher;
|
||||||
NullWatcher,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Collects a supplied tx range into an DESC ordered Vec of valid txs,
|
/// Collects a supplied tx range into an DESC ordered Vec of valid txs,
|
||||||
/// ensuring they all belong to the same timeline.
|
/// ensuring they all belong to the same timeline.
|
||||||
fn collect_ordered_txs_to_move(conn: &rusqlite::Connection, txs_from: RangeFrom<Entid>, timeline: Entid) -> Result<Vec<Entid>> {
|
fn collect_ordered_txs_to_move(
|
||||||
|
conn: &rusqlite::Connection,
|
||||||
|
txs_from: RangeFrom<Entid>,
|
||||||
|
timeline: Entid,
|
||||||
|
) -> Result<Vec<Entid>> {
|
||||||
let mut stmt = conn.prepare("SELECT tx, timeline FROM timelined_transactions WHERE tx >= ? AND timeline = ? GROUP BY tx ORDER BY tx DESC")?;
|
let mut stmt = conn.prepare("SELECT tx, timeline FROM timelined_transactions WHERE tx >= ? AND timeline = ? GROUP BY tx ORDER BY tx DESC")?;
|
||||||
let mut rows = stmt.query_and_then(&[&txs_from.start, &timeline], |row: &rusqlite::Row| -> Result<(Entid, Entid)>{
|
let mut rows = stmt.query_and_then(
|
||||||
Ok((row.get_checked(0)?, row.get_checked(1)?))
|
&[&txs_from.start, &timeline],
|
||||||
})?;
|
|row: &rusqlite::Row| -> Result<(Entid, Entid)> { Ok((row.get(0)?, row.get(1)?)) },
|
||||||
|
)?;
|
||||||
|
|
||||||
let mut txs = vec![];
|
let mut txs = vec![];
|
||||||
|
|
||||||
|
@ -69,11 +54,11 @@ fn collect_ordered_txs_to_move(conn: &rusqlite::Connection, txs_from: RangeFrom<
|
||||||
let t = t?;
|
let t = t?;
|
||||||
txs.push(t.0);
|
txs.push(t.0);
|
||||||
t.1
|
t.1
|
||||||
},
|
}
|
||||||
None => bail!(DbErrorKind::TimelinesInvalidRange)
|
None => bail!(DbErrorKind::TimelinesInvalidRange),
|
||||||
};
|
};
|
||||||
|
|
||||||
while let Some(t) = rows.next() {
|
for t in rows {
|
||||||
let t = t?;
|
let t = t?;
|
||||||
txs.push(t.0);
|
txs.push(t.0);
|
||||||
if t.1 != timeline {
|
if t.1 != timeline {
|
||||||
|
@ -84,55 +69,79 @@ fn collect_ordered_txs_to_move(conn: &rusqlite::Connection, txs_from: RangeFrom<
|
||||||
Ok(txs)
|
Ok(txs)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_transactions_to(conn: &rusqlite::Connection, tx_ids: &[Entid], new_timeline: Entid) -> Result<()> {
|
fn move_transactions_to(
|
||||||
|
conn: &rusqlite::Connection,
|
||||||
|
tx_ids: &[Entid],
|
||||||
|
new_timeline: Entid,
|
||||||
|
) -> Result<()> {
|
||||||
// Move specified transactions over to a specified timeline.
|
// Move specified transactions over to a specified timeline.
|
||||||
conn.execute(&format!(
|
conn.execute(
|
||||||
|
&format!(
|
||||||
"UPDATE timelined_transactions SET timeline = {} WHERE tx IN {}",
|
"UPDATE timelined_transactions SET timeline = {} WHERE tx IN {}",
|
||||||
new_timeline,
|
new_timeline,
|
||||||
::repeat_values(tx_ids.len(), 1)
|
crate::repeat_values(tx_ids.len(), 1)
|
||||||
), &(tx_ids.iter().map(|x| x as &rusqlite::types::ToSql).collect::<Vec<_>>())
|
),
|
||||||
|
params_from_iter(tx_ids.iter()),
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn remove_tx_from_datoms(conn: &rusqlite::Connection, tx_id: Entid) -> Result<()> {
|
||||||
|
conn.execute("DELETE FROM datoms WHERE e = ?", &[&tx_id])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn is_timeline_empty(conn: &rusqlite::Connection, timeline: Entid) -> Result<bool> {
|
fn is_timeline_empty(conn: &rusqlite::Connection, timeline: Entid) -> Result<bool> {
|
||||||
let mut stmt = conn.prepare("SELECT timeline FROM timelined_transactions WHERE timeline = ? GROUP BY timeline")?;
|
let mut stmt = conn.prepare(
|
||||||
let rows = stmt.query_and_then(&[&timeline], |row| -> Result<i64> {
|
"SELECT timeline FROM timelined_transactions WHERE timeline = ? GROUP BY timeline",
|
||||||
Ok(row.get_checked(0)?)
|
)?;
|
||||||
})?;
|
let rows = stmt.query_and_then(&[&timeline], |row| -> Result<i64> { Ok(row.get(0)?) })?;
|
||||||
Ok(rows.count() == 0)
|
Ok(rows.count() == 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get terms for tx_id, reversing them in meaning (swap add & retract).
|
/// Get terms for tx_id, reversing them in meaning (swap add & retract).
|
||||||
fn reversed_terms_for(conn: &rusqlite::Connection, tx_id: Entid) -> Result<Vec<TermWithoutTempIds>> {
|
fn reversed_terms_for(
|
||||||
|
conn: &rusqlite::Connection,
|
||||||
|
tx_id: Entid,
|
||||||
|
) -> Result<Vec<TermWithoutTempIds>> {
|
||||||
let mut stmt = conn.prepare("SELECT e, a, v, value_type_tag, tx, added FROM timelined_transactions WHERE tx = ? AND timeline = ? ORDER BY tx DESC")?;
|
let mut stmt = conn.prepare("SELECT e, a, v, value_type_tag, tx, added FROM timelined_transactions WHERE tx = ? AND timeline = ? ORDER BY tx DESC")?;
|
||||||
let mut rows = stmt.query_and_then(&[&tx_id, &::TIMELINE_MAIN], |row| -> Result<TermWithoutTempIds> {
|
let rows = stmt.query_and_then(
|
||||||
let op = match row.get_checked(5)? {
|
&[&tx_id, &crate::TIMELINE_MAIN],
|
||||||
true => OpType::Retract,
|
|row| -> Result<TermWithoutTempIds> {
|
||||||
false => OpType::Add
|
let op = if row.get(5)? {
|
||||||
|
OpType::Retract
|
||||||
|
} else {
|
||||||
|
OpType::Add
|
||||||
};
|
};
|
||||||
Ok(Term::AddOrRetract(
|
Ok(Term::AddOrRetract(
|
||||||
op,
|
op,
|
||||||
KnownEntid(row.get_checked(0)?),
|
KnownEntid(row.get(0)?),
|
||||||
row.get_checked(1)?,
|
row.get(1)?,
|
||||||
TypedValue::from_sql_value_pair(row.get_checked(2)?, row.get_checked(3)?)?,
|
TypedValue::from_sql_value_pair(row.get(2)?, row.get(3)?)?,
|
||||||
))
|
))
|
||||||
})?;
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
let mut terms = vec![];
|
let mut terms = vec![];
|
||||||
|
|
||||||
while let Some(row) = rows.next() {
|
for row in rows {
|
||||||
terms.push(row?);
|
terms.push(row?);
|
||||||
}
|
}
|
||||||
Ok(terms)
|
Ok(terms)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move specified transaction RangeFrom off of main timeline.
|
/// Move specified transaction RangeFrom off of main timeline.
|
||||||
pub fn move_from_main_timeline(conn: &rusqlite::Connection, schema: &Schema,
|
pub fn move_from_main_timeline(
|
||||||
partition_map: PartitionMap, txs_from: RangeFrom<Entid>, new_timeline: Entid) -> Result<(Option<Schema>, PartitionMap)> {
|
conn: &rusqlite::Connection,
|
||||||
|
schema: &Schema,
|
||||||
if new_timeline == ::TIMELINE_MAIN {
|
partition_map: PartitionMap,
|
||||||
bail!(DbErrorKind::NotYetImplemented(format!("Can't move transactions to main timeline")));
|
txs_from: RangeFrom<Entid>,
|
||||||
|
new_timeline: Entid,
|
||||||
|
) -> Result<(Option<Schema>, PartitionMap)> {
|
||||||
|
if new_timeline == crate::TIMELINE_MAIN {
|
||||||
|
bail!(DbErrorKind::NotYetImplemented(
|
||||||
|
"Can't move transactions to main timeline".to_string()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't currently ensure that moving transactions onto a non-empty timeline
|
// We don't currently ensure that moving transactions onto a non-empty timeline
|
||||||
|
@ -142,18 +151,34 @@ pub fn move_from_main_timeline(conn: &rusqlite::Connection, schema: &Schema,
|
||||||
bail!(DbErrorKind::TimelinesMoveToNonEmpty);
|
bail!(DbErrorKind::TimelinesMoveToNonEmpty);
|
||||||
}
|
}
|
||||||
|
|
||||||
let txs_to_move = collect_ordered_txs_to_move(conn, txs_from, ::TIMELINE_MAIN)?;
|
let txs_to_move = collect_ordered_txs_to_move(conn, txs_from, crate::TIMELINE_MAIN)?;
|
||||||
|
|
||||||
let mut last_schema = None;
|
let mut last_schema = None;
|
||||||
for tx_id in &txs_to_move {
|
for tx_id in &txs_to_move {
|
||||||
let reversed_terms = reversed_terms_for(conn, *tx_id)?;
|
let reversed_terms = reversed_terms_for(conn, *tx_id)?;
|
||||||
|
|
||||||
// Rewind schema and datoms.
|
// Rewind schema and datoms.
|
||||||
let (_, _, new_schema, _) = transact_terms_with_action(
|
let (report, _, new_schema, _) = transact_terms_with_action(
|
||||||
conn, partition_map.clone(), schema, schema, NullWatcher(),
|
conn,
|
||||||
|
partition_map.clone(),
|
||||||
|
schema,
|
||||||
|
schema,
|
||||||
|
NullWatcher(),
|
||||||
reversed_terms.into_iter().map(|t| t.rewrap()),
|
reversed_terms.into_iter().map(|t| t.rewrap()),
|
||||||
InternSet::new(), TransactorAction::Materialize
|
InternSet::new(),
|
||||||
|
TransactorAction::Materialize,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// Rewind operation generated a 'tx' and a 'txInstant' assertion, which got
|
||||||
|
// inserted into the 'datoms' table (due to TransactorAction::Materialize).
|
||||||
|
// This is problematic. If we transact a few more times, the transactor will
|
||||||
|
// generate the same 'tx', but with a different 'txInstant'.
|
||||||
|
// The end result will be a transaction which has a phantom
|
||||||
|
// retraction of a txInstant, since transactor operates against the state of
|
||||||
|
// 'datoms', and not against the 'transactions' table.
|
||||||
|
// A quick workaround is to just remove the bad txInstant datom.
|
||||||
|
// See test_clashing_tx_instants test case.
|
||||||
|
remove_tx_from_datoms(conn, report.tx_id)?;
|
||||||
last_schema = new_schema;
|
last_schema = new_schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,22 +194,18 @@ mod tests {
|
||||||
|
|
||||||
use edn;
|
use edn;
|
||||||
|
|
||||||
use std::borrow::{
|
use std::borrow::Borrow;
|
||||||
Borrow,
|
|
||||||
};
|
|
||||||
|
|
||||||
use debug::{
|
use crate::debug::TestConn;
|
||||||
TestConn,
|
|
||||||
};
|
|
||||||
|
|
||||||
use bootstrap;
|
use crate::bootstrap;
|
||||||
|
|
||||||
// For convenience during testing.
|
// For convenience during testing.
|
||||||
// Real consumers will perform similar operations when appropriate.
|
// Real consumers will perform similar operations when appropriate.
|
||||||
fn update_conn(conn: &mut TestConn, schema: &Option<Schema>, pmap: &PartitionMap) {
|
fn update_conn(conn: &mut TestConn, schema: &Option<Schema>, pmap: &PartitionMap) {
|
||||||
match schema {
|
match schema {
|
||||||
&Some(ref s) => conn.schema = s.clone(),
|
Some(ref s) => conn.schema = s.clone(),
|
||||||
&None => ()
|
None => (),
|
||||||
};
|
};
|
||||||
conn.partition_map = pmap.clone();
|
conn.partition_map = pmap.clone();
|
||||||
}
|
}
|
||||||
|
@ -204,35 +225,49 @@ mod tests {
|
||||||
let partition_map1 = conn.partition_map.clone();
|
let partition_map1 = conn.partition_map.clone();
|
||||||
|
|
||||||
let (new_schema, new_partition_map) = move_from_main_timeline(
|
let (new_schema, new_partition_map) = move_from_main_timeline(
|
||||||
&conn.sqlite, &conn.schema, conn.partition_map.clone(),
|
&conn.sqlite,
|
||||||
conn.last_tx_id().., 1
|
&conn.schema,
|
||||||
).expect("moved single tx");
|
conn.partition_map.clone(),
|
||||||
|
conn.last_tx_id()..,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.expect("moved single tx");
|
||||||
update_conn(&mut conn, &new_schema, &new_partition_map);
|
update_conn(&mut conn, &new_schema, &new_partition_map);
|
||||||
|
|
||||||
assert_matches!(conn.datoms(), "[]");
|
assert_matches!(conn.datoms(), "[]");
|
||||||
assert_matches!(conn.transactions(), "[]");
|
assert_matches!(conn.transactions(), "[]");
|
||||||
assert_eq!(new_partition_map, partition_map0);
|
assert_eq!(new_partition_map, partition_map0);
|
||||||
|
|
||||||
conn.partition_map = partition_map0.clone();
|
conn.partition_map = partition_map0;
|
||||||
let report2 = assert_transact!(conn, t);
|
let report2 = assert_transact!(conn, t);
|
||||||
let partition_map2 = conn.partition_map.clone();
|
let partition_map2 = conn.partition_map.clone();
|
||||||
|
|
||||||
// Ensure that we can't move transactions to a non-empty timeline:
|
// Ensure that we can't move transactions to a non-empty timeline:
|
||||||
move_from_main_timeline(
|
move_from_main_timeline(
|
||||||
&conn.sqlite, &conn.schema, conn.partition_map.clone(),
|
&conn.sqlite,
|
||||||
conn.last_tx_id().., 1
|
&conn.schema,
|
||||||
).expect_err("Can't move transactions to a non-empty timeline");
|
conn.partition_map.clone(),
|
||||||
|
conn.last_tx_id()..,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.expect_err("Can't move transactions to a non-empty timeline");
|
||||||
|
|
||||||
assert_eq!(report1.tx_id, report2.tx_id);
|
assert_eq!(report1.tx_id, report2.tx_id);
|
||||||
assert_eq!(partition_map1, partition_map2);
|
assert_eq!(partition_map1, partition_map2);
|
||||||
|
|
||||||
assert_matches!(conn.datoms(), r#"
|
assert_matches!(
|
||||||
|
conn.datoms(),
|
||||||
|
r#"
|
||||||
[[37 :db/doc "test"]]
|
[[37 :db/doc "test"]]
|
||||||
"#);
|
"#
|
||||||
assert_matches!(conn.transactions(), r#"
|
);
|
||||||
|
assert_matches!(
|
||||||
|
conn.transactions(),
|
||||||
|
r#"
|
||||||
[[[37 :db/doc "test" ?tx true]
|
[[[37 :db/doc "test" ?tx true]
|
||||||
[?tx :db/txInstant ?ms ?tx true]]]
|
[?tx :db/txInstant ?ms ?tx true]]]
|
||||||
"#);
|
"#
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -252,9 +287,13 @@ mod tests {
|
||||||
let schema1 = conn.schema.clone();
|
let schema1 = conn.schema.clone();
|
||||||
|
|
||||||
let (new_schema, new_partition_map) = move_from_main_timeline(
|
let (new_schema, new_partition_map) = move_from_main_timeline(
|
||||||
&conn.sqlite, &conn.schema, conn.partition_map.clone(),
|
&conn.sqlite,
|
||||||
conn.last_tx_id().., 1
|
&conn.schema,
|
||||||
).expect("moved single tx");
|
conn.partition_map.clone(),
|
||||||
|
conn.last_tx_id()..,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.expect("moved single tx");
|
||||||
update_conn(&mut conn, &new_schema, &new_partition_map);
|
update_conn(&mut conn, &new_schema, &new_partition_map);
|
||||||
|
|
||||||
assert_matches!(conn.datoms(), "[]");
|
assert_matches!(conn.datoms(), "[]");
|
||||||
|
@ -268,19 +307,128 @@ mod tests {
|
||||||
assert_eq!(conn.partition_map, partition_map1);
|
assert_eq!(conn.partition_map, partition_map1);
|
||||||
assert_eq!(conn.schema, schema1);
|
assert_eq!(conn.schema, schema1);
|
||||||
|
|
||||||
assert_matches!(conn.datoms(), r#"
|
assert_matches!(
|
||||||
|
conn.datoms(),
|
||||||
|
r#"
|
||||||
[[?e :db/ident :test/entid]
|
[[?e :db/ident :test/entid]
|
||||||
[?e :db/doc "test"]
|
[?e :db/doc "test"]
|
||||||
[?e :db.schema/version 1]]
|
[?e :db.schema/version 1]]
|
||||||
"#);
|
"#
|
||||||
assert_matches!(conn.transactions(), r#"
|
);
|
||||||
|
assert_matches!(
|
||||||
|
conn.transactions(),
|
||||||
|
r#"
|
||||||
[[[?e :db/ident :test/entid ?tx true]
|
[[[?e :db/ident :test/entid ?tx true]
|
||||||
[?e :db/doc "test" ?tx true]
|
[?e :db/doc "test" ?tx true]
|
||||||
[?e :db.schema/version 1 ?tx true]
|
[?e :db.schema/version 1 ?tx true]
|
||||||
[?tx :db/txInstant ?ms ?tx true]]]
|
[?tx :db/txInstant ?ms ?tx true]]]
|
||||||
"#);
|
"#
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clashing_tx_instants() {
|
||||||
|
let mut conn = TestConn::default();
|
||||||
|
conn.sanitized_partition_map();
|
||||||
|
|
||||||
|
// Transact a basic schema.
|
||||||
|
assert_transact!(
|
||||||
|
conn,
|
||||||
|
r#"
|
||||||
|
[{:db/ident :person/name :db/valueType :db.type/string :db/cardinality :db.cardinality/one :db/unique :db.unique/identity :db/index true}]
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
|
||||||
|
// Make an assertion against our schema.
|
||||||
|
assert_transact!(conn, r#"[{:person/name "Vanya"}]"#);
|
||||||
|
|
||||||
|
// Move that assertion away from the main timeline.
|
||||||
|
let (new_schema, new_partition_map) = move_from_main_timeline(
|
||||||
|
&conn.sqlite,
|
||||||
|
&conn.schema,
|
||||||
|
conn.partition_map.clone(),
|
||||||
|
conn.last_tx_id()..,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.expect("moved single tx");
|
||||||
|
update_conn(&mut conn, &new_schema, &new_partition_map);
|
||||||
|
|
||||||
|
// Assert that our datoms are now just the schema.
|
||||||
|
assert_matches!(
|
||||||
|
conn.datoms(),
|
||||||
|
"
|
||||||
|
[[?e :db/ident :person/name]
|
||||||
|
[?e :db/valueType :db.type/string]
|
||||||
|
[?e :db/cardinality :db.cardinality/one]
|
||||||
|
[?e :db/unique :db.unique/identity]
|
||||||
|
[?e :db/index true]]"
|
||||||
|
);
|
||||||
|
// Same for transactions.
|
||||||
|
assert_matches!(
|
||||||
|
conn.transactions(),
|
||||||
|
"
|
||||||
|
[[[?e :db/ident :person/name ?tx true]
|
||||||
|
[?e :db/valueType :db.type/string ?tx true]
|
||||||
|
[?e :db/cardinality :db.cardinality/one ?tx true]
|
||||||
|
[?e :db/unique :db.unique/identity ?tx true]
|
||||||
|
[?e :db/index true ?tx true]
|
||||||
|
[?tx :db/txInstant ?ms ?tx true]]]"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Re-assert our initial fact against our schema.
|
||||||
|
assert_transact!(
|
||||||
|
conn,
|
||||||
|
r#"
|
||||||
|
[[:db/add "tempid" :person/name "Vanya"]]"#
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now, change that fact. This is the "clashing" transaction, if we're
|
||||||
|
// performing a timeline move using the transactor.
|
||||||
|
assert_transact!(
|
||||||
|
conn,
|
||||||
|
r#"
|
||||||
|
[[:db/add (lookup-ref :person/name "Vanya") :person/name "Ivan"]]"#
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert that our datoms are now the schema and the final assertion.
|
||||||
|
assert_matches!(
|
||||||
|
conn.datoms(),
|
||||||
|
r#"
|
||||||
|
[[?e1 :db/ident :person/name]
|
||||||
|
[?e1 :db/valueType :db.type/string]
|
||||||
|
[?e1 :db/cardinality :db.cardinality/one]
|
||||||
|
[?e1 :db/unique :db.unique/identity]
|
||||||
|
[?e1 :db/index true]
|
||||||
|
[?e2 :person/name "Ivan"]]
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert that we have three correct looking transactions.
|
||||||
|
// This will fail if we're not cleaning up the 'datoms' table
|
||||||
|
// after the timeline move.
|
||||||
|
assert_matches!(
|
||||||
|
conn.transactions(),
|
||||||
|
r#"
|
||||||
|
[[
|
||||||
|
[?e1 :db/ident :person/name ?tx1 true]
|
||||||
|
[?e1 :db/valueType :db.type/string ?tx1 true]
|
||||||
|
[?e1 :db/cardinality :db.cardinality/one ?tx1 true]
|
||||||
|
[?e1 :db/unique :db.unique/identity ?tx1 true]
|
||||||
|
[?e1 :db/index true ?tx1 true]
|
||||||
|
[?tx1 :db/txInstant ?ms1 ?tx1 true]
|
||||||
|
]
|
||||||
|
[
|
||||||
|
[?e2 :person/name "Vanya" ?tx2 true]
|
||||||
|
[?tx2 :db/txInstant ?ms2 ?tx2 true]
|
||||||
|
]
|
||||||
|
[
|
||||||
|
[?e2 :person/name "Ivan" ?tx3 true]
|
||||||
|
[?e2 :person/name "Vanya" ?tx3 false]
|
||||||
|
[?tx3 :db/txInstant ?ms3 ?tx3 true]
|
||||||
|
]]
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pop_schema() {
|
fn test_pop_schema() {
|
||||||
|
@ -300,8 +448,13 @@ mod tests {
|
||||||
let schema1 = conn.schema.clone();
|
let schema1 = conn.schema.clone();
|
||||||
|
|
||||||
let (new_schema, new_partition_map) = move_from_main_timeline(
|
let (new_schema, new_partition_map) = move_from_main_timeline(
|
||||||
&conn.sqlite, &conn.schema, conn.partition_map.clone(),
|
&conn.sqlite,
|
||||||
report1.tx_id.., 1).expect("moved single tx");
|
&conn.schema,
|
||||||
|
conn.partition_map.clone(),
|
||||||
|
report1.tx_id..,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.expect("moved single tx");
|
||||||
update_conn(&mut conn, &new_schema, &new_partition_map);
|
update_conn(&mut conn, &new_schema, &new_partition_map);
|
||||||
|
|
||||||
assert_matches!(conn.datoms(), "[]");
|
assert_matches!(conn.datoms(), "[]");
|
||||||
|
@ -317,15 +470,20 @@ mod tests {
|
||||||
assert_eq!(partition_map1, partition_map2);
|
assert_eq!(partition_map1, partition_map2);
|
||||||
assert_eq!(schema1, schema2);
|
assert_eq!(schema1, schema2);
|
||||||
|
|
||||||
assert_matches!(conn.datoms(), r#"
|
assert_matches!(
|
||||||
|
conn.datoms(),
|
||||||
|
r#"
|
||||||
[[?e1 :db/ident :test/one]
|
[[?e1 :db/ident :test/one]
|
||||||
[?e1 :db/valueType :db.type/long]
|
[?e1 :db/valueType :db.type/long]
|
||||||
[?e1 :db/cardinality :db.cardinality/one]
|
[?e1 :db/cardinality :db.cardinality/one]
|
||||||
[?e2 :db/ident :test/many]
|
[?e2 :db/ident :test/many]
|
||||||
[?e2 :db/valueType :db.type/long]
|
[?e2 :db/valueType :db.type/long]
|
||||||
[?e2 :db/cardinality :db.cardinality/many]]
|
[?e2 :db/cardinality :db.cardinality/many]]
|
||||||
"#);
|
"#
|
||||||
assert_matches!(conn.transactions(), r#"
|
);
|
||||||
|
assert_matches!(
|
||||||
|
conn.transactions(),
|
||||||
|
r#"
|
||||||
[[[?e1 :db/ident :test/one ?tx1 true]
|
[[[?e1 :db/ident :test/one ?tx1 true]
|
||||||
[?e1 :db/valueType :db.type/long ?tx1 true]
|
[?e1 :db/valueType :db.type/long ?tx1 true]
|
||||||
[?e1 :db/cardinality :db.cardinality/one ?tx1 true]
|
[?e1 :db/cardinality :db.cardinality/one ?tx1 true]
|
||||||
|
@ -333,7 +491,8 @@ mod tests {
|
||||||
[?e2 :db/valueType :db.type/long ?tx1 true]
|
[?e2 :db/valueType :db.type/long ?tx1 true]
|
||||||
[?e2 :db/cardinality :db.cardinality/many ?tx1 true]
|
[?e2 :db/cardinality :db.cardinality/many ?tx1 true]
|
||||||
[?tx1 :db/txInstant ?ms ?tx1 true]]]
|
[?tx1 :db/txInstant ?ms ?tx1 true]]]
|
||||||
"#);
|
"#
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -361,8 +520,13 @@ mod tests {
|
||||||
let schema1 = conn.schema.clone();
|
let schema1 = conn.schema.clone();
|
||||||
|
|
||||||
let (new_schema, new_partition_map) = move_from_main_timeline(
|
let (new_schema, new_partition_map) = move_from_main_timeline(
|
||||||
&conn.sqlite, &conn.schema, conn.partition_map.clone(),
|
&conn.sqlite,
|
||||||
report1.tx_id.., 1).expect("moved single tx");
|
&conn.schema,
|
||||||
|
conn.partition_map.clone(),
|
||||||
|
report1.tx_id..,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.expect("moved single tx");
|
||||||
update_conn(&mut conn, &new_schema, &new_partition_map);
|
update_conn(&mut conn, &new_schema, &new_partition_map);
|
||||||
|
|
||||||
assert_matches!(conn.datoms(), "[]");
|
assert_matches!(conn.datoms(), "[]");
|
||||||
|
@ -378,15 +542,20 @@ mod tests {
|
||||||
assert_eq!(partition_map1, partition_map2);
|
assert_eq!(partition_map1, partition_map2);
|
||||||
assert_eq!(schema1, schema2);
|
assert_eq!(schema1, schema2);
|
||||||
|
|
||||||
assert_matches!(conn.datoms(), r#"
|
assert_matches!(
|
||||||
|
conn.datoms(),
|
||||||
|
r#"
|
||||||
[[?e1 :db/ident :test/one]
|
[[?e1 :db/ident :test/one]
|
||||||
[?e1 :db/valueType :db.type/string]
|
[?e1 :db/valueType :db.type/string]
|
||||||
[?e1 :db/cardinality :db.cardinality/one]
|
[?e1 :db/cardinality :db.cardinality/one]
|
||||||
[?e1 :db/unique :db.unique/value]
|
[?e1 :db/unique :db.unique/value]
|
||||||
[?e1 :db/index true]
|
[?e1 :db/index true]
|
||||||
[?e1 :db/fulltext true]]
|
[?e1 :db/fulltext true]]
|
||||||
"#);
|
"#
|
||||||
assert_matches!(conn.transactions(), r#"
|
);
|
||||||
|
assert_matches!(
|
||||||
|
conn.transactions(),
|
||||||
|
r#"
|
||||||
[[[?e1 :db/ident :test/one ?tx1 true]
|
[[[?e1 :db/ident :test/one ?tx1 true]
|
||||||
[?e1 :db/valueType :db.type/string ?tx1 true]
|
[?e1 :db/valueType :db.type/string ?tx1 true]
|
||||||
[?e1 :db/cardinality :db.cardinality/one ?tx1 true]
|
[?e1 :db/cardinality :db.cardinality/one ?tx1 true]
|
||||||
|
@ -394,7 +563,8 @@ mod tests {
|
||||||
[?e1 :db/index true ?tx1 true]
|
[?e1 :db/index true ?tx1 true]
|
||||||
[?e1 :db/fulltext true ?tx1 true]
|
[?e1 :db/fulltext true ?tx1 true]
|
||||||
[?tx1 :db/txInstant ?ms ?tx1 true]]]
|
[?tx1 :db/txInstant ?ms ?tx1 true]]]
|
||||||
"#);
|
"#
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -422,8 +592,13 @@ mod tests {
|
||||||
let schema1 = conn.schema.clone();
|
let schema1 = conn.schema.clone();
|
||||||
|
|
||||||
let (new_schema, new_partition_map) = move_from_main_timeline(
|
let (new_schema, new_partition_map) = move_from_main_timeline(
|
||||||
&conn.sqlite, &conn.schema, conn.partition_map.clone(),
|
&conn.sqlite,
|
||||||
report1.tx_id.., 1).expect("moved single tx");
|
&conn.schema,
|
||||||
|
conn.partition_map.clone(),
|
||||||
|
report1.tx_id..,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.expect("moved single tx");
|
||||||
update_conn(&mut conn, &new_schema, &new_partition_map);
|
update_conn(&mut conn, &new_schema, &new_partition_map);
|
||||||
|
|
||||||
assert_matches!(conn.datoms(), "[]");
|
assert_matches!(conn.datoms(), "[]");
|
||||||
|
@ -434,7 +609,10 @@ mod tests {
|
||||||
assert_eq!(conn.schema.entid_map, schema0.entid_map);
|
assert_eq!(conn.schema.entid_map, schema0.entid_map);
|
||||||
assert_eq!(conn.schema.ident_map, schema0.ident_map);
|
assert_eq!(conn.schema.ident_map, schema0.ident_map);
|
||||||
assert_eq!(conn.schema.attribute_map, schema0.attribute_map);
|
assert_eq!(conn.schema.attribute_map, schema0.attribute_map);
|
||||||
assert_eq!(conn.schema.component_attributes, schema0.component_attributes);
|
assert_eq!(
|
||||||
|
conn.schema.component_attributes,
|
||||||
|
schema0.component_attributes
|
||||||
|
);
|
||||||
// Assert the whole schema, just in case we missed something:
|
// Assert the whole schema, just in case we missed something:
|
||||||
assert_eq!(conn.schema, schema0);
|
assert_eq!(conn.schema, schema0);
|
||||||
|
|
||||||
|
@ -446,15 +624,20 @@ mod tests {
|
||||||
assert_eq!(partition_map1, partition_map2);
|
assert_eq!(partition_map1, partition_map2);
|
||||||
assert_eq!(schema1, schema2);
|
assert_eq!(schema1, schema2);
|
||||||
|
|
||||||
assert_matches!(conn.datoms(), r#"
|
assert_matches!(
|
||||||
|
conn.datoms(),
|
||||||
|
r#"
|
||||||
[[?e1 :db/ident :test/one]
|
[[?e1 :db/ident :test/one]
|
||||||
[?e1 :db/valueType :db.type/ref]
|
[?e1 :db/valueType :db.type/ref]
|
||||||
[?e1 :db/cardinality :db.cardinality/one]
|
[?e1 :db/cardinality :db.cardinality/one]
|
||||||
[?e1 :db/unique :db.unique/value]
|
[?e1 :db/unique :db.unique/value]
|
||||||
[?e1 :db/isComponent true]
|
[?e1 :db/isComponent true]
|
||||||
[?e1 :db/index true]]
|
[?e1 :db/index true]]
|
||||||
"#);
|
"#
|
||||||
assert_matches!(conn.transactions(), r#"
|
);
|
||||||
|
assert_matches!(
|
||||||
|
conn.transactions(),
|
||||||
|
r#"
|
||||||
[[[?e1 :db/ident :test/one ?tx1 true]
|
[[[?e1 :db/ident :test/one ?tx1 true]
|
||||||
[?e1 :db/valueType :db.type/ref ?tx1 true]
|
[?e1 :db/valueType :db.type/ref ?tx1 true]
|
||||||
[?e1 :db/cardinality :db.cardinality/one ?tx1 true]
|
[?e1 :db/cardinality :db.cardinality/one ?tx1 true]
|
||||||
|
@ -462,7 +645,8 @@ mod tests {
|
||||||
[?e1 :db/isComponent true ?tx1 true]
|
[?e1 :db/isComponent true ?tx1 true]
|
||||||
[?e1 :db/index true ?tx1 true]
|
[?e1 :db/index true ?tx1 true]
|
||||||
[?tx1 :db/txInstant ?ms ?tx1 true]]]
|
[?tx1 :db/txInstant ?ms ?tx1 true]]]
|
||||||
"#);
|
"#
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -472,12 +656,17 @@ mod tests {
|
||||||
|
|
||||||
let partition_map_after_bootstrap = conn.partition_map.clone();
|
let partition_map_after_bootstrap = conn.partition_map.clone();
|
||||||
|
|
||||||
assert_eq!((65536..65538),
|
assert_eq!(
|
||||||
conn.partition_map.allocate_entids(":db.part/user", 2));
|
(65536..65538),
|
||||||
let tx_report0 = assert_transact!(conn, r#"[
|
conn.partition_map.allocate_entids(":db.part/user", 2)
|
||||||
|
);
|
||||||
|
let tx_report0 = assert_transact!(
|
||||||
|
conn,
|
||||||
|
r#"[
|
||||||
{:db/id 65536 :db/ident :test/one :db/valueType :db.type/long :db/cardinality :db.cardinality/one :db/unique :db.unique/identity :db/index true}
|
{:db/id 65536 :db/ident :test/one :db/valueType :db.type/long :db/cardinality :db.cardinality/one :db/unique :db.unique/identity :db/index true}
|
||||||
{:db/id 65537 :db/ident :test/many :db/valueType :db.type/long :db/cardinality :db.cardinality/many}
|
{:db/id 65537 :db/ident :test/many :db/valueType :db.type/long :db/cardinality :db.cardinality/many}
|
||||||
]"#);
|
]"#
|
||||||
|
);
|
||||||
|
|
||||||
let first = "[
|
let first = "[
|
||||||
[65536 :db/ident :test/one]
|
[65536 :db/ident :test/one]
|
||||||
|
@ -493,21 +682,28 @@ mod tests {
|
||||||
|
|
||||||
let partition_map0 = conn.partition_map.clone();
|
let partition_map0 = conn.partition_map.clone();
|
||||||
|
|
||||||
assert_eq!((65538..65539),
|
assert_eq!(
|
||||||
conn.partition_map.allocate_entids(":db.part/user", 1));
|
(65538..65539),
|
||||||
let tx_report1 = assert_transact!(conn, r#"[
|
conn.partition_map.allocate_entids(":db.part/user", 1)
|
||||||
|
);
|
||||||
|
let tx_report1 = assert_transact!(
|
||||||
|
conn,
|
||||||
|
r#"[
|
||||||
[:db/add 65538 :test/one 1]
|
[:db/add 65538 :test/one 1]
|
||||||
[:db/add 65538 :test/many 2]
|
[:db/add 65538 :test/many 2]
|
||||||
[:db/add 65538 :test/many 3]
|
[:db/add 65538 :test/many 3]
|
||||||
]"#);
|
]"#
|
||||||
|
);
|
||||||
let schema1 = conn.schema.clone();
|
let schema1 = conn.schema.clone();
|
||||||
let partition_map1 = conn.partition_map.clone();
|
let partition_map1 = conn.partition_map.clone();
|
||||||
|
|
||||||
assert_matches!(conn.last_transaction(),
|
assert_matches!(
|
||||||
|
conn.last_transaction(),
|
||||||
"[[65538 :test/one 1 ?tx true]
|
"[[65538 :test/one 1 ?tx true]
|
||||||
[65538 :test/many 2 ?tx true]
|
[65538 :test/many 2 ?tx true]
|
||||||
[65538 :test/many 3 ?tx true]
|
[65538 :test/many 3 ?tx true]
|
||||||
[?tx :db/txInstant ?ms ?tx true]]");
|
[?tx :db/txInstant ?ms ?tx true]]"
|
||||||
|
);
|
||||||
|
|
||||||
let second = "[
|
let second = "[
|
||||||
[65536 :db/ident :test/one]
|
[65536 :db/ident :test/one]
|
||||||
|
@ -524,20 +720,25 @@ mod tests {
|
||||||
]";
|
]";
|
||||||
assert_matches!(conn.datoms(), second);
|
assert_matches!(conn.datoms(), second);
|
||||||
|
|
||||||
let tx_report2 = assert_transact!(conn, r#"[
|
let tx_report2 = assert_transact!(
|
||||||
|
conn,
|
||||||
|
r#"[
|
||||||
[:db/add 65538 :test/one 2]
|
[:db/add 65538 :test/one 2]
|
||||||
[:db/add 65538 :test/many 2]
|
[:db/add 65538 :test/many 2]
|
||||||
[:db/retract 65538 :test/many 3]
|
[:db/retract 65538 :test/many 3]
|
||||||
[:db/add 65538 :test/many 4]
|
[:db/add 65538 :test/many 4]
|
||||||
]"#);
|
]"#
|
||||||
|
);
|
||||||
let schema2 = conn.schema.clone();
|
let schema2 = conn.schema.clone();
|
||||||
|
|
||||||
assert_matches!(conn.last_transaction(),
|
assert_matches!(
|
||||||
|
conn.last_transaction(),
|
||||||
"[[65538 :test/one 1 ?tx false]
|
"[[65538 :test/one 1 ?tx false]
|
||||||
[65538 :test/one 2 ?tx true]
|
[65538 :test/one 2 ?tx true]
|
||||||
[65538 :test/many 3 ?tx false]
|
[65538 :test/many 3 ?tx false]
|
||||||
[65538 :test/many 4 ?tx true]
|
[65538 :test/many 4 ?tx true]
|
||||||
[?tx :db/txInstant ?ms ?tx true]]");
|
[?tx :db/txInstant ?ms ?tx true]]"
|
||||||
|
);
|
||||||
|
|
||||||
let third = "[
|
let third = "[
|
||||||
[65536 :db/ident :test/one]
|
[65536 :db/ident :test/one]
|
||||||
|
@ -555,8 +756,13 @@ mod tests {
|
||||||
assert_matches!(conn.datoms(), third);
|
assert_matches!(conn.datoms(), third);
|
||||||
|
|
||||||
let (new_schema, new_partition_map) = move_from_main_timeline(
|
let (new_schema, new_partition_map) = move_from_main_timeline(
|
||||||
&conn.sqlite, &conn.schema, conn.partition_map.clone(),
|
&conn.sqlite,
|
||||||
tx_report2.tx_id.., 1).expect("moved timeline");
|
&conn.schema,
|
||||||
|
conn.partition_map.clone(),
|
||||||
|
tx_report2.tx_id..,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.expect("moved timeline");
|
||||||
update_conn(&mut conn, &new_schema, &new_partition_map);
|
update_conn(&mut conn, &new_schema, &new_partition_map);
|
||||||
|
|
||||||
assert_matches!(conn.datoms(), second);
|
assert_matches!(conn.datoms(), second);
|
||||||
|
@ -567,8 +773,13 @@ mod tests {
|
||||||
assert_eq!(conn.partition_map, partition_map1);
|
assert_eq!(conn.partition_map, partition_map1);
|
||||||
|
|
||||||
let (new_schema, new_partition_map) = move_from_main_timeline(
|
let (new_schema, new_partition_map) = move_from_main_timeline(
|
||||||
&conn.sqlite, &conn.schema, conn.partition_map.clone(),
|
&conn.sqlite,
|
||||||
tx_report1.tx_id.., 2).expect("moved timeline");
|
&conn.schema,
|
||||||
|
conn.partition_map.clone(),
|
||||||
|
tx_report1.tx_id..,
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
.expect("moved timeline");
|
||||||
update_conn(&mut conn, &new_schema, &new_partition_map);
|
update_conn(&mut conn, &new_schema, &new_partition_map);
|
||||||
assert_matches!(conn.datoms(), first);
|
assert_matches!(conn.datoms(), first);
|
||||||
assert_eq!(None, new_schema);
|
assert_eq!(None, new_schema);
|
||||||
|
@ -576,8 +787,13 @@ mod tests {
|
||||||
assert_eq!(conn.partition_map, partition_map0);
|
assert_eq!(conn.partition_map, partition_map0);
|
||||||
|
|
||||||
let (new_schema, new_partition_map) = move_from_main_timeline(
|
let (new_schema, new_partition_map) = move_from_main_timeline(
|
||||||
&conn.sqlite, &conn.schema, conn.partition_map.clone(),
|
&conn.sqlite,
|
||||||
tx_report0.tx_id.., 3).expect("moved timeline");
|
&conn.schema,
|
||||||
|
conn.partition_map.clone(),
|
||||||
|
tx_report0.tx_id..,
|
||||||
|
3,
|
||||||
|
)
|
||||||
|
.expect("moved timeline");
|
||||||
update_conn(&mut conn, &new_schema, &new_partition_map);
|
update_conn(&mut conn, &new_schema, &new_partition_map);
|
||||||
assert_eq!(true, new_schema.is_some());
|
assert_eq!(true, new_schema.is_some());
|
||||||
assert_eq!(bootstrap::bootstrap_schema(), conn.schema);
|
assert_eq!(bootstrap::bootstrap_schema(), conn.schema);
|
||||||
|
@ -593,31 +809,47 @@ mod tests {
|
||||||
|
|
||||||
let partition_map_after_bootstrap = conn.partition_map.clone();
|
let partition_map_after_bootstrap = conn.partition_map.clone();
|
||||||
|
|
||||||
assert_eq!((65536..65539),
|
assert_eq!(
|
||||||
conn.partition_map.allocate_entids(":db.part/user", 3));
|
(65536..65539),
|
||||||
let tx_report0 = assert_transact!(conn, r#"[
|
conn.partition_map.allocate_entids(":db.part/user", 3)
|
||||||
|
);
|
||||||
|
let tx_report0 = assert_transact!(
|
||||||
|
conn,
|
||||||
|
r#"[
|
||||||
{:db/id 65536 :db/ident :test/one :db/valueType :db.type/long :db/cardinality :db.cardinality/one}
|
{:db/id 65536 :db/ident :test/one :db/valueType :db.type/long :db/cardinality :db.cardinality/one}
|
||||||
{:db/id 65537 :db/ident :test/many :db/valueType :db.type/long :db/cardinality :db.cardinality/many}
|
{:db/id 65537 :db/ident :test/many :db/valueType :db.type/long :db/cardinality :db.cardinality/many}
|
||||||
]"#);
|
]"#
|
||||||
|
);
|
||||||
|
|
||||||
assert_transact!(conn, r#"[
|
assert_transact!(
|
||||||
|
conn,
|
||||||
|
r#"[
|
||||||
[:db/add 65538 :test/one 1]
|
[:db/add 65538 :test/one 1]
|
||||||
[:db/add 65538 :test/many 2]
|
[:db/add 65538 :test/many 2]
|
||||||
[:db/add 65538 :test/many 3]
|
[:db/add 65538 :test/many 3]
|
||||||
]"#);
|
]"#
|
||||||
|
);
|
||||||
|
|
||||||
assert_transact!(conn, r#"[
|
assert_transact!(
|
||||||
|
conn,
|
||||||
|
r#"[
|
||||||
[:db/add 65538 :test/one 2]
|
[:db/add 65538 :test/one 2]
|
||||||
[:db/add 65538 :test/many 2]
|
[:db/add 65538 :test/many 2]
|
||||||
[:db/retract 65538 :test/many 3]
|
[:db/retract 65538 :test/many 3]
|
||||||
[:db/add 65538 :test/many 4]
|
[:db/add 65538 :test/many 4]
|
||||||
]"#);
|
]"#
|
||||||
|
);
|
||||||
|
|
||||||
// Remove all of these transactions from the main timeline,
|
// Remove all of these transactions from the main timeline,
|
||||||
// ensure we get back to a "just bootstrapped" state.
|
// ensure we get back to a "just bootstrapped" state.
|
||||||
let (new_schema, new_partition_map) = move_from_main_timeline(
|
let (new_schema, new_partition_map) = move_from_main_timeline(
|
||||||
&conn.sqlite, &conn.schema, conn.partition_map.clone(),
|
&conn.sqlite,
|
||||||
tx_report0.tx_id.., 1).expect("moved timeline");
|
&conn.schema,
|
||||||
|
conn.partition_map.clone(),
|
||||||
|
tx_report0.tx_id..,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.expect("moved timeline");
|
||||||
update_conn(&mut conn, &new_schema, &new_partition_map);
|
update_conn(&mut conn, &new_schema, &new_partition_map);
|
||||||
|
|
||||||
update_conn(&mut conn, &new_schema, &new_partition_map);
|
update_conn(&mut conn, &new_schema, &new_partition_map);
|
||||||
|
|
651
db/src/tx.rs
651
db/src/tx.rs
File diff suppressed because it is too large
Load diff
|
@ -8,24 +8,13 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
use std::collections::{
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
BTreeSet,
|
|
||||||
BTreeMap,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mentat_core::{
|
use core_traits::{Entid, TypedValue, ValueType};
|
||||||
Entid,
|
|
||||||
TypedValue,
|
|
||||||
ValueType,
|
|
||||||
};
|
|
||||||
|
|
||||||
use errors::{
|
use db_traits::errors::CardinalityConflict;
|
||||||
CardinalityConflict,
|
|
||||||
};
|
|
||||||
|
|
||||||
use internal_types::{
|
use crate::internal_types::AEVTrie;
|
||||||
AEVTrie,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Map from found [e a v] to expected type.
|
/// Map from found [e a v] to expected type.
|
||||||
pub(crate) type TypeDisagreements = BTreeMap<(Entid, Entid, TypedValue), ValueType>;
|
pub(crate) type TypeDisagreements = BTreeMap<(Entid, Entid, TypedValue), ValueType>;
|
||||||
|
@ -63,7 +52,9 @@ pub(crate) fn type_disagreements<'schema>(aev_trie: &AEVTrie<'schema>) -> TypeDi
|
||||||
/// We try to be maximally helpful by yielding every malformed set of datoms, rather than just the
|
/// We try to be maximally helpful by yielding every malformed set of datoms, rather than just the
|
||||||
/// first set, or even the first conflict. In the future, we might change this choice, or allow the
|
/// first set, or even the first conflict. In the future, we might change this choice, or allow the
|
||||||
/// consumer to specify the robustness of the cardinality checking desired.
|
/// consumer to specify the robustness of the cardinality checking desired.
|
||||||
pub(crate) fn cardinality_conflicts<'schema>(aev_trie: &AEVTrie<'schema>) -> Vec<CardinalityConflict> {
|
pub(crate) fn cardinality_conflicts<'schema>(
|
||||||
|
aev_trie: &AEVTrie<'schema>,
|
||||||
|
) -> Vec<CardinalityConflict> {
|
||||||
let mut errors = vec![];
|
let mut errors = vec![];
|
||||||
|
|
||||||
for (&(a, attribute), evs) in aev_trie {
|
for (&(a, attribute), evs) in aev_trie {
|
||||||
|
|
|
@ -8,59 +8,49 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
use std::sync::{
|
use std::sync::{Arc, Weak};
|
||||||
Arc,
|
|
||||||
Weak,
|
|
||||||
};
|
|
||||||
|
|
||||||
use std::sync::mpsc::{
|
use std::sync::mpsc::{channel, Receiver, RecvError, Sender};
|
||||||
channel,
|
|
||||||
Receiver,
|
|
||||||
RecvError,
|
|
||||||
Sender,
|
|
||||||
};
|
|
||||||
|
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
use indexmap::{
|
use indexmap::IndexMap;
|
||||||
IndexMap,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mentat_core::{
|
use core_traits::{Entid, TypedValue};
|
||||||
Entid,
|
|
||||||
Schema,
|
|
||||||
TypedValue,
|
|
||||||
};
|
|
||||||
|
|
||||||
use edn::entities::{
|
use mentat_core::Schema;
|
||||||
OpType,
|
|
||||||
};
|
|
||||||
|
|
||||||
use errors::{
|
use edn::entities::OpType;
|
||||||
Result,
|
|
||||||
};
|
|
||||||
|
|
||||||
use types::{
|
use db_traits::errors::Result;
|
||||||
AttributeSet,
|
|
||||||
};
|
|
||||||
|
|
||||||
use watcher::TransactWatcher;
|
use crate::types::AttributeSet;
|
||||||
|
|
||||||
|
use crate::watcher::TransactWatcher;
|
||||||
|
|
||||||
pub struct TxObserver {
|
pub struct TxObserver {
|
||||||
notify_fn: Arc<Box<Fn(&str, IndexMap<&Entid, &AttributeSet>) + Send + Sync>>,
|
#[allow(clippy::type_complexity)]
|
||||||
|
notify_fn: Arc<Box<dyn Fn(&str, IndexMap<&Entid, &AttributeSet>) + Send + Sync>>,
|
||||||
attributes: AttributeSet,
|
attributes: AttributeSet,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TxObserver {
|
impl TxObserver {
|
||||||
pub fn new<F>(attributes: AttributeSet, notify_fn: F) -> TxObserver where F: Fn(&str, IndexMap<&Entid, &AttributeSet>) + 'static + Send + Sync {
|
pub fn new<F>(attributes: AttributeSet, notify_fn: F) -> TxObserver
|
||||||
|
where
|
||||||
|
F: Fn(&str, IndexMap<&Entid, &AttributeSet>) + 'static + Send + Sync,
|
||||||
|
{
|
||||||
TxObserver {
|
TxObserver {
|
||||||
notify_fn: Arc::new(Box::new(notify_fn)),
|
notify_fn: Arc::new(Box::new(notify_fn)),
|
||||||
attributes,
|
attributes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn applicable_reports<'r>(&self, reports: &'r IndexMap<Entid, AttributeSet>) -> IndexMap<&'r Entid, &'r AttributeSet> {
|
pub fn applicable_reports<'r>(
|
||||||
reports.into_iter()
|
&self,
|
||||||
|
reports: &'r IndexMap<Entid, AttributeSet>,
|
||||||
|
) -> IndexMap<&'r Entid, &'r AttributeSet> {
|
||||||
|
reports
|
||||||
|
.into_iter()
|
||||||
.filter(|&(_txid, attrs)| !self.attributes.is_disjoint(attrs))
|
.filter(|&(_txid, attrs)| !self.attributes.is_disjoint(attrs))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
@ -80,7 +70,10 @@ pub struct TxCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TxCommand {
|
impl TxCommand {
|
||||||
fn new(observers: &Arc<IndexMap<String, Arc<TxObserver>>>, reports: IndexMap<Entid, AttributeSet>) -> Self {
|
fn new(
|
||||||
|
observers: &Arc<IndexMap<String, Arc<TxObserver>>>,
|
||||||
|
reports: IndexMap<Entid, AttributeSet>,
|
||||||
|
) -> Self {
|
||||||
TxCommand {
|
TxCommand {
|
||||||
reports,
|
reports,
|
||||||
observers: Arc::downgrade(observers),
|
observers: Arc::downgrade(observers),
|
||||||
|
@ -90,20 +83,21 @@ impl TxCommand {
|
||||||
|
|
||||||
impl Command for TxCommand {
|
impl Command for TxCommand {
|
||||||
fn execute(&mut self) {
|
fn execute(&mut self) {
|
||||||
self.observers.upgrade().map(|observers| {
|
if let Some(observers) = self.observers.upgrade() {
|
||||||
for (key, observer) in observers.iter() {
|
for (key, observer) in observers.iter() {
|
||||||
let applicable_reports = observer.applicable_reports(&self.reports);
|
let applicable_reports = observer.applicable_reports(&self.reports);
|
||||||
if !applicable_reports.is_empty() {
|
if !applicable_reports.is_empty() {
|
||||||
observer.notify(&key, applicable_reports);
|
observer.notify(&key, applicable_reports);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
pub struct TxObservationService {
|
pub struct TxObservationService {
|
||||||
observers: Arc<IndexMap<String, Arc<TxObserver>>>,
|
observers: Arc<IndexMap<String, Arc<TxObserver>>>,
|
||||||
executor: Option<Sender<Box<Command + Send>>>,
|
executor: Option<Sender<Box<dyn Command + Send>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TxObservationService {
|
impl TxObservationService {
|
||||||
|
@ -115,7 +109,7 @@ impl TxObservationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// For testing purposes
|
// For testing purposes
|
||||||
pub fn is_registered(&self, key: &String) -> bool {
|
pub fn is_registered(&self, key: &str) -> bool {
|
||||||
self.observers.contains_key(key)
|
self.observers.contains_key(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +117,7 @@ impl TxObservationService {
|
||||||
Arc::make_mut(&mut self.observers).insert(key, observer);
|
Arc::make_mut(&mut self.observers).insert(key, observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deregister(&mut self, key: &String) {
|
pub fn deregister(&mut self, key: &str) {
|
||||||
Arc::make_mut(&mut self.observers).remove(key);
|
Arc::make_mut(&mut self.observers).remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +132,11 @@ impl TxObservationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
let executor = self.executor.get_or_insert_with(|| {
|
let executor = self.executor.get_or_insert_with(|| {
|
||||||
let (tx, rx): (Sender<Box<Command + Send>>, Receiver<Box<Command + Send>>) = channel();
|
#[allow(clippy::type_complexity)]
|
||||||
|
let (tx, rx): (
|
||||||
|
Sender<Box<dyn Command + Send>>,
|
||||||
|
Receiver<Box<dyn Command + Send>>,
|
||||||
|
) = channel();
|
||||||
let mut worker = CommandExecutor::new(rx);
|
let mut worker = CommandExecutor::new(rx);
|
||||||
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
|
@ -159,6 +157,7 @@ impl Drop for TxObservationService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
pub struct InProgressObserverTransactWatcher {
|
pub struct InProgressObserverTransactWatcher {
|
||||||
collected_attributes: AttributeSet,
|
collected_attributes: AttributeSet,
|
||||||
pub txes: IndexMap<Entid, AttributeSet>,
|
pub txes: IndexMap<Entid, AttributeSet>,
|
||||||
|
@ -179,21 +178,19 @@ impl TransactWatcher for InProgressObserverTransactWatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn done(&mut self, t: &Entid, _schema: &Schema) -> Result<()> {
|
fn done(&mut self, t: &Entid, _schema: &Schema) -> Result<()> {
|
||||||
let collected_attributes = ::std::mem::replace(&mut self.collected_attributes, Default::default());
|
let collected_attributes = ::std::mem::take(&mut self.collected_attributes);
|
||||||
self.txes.insert(*t, collected_attributes);
|
self.txes.insert(*t, collected_attributes);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CommandExecutor {
|
struct CommandExecutor {
|
||||||
receiver: Receiver<Box<Command + Send>>,
|
receiver: Receiver<Box<dyn Command + Send>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommandExecutor {
|
impl CommandExecutor {
|
||||||
fn new(rx: Receiver<Box<Command + Send>>) -> Self {
|
fn new(rx: Receiver<Box<dyn Command + Send>>) -> Self {
|
||||||
CommandExecutor {
|
CommandExecutor { receiver: rx }
|
||||||
receiver: rx,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main(&mut self) {
|
fn main(&mut self) {
|
||||||
|
@ -204,12 +201,10 @@ impl CommandExecutor {
|
||||||
// sync_channel) is disconnected, implying that no further messages will ever be
|
// sync_channel) is disconnected, implying that no further messages will ever be
|
||||||
// received."
|
// received."
|
||||||
// No need to log here.
|
// No need to log here.
|
||||||
return
|
return;
|
||||||
},
|
}
|
||||||
|
|
||||||
Ok(mut cmd) => {
|
Ok(mut cmd) => cmd.execute(),
|
||||||
cmd.execute()
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,39 +10,19 @@
|
||||||
|
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use std::collections::{
|
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||||
BTreeMap,
|
use std::iter::FromIterator;
|
||||||
BTreeSet,
|
use std::ops::{Deref, DerefMut, Range};
|
||||||
HashMap,
|
|
||||||
};
|
|
||||||
use std::iter::{
|
|
||||||
FromIterator,
|
|
||||||
};
|
|
||||||
use std::ops::{
|
|
||||||
Deref,
|
|
||||||
DerefMut,
|
|
||||||
Range,
|
|
||||||
};
|
|
||||||
|
|
||||||
extern crate mentat_core;
|
extern crate mentat_core;
|
||||||
|
|
||||||
pub use self::mentat_core::{
|
use core_traits::{Entid, TypedValue, ValueType};
|
||||||
Attribute,
|
|
||||||
AttributeBitFlags,
|
|
||||||
DateTime,
|
|
||||||
Entid,
|
|
||||||
Schema,
|
|
||||||
TypedValue,
|
|
||||||
Utc,
|
|
||||||
ValueType,
|
|
||||||
};
|
|
||||||
|
|
||||||
use edn::entities::{
|
pub use self::mentat_core::{DateTime, Schema, Utc};
|
||||||
EntityPlace,
|
|
||||||
TempId,
|
|
||||||
};
|
|
||||||
|
|
||||||
use errors;
|
use edn::entities::{EntityPlace, TempId};
|
||||||
|
|
||||||
|
use db_traits::errors;
|
||||||
|
|
||||||
/// Represents one partition of the entid space.
|
/// Represents one partition of the entid space.
|
||||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||||
|
@ -60,12 +40,22 @@ pub struct Partition {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Partition {
|
impl Partition {
|
||||||
pub fn new(start: Entid, end: Entid, next_entid_to_allocate: Entid, allow_excision: bool) -> Partition {
|
pub fn new(
|
||||||
|
start: Entid,
|
||||||
|
end: Entid,
|
||||||
|
next_entid_to_allocate: Entid,
|
||||||
|
allow_excision: bool,
|
||||||
|
) -> Partition {
|
||||||
assert!(
|
assert!(
|
||||||
start <= next_entid_to_allocate && next_entid_to_allocate <= end,
|
start <= next_entid_to_allocate && next_entid_to_allocate <= end,
|
||||||
"A partition represents a monotonic increasing sequence of entids."
|
"A partition represents a monotonic increasing sequence of entids."
|
||||||
);
|
);
|
||||||
Partition { start, end, next_entid_to_allocate, allow_excision }
|
Partition {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
next_entid_to_allocate,
|
||||||
|
allow_excision,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains_entid(&self, e: Entid) -> bool {
|
pub fn contains_entid(&self, e: Entid) -> bool {
|
||||||
|
@ -81,7 +71,10 @@ impl Partition {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_next_entid(&mut self, e: Entid) {
|
pub fn set_next_entid(&mut self, e: Entid) {
|
||||||
assert!(self.allows_entid(e), "Partition index must be within its allocated space.");
|
assert!(
|
||||||
|
self.allows_entid(e),
|
||||||
|
"Partition index must be within its allocated space."
|
||||||
|
);
|
||||||
self.next_entid_to_allocate = e;
|
self.next_entid_to_allocate = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,6 +87,7 @@ impl Partition {
|
||||||
|
|
||||||
/// Map partition names to `Partition` instances.
|
/// Map partition names to `Partition` instances.
|
||||||
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "syncable", derive(Serialize, Deserialize))]
|
||||||
pub struct PartitionMap(BTreeMap<String, Partition>);
|
pub struct PartitionMap(BTreeMap<String, Partition>);
|
||||||
|
|
||||||
impl Deref for PartitionMap {
|
impl Deref for PartitionMap {
|
||||||
|
@ -133,8 +127,8 @@ pub struct DB {
|
||||||
impl DB {
|
impl DB {
|
||||||
pub fn new(partition_map: PartitionMap, schema: Schema) -> DB {
|
pub fn new(partition_map: PartitionMap, schema: Schema) -> DB {
|
||||||
DB {
|
DB {
|
||||||
partition_map: partition_map,
|
partition_map,
|
||||||
schema: schema
|
schema,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -161,7 +155,8 @@ pub type AttributeSet = BTreeSet<Entid>;
|
||||||
pub trait TransactableValue: Clone {
|
pub trait TransactableValue: Clone {
|
||||||
/// Coerce this value place into the given type. This is where we perform schema-aware
|
/// Coerce this value place into the given type. This is where we perform schema-aware
|
||||||
/// coercion, for example coercing an integral value into a ref where appropriate.
|
/// coercion, for example coercing an integral value into a ref where appropriate.
|
||||||
fn into_typed_value(self, schema: &Schema, value_type: ValueType) -> errors::Result<TypedValue>;
|
fn into_typed_value(self, schema: &Schema, value_type: ValueType)
|
||||||
|
-> errors::Result<TypedValue>;
|
||||||
|
|
||||||
/// Make an entity place out of this value place. This is where we limit values in nested maps
|
/// Make an entity place out of this value place. This is where we limit values in nested maps
|
||||||
/// to valid entity places.
|
/// to valid entity places.
|
||||||
|
|
|
@ -13,42 +13,24 @@
|
||||||
//! This module implements the upsert resolution algorithm described at
|
//! This module implements the upsert resolution algorithm described at
|
||||||
//! https://github.com/mozilla/mentat/wiki/Transacting:-upsert-resolution-algorithm.
|
//! https://github.com/mozilla/mentat/wiki/Transacting:-upsert-resolution-algorithm.
|
||||||
|
|
||||||
use std::collections::{
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
BTreeMap,
|
|
||||||
BTreeSet,
|
|
||||||
};
|
|
||||||
|
|
||||||
use indexmap;
|
use indexmap;
|
||||||
use petgraph::unionfind;
|
use petgraph::unionfind;
|
||||||
|
|
||||||
use errors::{
|
use crate::internal_types::{
|
||||||
DbErrorKind,
|
Population, TempIdHandle, TempIdMap, Term, TermWithTempIds, TermWithoutTempIds, TypedValueOr,
|
||||||
Result,
|
|
||||||
};
|
|
||||||
use types::{
|
|
||||||
AVPair,
|
|
||||||
};
|
|
||||||
use internal_types::{
|
|
||||||
Population,
|
|
||||||
TempIdHandle,
|
|
||||||
TempIdMap,
|
|
||||||
Term,
|
|
||||||
TermWithoutTempIds,
|
|
||||||
TermWithTempIds,
|
|
||||||
TypedValueOr,
|
|
||||||
};
|
};
|
||||||
|
use crate::types::AVPair;
|
||||||
|
use db_traits::errors::{DbErrorKind, Result};
|
||||||
|
|
||||||
use mentat_core::util::Either::*;
|
use mentat_core::util::Either::*;
|
||||||
|
|
||||||
use mentat_core::{
|
use core_traits::{attribute, Attribute, Entid, TypedValue};
|
||||||
attribute,
|
|
||||||
Attribute,
|
use crate::schema::SchemaBuilding;
|
||||||
Entid,
|
|
||||||
Schema,
|
|
||||||
TypedValue,
|
|
||||||
};
|
|
||||||
use edn::entities::OpType;
|
use edn::entities::OpType;
|
||||||
use schema::SchemaBuilding;
|
use mentat_core::Schema;
|
||||||
|
|
||||||
/// A "Simple upsert" that looks like [:db/add TEMPID a v], where a is :db.unique/identity.
|
/// A "Simple upsert" that looks like [:db/add TEMPID a v], where a is :db.unique/identity.
|
||||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||||
|
@ -102,7 +84,10 @@ pub(crate) struct FinalPopulations {
|
||||||
impl Generation {
|
impl Generation {
|
||||||
/// Split entities into a generation of populations that need to evolve to have their tempids
|
/// Split entities into a generation of populations that need to evolve to have their tempids
|
||||||
/// resolved or allocated, and a population of inert entities that do not reference tempids.
|
/// resolved or allocated, and a population of inert entities that do not reference tempids.
|
||||||
pub(crate) fn from<I>(terms: I, schema: &Schema) -> Result<(Generation, Population)> where I: IntoIterator<Item=TermWithTempIds> {
|
pub(crate) fn from<I>(terms: I, schema: &Schema) -> Result<(Generation, Population)>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = TermWithTempIds>,
|
||||||
|
{
|
||||||
let mut generation = Generation::default();
|
let mut generation = Generation::default();
|
||||||
let mut inert = vec![];
|
let mut inert = vec![];
|
||||||
|
|
||||||
|
@ -117,22 +102,28 @@ impl Generation {
|
||||||
if op == OpType::Add && is_unique(a)? {
|
if op == OpType::Add && is_unique(a)? {
|
||||||
generation.upserts_ev.push(UpsertEV(e, a, v));
|
generation.upserts_ev.push(UpsertEV(e, a, v));
|
||||||
} else {
|
} else {
|
||||||
generation.allocations.push(Term::AddOrRetract(op, Right(e), a, Right(v)));
|
generation
|
||||||
|
.allocations
|
||||||
|
.push(Term::AddOrRetract(op, Right(e), a, Right(v)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
Term::AddOrRetract(op, Right(e), a, Left(v)) => {
|
Term::AddOrRetract(op, Right(e), a, Left(v)) => {
|
||||||
if op == OpType::Add && is_unique(a)? {
|
if op == OpType::Add && is_unique(a)? {
|
||||||
generation.upserts_e.push(UpsertE(e, a, v));
|
generation.upserts_e.push(UpsertE(e, a, v));
|
||||||
} else {
|
} else {
|
||||||
generation.allocations.push(Term::AddOrRetract(op, Right(e), a, Left(v)));
|
generation
|
||||||
|
.allocations
|
||||||
|
.push(Term::AddOrRetract(op, Right(e), a, Left(v)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
Term::AddOrRetract(op, Left(e), a, Right(v)) => {
|
Term::AddOrRetract(op, Left(e), a, Right(v)) => {
|
||||||
generation.allocations.push(Term::AddOrRetract(op, Left(e), a, Right(v)));
|
generation
|
||||||
},
|
.allocations
|
||||||
|
.push(Term::AddOrRetract(op, Left(e), a, Right(v)));
|
||||||
|
}
|
||||||
Term::AddOrRetract(op, Left(e), a, Left(v)) => {
|
Term::AddOrRetract(op, Left(e), a, Left(v)) => {
|
||||||
inert.push(Term::AddOrRetract(op, Left(e), a, Left(v)));
|
inert.push(Term::AddOrRetract(op, Left(e), a, Left(v)));
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +152,10 @@ impl Generation {
|
||||||
for UpsertE(t, a, v) in self.upserts_e {
|
for UpsertE(t, a, v) in self.upserts_e {
|
||||||
match temp_id_map.get(&*t) {
|
match temp_id_map.get(&*t) {
|
||||||
Some(&n) => next.upserted.push(Term::AddOrRetract(OpType::Add, n, a, v)),
|
Some(&n) => next.upserted.push(Term::AddOrRetract(OpType::Add, n, a, v)),
|
||||||
None => next.allocations.push(Term::AddOrRetract(OpType::Add, Right(t), a, Left(v))),
|
None => {
|
||||||
|
next.allocations
|
||||||
|
.push(Term::AddOrRetract(OpType::Add, Right(t), a, Left(v)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,10 +166,13 @@ impl Generation {
|
||||||
// could conflict. Moving straight to resolved doesn't give us a chance to
|
// could conflict. Moving straight to resolved doesn't give us a chance to
|
||||||
// search the store for the conflict.
|
// search the store for the conflict.
|
||||||
next.upserts_e.push(UpsertE(t1, a, TypedValue::Ref(n2.0)))
|
next.upserts_e.push(UpsertE(t1, a, TypedValue::Ref(n2.0)))
|
||||||
},
|
}
|
||||||
(None, Some(&n2)) => next.upserts_e.push(UpsertE(t1, a, TypedValue::Ref(n2.0))),
|
(None, Some(&n2)) => next.upserts_e.push(UpsertE(t1, a, TypedValue::Ref(n2.0))),
|
||||||
(Some(&n1), None) => next.allocations.push(Term::AddOrRetract(OpType::Add, Left(n1), a, Right(t2))),
|
(Some(&n1), None) => {
|
||||||
(None, None) => next.upserts_ev.push(UpsertEV(t1, a, t2))
|
next.allocations
|
||||||
|
.push(Term::AddOrRetract(OpType::Add, Left(n1), a, Right(t2)))
|
||||||
|
}
|
||||||
|
(None, None) => next.upserts_ev.push(UpsertEV(t1, a, t2)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,23 +184,40 @@ impl Generation {
|
||||||
match term {
|
match term {
|
||||||
Term::AddOrRetract(op, Right(t1), a, Right(t2)) => {
|
Term::AddOrRetract(op, Right(t1), a, Right(t2)) => {
|
||||||
match (temp_id_map.get(&*t1), temp_id_map.get(&*t2)) {
|
match (temp_id_map.get(&*t1), temp_id_map.get(&*t2)) {
|
||||||
(Some(&n1), Some(&n2)) => next.resolved.push(Term::AddOrRetract(op, n1, a, TypedValue::Ref(n2.0))),
|
(Some(&n1), Some(&n2)) => {
|
||||||
(None, Some(&n2)) => next.allocations.push(Term::AddOrRetract(op, Right(t1), a, Left(TypedValue::Ref(n2.0)))),
|
next.resolved
|
||||||
(Some(&n1), None) => next.allocations.push(Term::AddOrRetract(op, Left(n1), a, Right(t2))),
|
.push(Term::AddOrRetract(op, n1, a, TypedValue::Ref(n2.0)))
|
||||||
(None, None) => next.allocations.push(Term::AddOrRetract(op, Right(t1), a, Right(t2))),
|
|
||||||
}
|
}
|
||||||
},
|
(None, Some(&n2)) => next.allocations.push(Term::AddOrRetract(
|
||||||
Term::AddOrRetract(op, Right(t), a, Left(v)) => {
|
op,
|
||||||
match temp_id_map.get(&*t) {
|
Right(t1),
|
||||||
|
a,
|
||||||
|
Left(TypedValue::Ref(n2.0)),
|
||||||
|
)),
|
||||||
|
(Some(&n1), None) => {
|
||||||
|
next.allocations
|
||||||
|
.push(Term::AddOrRetract(op, Left(n1), a, Right(t2)))
|
||||||
|
}
|
||||||
|
(None, None) => {
|
||||||
|
next.allocations
|
||||||
|
.push(Term::AddOrRetract(op, Right(t1), a, Right(t2)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Term::AddOrRetract(op, Right(t), a, Left(v)) => match temp_id_map.get(&*t) {
|
||||||
Some(&n) => next.resolved.push(Term::AddOrRetract(op, n, a, v)),
|
Some(&n) => next.resolved.push(Term::AddOrRetract(op, n, a, v)),
|
||||||
None => next.allocations.push(Term::AddOrRetract(op, Right(t), a, Left(v))),
|
None => next
|
||||||
}
|
.allocations
|
||||||
|
.push(Term::AddOrRetract(op, Right(t), a, Left(v))),
|
||||||
},
|
},
|
||||||
Term::AddOrRetract(op, Left(e), a, Right(t)) => {
|
Term::AddOrRetract(op, Left(e), a, Right(t)) => match temp_id_map.get(&*t) {
|
||||||
match temp_id_map.get(&*t) {
|
Some(&n) => {
|
||||||
Some(&n) => next.resolved.push(Term::AddOrRetract(op, e, a, TypedValue::Ref(n.0))),
|
next.resolved
|
||||||
None => next.allocations.push(Term::AddOrRetract(op, Left(e), a, Right(t))),
|
.push(Term::AddOrRetract(op, e, a, TypedValue::Ref(n.0)))
|
||||||
}
|
}
|
||||||
|
None => next
|
||||||
|
.allocations
|
||||||
|
.push(Term::AddOrRetract(op, Left(e), a, Right(t))),
|
||||||
},
|
},
|
||||||
Term::AddOrRetract(_, Left(_), _, Left(_)) => unreachable!(),
|
Term::AddOrRetract(_, Left(_), _, Left(_)) => unreachable!(),
|
||||||
}
|
}
|
||||||
|
@ -213,7 +227,7 @@ impl Generation {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect id->[a v] pairs that might upsert at this evolutionary step.
|
// Collect id->[a v] pairs that might upsert at this evolutionary step.
|
||||||
pub(crate) fn temp_id_avs<'a>(&'a self) -> Vec<(TempIdHandle, AVPair)> {
|
pub(crate) fn temp_id_avs(&self) -> Vec<(TempIdHandle, AVPair)> {
|
||||||
let mut temp_id_avs: Vec<(TempIdHandle, AVPair)> = vec![];
|
let mut temp_id_avs: Vec<(TempIdHandle, AVPair)> = vec![];
|
||||||
// TODO: map/collect.
|
// TODO: map/collect.
|
||||||
for &UpsertE(ref t, ref a, ref v) in &self.upserts_e {
|
for &UpsertE(ref t, ref a, ref v) in &self.upserts_e {
|
||||||
|
@ -229,7 +243,11 @@ impl Generation {
|
||||||
let mut upserts_ev = vec![];
|
let mut upserts_ev = vec![];
|
||||||
::std::mem::swap(&mut self.upserts_ev, &mut upserts_ev);
|
::std::mem::swap(&mut self.upserts_ev, &mut upserts_ev);
|
||||||
|
|
||||||
self.allocations.extend(upserts_ev.into_iter().map(|UpsertEV(t1, a, t2)| Term::AddOrRetract(OpType::Add, Right(t1), a, Right(t2))));
|
self.allocations.extend(
|
||||||
|
upserts_ev.into_iter().map(|UpsertEV(t1, a, t2)| {
|
||||||
|
Term::AddOrRetract(OpType::Add, Right(t1), a, Right(t2))
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -238,38 +256,48 @@ impl Generation {
|
||||||
///
|
///
|
||||||
/// Some of the tempids may be identified, so we also provide a map from tempid to a dense set
|
/// Some of the tempids may be identified, so we also provide a map from tempid to a dense set
|
||||||
/// of contiguous integer labels.
|
/// of contiguous integer labels.
|
||||||
pub(crate) fn temp_ids_in_allocations(&self, schema: &Schema) -> Result<BTreeMap<TempIdHandle, usize>> {
|
pub(crate) fn temp_ids_in_allocations(
|
||||||
|
&self,
|
||||||
|
schema: &Schema,
|
||||||
|
) -> Result<BTreeMap<TempIdHandle, usize>> {
|
||||||
assert!(self.upserts_e.is_empty(), "All upserts should have been upserted, resolved, or moved to the allocated population!");
|
assert!(self.upserts_e.is_empty(), "All upserts should have been upserted, resolved, or moved to the allocated population!");
|
||||||
assert!(self.upserts_ev.is_empty(), "All upserts should have been upserted, resolved, or moved to the allocated population!");
|
assert!(self.upserts_ev.is_empty(), "All upserts should have been upserted, resolved, or moved to the allocated population!");
|
||||||
|
|
||||||
let mut temp_ids: BTreeSet<TempIdHandle> = BTreeSet::default();
|
let mut temp_ids: BTreeSet<TempIdHandle> = BTreeSet::default();
|
||||||
let mut tempid_avs: BTreeMap<(Entid, TypedValueOr<TempIdHandle>), Vec<TempIdHandle>> = BTreeMap::default();
|
let mut tempid_avs: BTreeMap<(Entid, TypedValueOr<TempIdHandle>), Vec<TempIdHandle>> =
|
||||||
|
BTreeMap::default();
|
||||||
|
|
||||||
for term in self.allocations.iter() {
|
for term in self.allocations.iter() {
|
||||||
match term {
|
match term {
|
||||||
&Term::AddOrRetract(OpType::Add, Right(ref t1), a, Right(ref t2)) => {
|
Term::AddOrRetract(OpType::Add, Right(ref t1), a, Right(ref t2)) => {
|
||||||
temp_ids.insert(t1.clone());
|
temp_ids.insert(t1.clone());
|
||||||
temp_ids.insert(t2.clone());
|
temp_ids.insert(t2.clone());
|
||||||
let attribute: &Attribute = schema.require_attribute_for_entid(a)?;
|
let attribute: &Attribute = schema.require_attribute_for_entid(*a)?;
|
||||||
if attribute.unique == Some(attribute::Unique::Identity) {
|
if attribute.unique == Some(attribute::Unique::Identity) {
|
||||||
tempid_avs.entry((a, Right(t2.clone()))).or_insert(vec![]).push(t1.clone());
|
tempid_avs
|
||||||
|
.entry((*a, Right(t2.clone())))
|
||||||
|
.or_insert_with(Vec::new)
|
||||||
|
.push(t1.clone());
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
&Term::AddOrRetract(OpType::Add, Right(ref t), a, ref x @ Left(_)) => {
|
Term::AddOrRetract(OpType::Add, Right(ref t), a, ref x @ Left(_)) => {
|
||||||
temp_ids.insert(t.clone());
|
temp_ids.insert(t.clone());
|
||||||
let attribute: &Attribute = schema.require_attribute_for_entid(a)?;
|
let attribute: &Attribute = schema.require_attribute_for_entid(*a)?;
|
||||||
if attribute.unique == Some(attribute::Unique::Identity) {
|
if attribute.unique == Some(attribute::Unique::Identity) {
|
||||||
tempid_avs.entry((a, x.clone())).or_insert(vec![]).push(t.clone());
|
tempid_avs
|
||||||
|
.entry((*a, x.clone()))
|
||||||
|
.or_insert_with(Vec::new)
|
||||||
|
.push(t.clone());
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
&Term::AddOrRetract(OpType::Add, Left(_), _, Right(ref t)) => {
|
Term::AddOrRetract(OpType::Add, Left(_), _, Right(ref t)) => {
|
||||||
temp_ids.insert(t.clone());
|
temp_ids.insert(t.clone());
|
||||||
},
|
}
|
||||||
&Term::AddOrRetract(OpType::Add, Left(_), _, Left(_)) => unreachable!(),
|
Term::AddOrRetract(OpType::Add, Left(_), _, Left(_)) => unreachable!(),
|
||||||
&Term::AddOrRetract(OpType::Retract, _, _, _) => {
|
Term::AddOrRetract(OpType::Retract, _, _, _) => {
|
||||||
// [:db/retract ...] entities never allocate entids; they have to resolve due to
|
// [:db/retract ...] entities never allocate entids; they have to resolve due to
|
||||||
// other upserts (or they fail the transaction).
|
// other upserts (or they fail the transaction).
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,16 +307,23 @@ impl Generation {
|
||||||
|
|
||||||
// The union-find implementation from petgraph operates on contiguous indices, so we need to
|
// The union-find implementation from petgraph operates on contiguous indices, so we need to
|
||||||
// maintain the map from our tempids to indices ourselves.
|
// maintain the map from our tempids to indices ourselves.
|
||||||
let temp_ids: BTreeMap<TempIdHandle, usize> = temp_ids.into_iter().enumerate().map(|(i, tempid)| (tempid, i)).collect();
|
let temp_ids: BTreeMap<TempIdHandle, usize> = temp_ids
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, tempid)| (tempid, i))
|
||||||
|
.collect();
|
||||||
|
|
||||||
debug!("need to label tempids aggregated using tempid_avs {:?}", tempid_avs);
|
debug!(
|
||||||
|
"need to label tempids aggregated using tempid_avs {:?}",
|
||||||
|
tempid_avs
|
||||||
|
);
|
||||||
|
|
||||||
for vs in tempid_avs.values() {
|
for vs in tempid_avs.values() {
|
||||||
vs.first().and_then(|first| temp_ids.get(first)).map(|&first_index| {
|
if let Some(&first_index) = vs.first().and_then(|first| temp_ids.get(first)) {
|
||||||
for tempid in vs {
|
for tempid in vs {
|
||||||
temp_ids.get(tempid).map(|&i| uf.union(first_index, i));
|
temp_ids.get(tempid).map(|&i| uf.union(first_index, i));
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("union-find aggregation {:?}", uf.clone().into_labeling());
|
debug!("union-find aggregation {:?}", uf.clone().into_labeling());
|
||||||
|
@ -305,17 +340,26 @@ impl Generation {
|
||||||
for (tempid, tempid_index) in temp_ids {
|
for (tempid, tempid_index) in temp_ids {
|
||||||
let rep = uf.find_mut(tempid_index);
|
let rep = uf.find_mut(tempid_index);
|
||||||
dense_labels.insert(rep);
|
dense_labels.insert(rep);
|
||||||
dense_labels.get_full(&rep).map(|(dense_index, _)| tempid_map.insert(tempid.clone(), dense_index));
|
dense_labels
|
||||||
|
.get_full(&rep)
|
||||||
|
.map(|(dense_index, _)| tempid_map.insert(tempid.clone(), dense_index));
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("labeled tempids using {} labels: {:?}", dense_labels.len(), tempid_map);
|
debug!(
|
||||||
|
"labeled tempids using {} labels: {:?}",
|
||||||
|
dense_labels.len(),
|
||||||
|
tempid_map
|
||||||
|
);
|
||||||
|
|
||||||
Ok(tempid_map)
|
Ok(tempid_map)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// After evolution is complete, use the provided allocated entids to segment `self` into
|
/// After evolution is complete, use the provided allocated entids to segment `self` into
|
||||||
/// populations, each with no references to tempids.
|
/// populations, each with no references to tempids.
|
||||||
pub(crate) fn into_final_populations(self, temp_id_map: &TempIdMap) -> Result<FinalPopulations> {
|
pub(crate) fn into_final_populations(
|
||||||
|
self,
|
||||||
|
temp_id_map: &TempIdMap,
|
||||||
|
) -> Result<FinalPopulations> {
|
||||||
assert!(self.upserts_e.is_empty());
|
assert!(self.upserts_e.is_empty());
|
||||||
assert!(self.upserts_ev.is_empty());
|
assert!(self.upserts_ev.is_empty());
|
||||||
|
|
||||||
|
@ -333,21 +377,27 @@ impl Generation {
|
||||||
(OpType::Add, _, _) => unreachable!(), // This is a coding error -- every tempid in a :db/add entity should resolve or be allocated.
|
(OpType::Add, _, _) => unreachable!(), // This is a coding error -- every tempid in a :db/add entity should resolve or be allocated.
|
||||||
(OpType::Retract, _, _) => bail!(DbErrorKind::NotYetImplemented(format!("[:db/retract ...] entity referenced tempid that did not upsert: one of {}, {}", t1, t2))),
|
(OpType::Retract, _, _) => bail!(DbErrorKind::NotYetImplemented(format!("[:db/retract ...] entity referenced tempid that did not upsert: one of {}, {}", t1, t2))),
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Term::AddOrRetract(op, Right(t), a, Left(v)) => {
|
Term::AddOrRetract(op, Right(t), a, Left(v)) => {
|
||||||
match (op, temp_id_map.get(&*t)) {
|
match (op, temp_id_map.get(&*t)) {
|
||||||
(op, Some(&n)) => Term::AddOrRetract(op, n, a, v),
|
(op, Some(&n)) => Term::AddOrRetract(op, n, a, v),
|
||||||
(OpType::Add, _) => unreachable!(), // This is a coding error.
|
(OpType::Add, _) => unreachable!(), // This is a coding error.
|
||||||
(OpType::Retract, _) => bail!(DbErrorKind::NotYetImplemented(format!("[:db/retract ...] entity referenced tempid that did not upsert: {}", t))),
|
(OpType::Retract, _) => bail!(DbErrorKind::NotYetImplemented(format!(
|
||||||
|
"[:db/retract ...] entity referenced tempid that did not upsert: {}",
|
||||||
|
t
|
||||||
|
))),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
Term::AddOrRetract(op, Left(e), a, Right(t)) => {
|
Term::AddOrRetract(op, Left(e), a, Right(t)) => {
|
||||||
match (op, temp_id_map.get(&*t)) {
|
match (op, temp_id_map.get(&*t)) {
|
||||||
(op, Some(&n)) => Term::AddOrRetract(op, e, a, TypedValue::Ref(n.0)),
|
(op, Some(&n)) => Term::AddOrRetract(op, e, a, TypedValue::Ref(n.0)),
|
||||||
(OpType::Add, _) => unreachable!(), // This is a coding error.
|
(OpType::Add, _) => unreachable!(), // This is a coding error.
|
||||||
(OpType::Retract, _) => bail!(DbErrorKind::NotYetImplemented(format!("[:db/retract ...] entity referenced tempid that did not upsert: {}", t))),
|
(OpType::Retract, _) => bail!(DbErrorKind::NotYetImplemented(format!(
|
||||||
|
"[:db/retract ...] entity referenced tempid that did not upsert: {}",
|
||||||
|
t
|
||||||
|
))),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
Term::AddOrRetract(_, Left(_), _, Left(_)) => unreachable!(), // This is a coding error -- these should not be in allocations.
|
Term::AddOrRetract(_, Left(_), _, Left(_)) => unreachable!(), // This is a coding error -- these should not be in allocations.
|
||||||
};
|
};
|
||||||
populations.allocated.push(allocated);
|
populations.allocated.push(allocated);
|
||||||
|
|
|
@ -17,19 +17,13 @@
|
||||||
// - When observers are registered we want to flip some flags as writes occur so that we can
|
// - When observers are registered we want to flip some flags as writes occur so that we can
|
||||||
// notifying them outside the transaction.
|
// notifying them outside the transaction.
|
||||||
|
|
||||||
use mentat_core::{
|
use core_traits::{Entid, TypedValue};
|
||||||
Entid,
|
|
||||||
Schema,
|
|
||||||
TypedValue,
|
|
||||||
};
|
|
||||||
|
|
||||||
use edn::entities::{
|
use mentat_core::Schema;
|
||||||
OpType,
|
|
||||||
};
|
|
||||||
|
|
||||||
use errors::{
|
use edn::entities::OpType;
|
||||||
Result,
|
|
||||||
};
|
use db_traits::errors::Result;
|
||||||
|
|
||||||
pub trait TransactWatcher {
|
pub trait TransactWatcher {
|
||||||
fn datom(&mut self, op: OpType, e: Entid, a: Entid, v: &TypedValue);
|
fn datom(&mut self, op: OpType, e: Entid, a: Entid, v: &TypedValue);
|
||||||
|
@ -44,8 +38,7 @@ pub trait TransactWatcher {
|
||||||
pub struct NullWatcher();
|
pub struct NullWatcher();
|
||||||
|
|
||||||
impl TransactWatcher for NullWatcher {
|
impl TransactWatcher for NullWatcher {
|
||||||
fn datom(&mut self, _op: OpType, _e: Entid, _a: Entid, _v: &TypedValue) {
|
fn datom(&mut self, _op: OpType, _e: Entid, _a: Entid, _v: &TypedValue) {}
|
||||||
}
|
|
||||||
|
|
||||||
fn done(&mut self, _t: &Entid, _schema: &Schema) -> Result<()> {
|
fn done(&mut self, _t: &Entid, _schema: &Schema) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
|
extern crate core_traits;
|
||||||
extern crate edn;
|
extern crate edn;
|
||||||
extern crate mentat_core;
|
|
||||||
extern crate mentat_db;
|
extern crate mentat_db;
|
||||||
extern crate ordered_float;
|
extern crate ordered_float;
|
||||||
extern crate rusqlite;
|
extern crate rusqlite;
|
||||||
|
@ -18,41 +18,105 @@ use ordered_float::OrderedFloat;
|
||||||
|
|
||||||
use edn::symbols;
|
use edn::symbols;
|
||||||
|
|
||||||
use mentat_core::{TypedValue, ValueType};
|
use core_traits::{TypedValue, ValueType};
|
||||||
use mentat_db::db::TypedSQLValue;
|
use mentat_db::db::TypedSQLValue;
|
||||||
|
|
||||||
// It's not possible to test to_sql_value_pair since rusqlite::ToSqlOutput doesn't implement
|
// It's not possible to test to_sql_value_pair since rusqlite::ToSqlOutput doesn't implement
|
||||||
// PartialEq.
|
// PartialEq.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_sql_value_pair() {
|
fn test_from_sql_value_pair() {
|
||||||
assert_eq!(TypedValue::from_sql_value_pair(rusqlite::types::Value::Integer(1234), 0).unwrap(), TypedValue::Ref(1234));
|
assert_eq!(
|
||||||
|
TypedValue::from_sql_value_pair(rusqlite::types::Value::Integer(1234), 0).unwrap(),
|
||||||
|
TypedValue::Ref(1234)
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(TypedValue::from_sql_value_pair(rusqlite::types::Value::Integer(0), 1).unwrap(), TypedValue::Boolean(false));
|
assert_eq!(
|
||||||
assert_eq!(TypedValue::from_sql_value_pair(rusqlite::types::Value::Integer(1), 1).unwrap(), TypedValue::Boolean(true));
|
TypedValue::from_sql_value_pair(rusqlite::types::Value::Integer(0), 1).unwrap(),
|
||||||
|
TypedValue::Boolean(false)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
TypedValue::from_sql_value_pair(rusqlite::types::Value::Integer(1), 1).unwrap(),
|
||||||
|
TypedValue::Boolean(true)
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(TypedValue::from_sql_value_pair(rusqlite::types::Value::Integer(0), 5).unwrap(), TypedValue::Long(0));
|
assert_eq!(
|
||||||
assert_eq!(TypedValue::from_sql_value_pair(rusqlite::types::Value::Integer(1234), 5).unwrap(), TypedValue::Long(1234));
|
TypedValue::from_sql_value_pair(rusqlite::types::Value::Integer(0), 5).unwrap(),
|
||||||
|
TypedValue::Long(0)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
TypedValue::from_sql_value_pair(rusqlite::types::Value::Integer(1234), 5).unwrap(),
|
||||||
|
TypedValue::Long(1234)
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(TypedValue::from_sql_value_pair(rusqlite::types::Value::Real(0.0), 5).unwrap(), TypedValue::Double(OrderedFloat(0.0)));
|
assert_eq!(
|
||||||
assert_eq!(TypedValue::from_sql_value_pair(rusqlite::types::Value::Real(0.5), 5).unwrap(), TypedValue::Double(OrderedFloat(0.5)));
|
TypedValue::from_sql_value_pair(rusqlite::types::Value::Real(0.0), 5).unwrap(),
|
||||||
|
TypedValue::Double(OrderedFloat(0.0))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
TypedValue::from_sql_value_pair(rusqlite::types::Value::Real(0.5), 5).unwrap(),
|
||||||
|
TypedValue::Double(OrderedFloat(0.5))
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(TypedValue::from_sql_value_pair(rusqlite::types::Value::Text(":db/keyword".into()), 10).unwrap(), TypedValue::typed_string(":db/keyword"));
|
assert_eq!(
|
||||||
assert_eq!(TypedValue::from_sql_value_pair(rusqlite::types::Value::Text(":db/keyword".into()), 13).unwrap(), TypedValue::typed_ns_keyword("db", "keyword"));
|
TypedValue::from_sql_value_pair(rusqlite::types::Value::Text(":db/keyword".into()), 10)
|
||||||
|
.unwrap(),
|
||||||
|
TypedValue::typed_string(":db/keyword")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
TypedValue::from_sql_value_pair(rusqlite::types::Value::Text(":db/keyword".into()), 13)
|
||||||
|
.unwrap(),
|
||||||
|
TypedValue::typed_ns_keyword("db", "keyword")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
TypedValue::from_sql_value_pair(rusqlite::types::Value::Blob(vec![1, 2, 3, 42]), 15)
|
||||||
|
.unwrap(),
|
||||||
|
TypedValue::Bytes((vec![1, 2, 3, 42]).into())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_to_edn_value_pair() {
|
fn test_to_edn_value_pair() {
|
||||||
assert_eq!(TypedValue::Ref(1234).to_edn_value_pair(), (edn::Value::Integer(1234), ValueType::Ref));
|
assert_eq!(
|
||||||
|
TypedValue::Ref(1234).to_edn_value_pair(),
|
||||||
|
(edn::Value::Integer(1234), ValueType::Ref)
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(TypedValue::Boolean(false).to_edn_value_pair(), (edn::Value::Boolean(false), ValueType::Boolean));
|
assert_eq!(
|
||||||
assert_eq!(TypedValue::Boolean(true).to_edn_value_pair(), (edn::Value::Boolean(true), ValueType::Boolean));
|
TypedValue::Boolean(false).to_edn_value_pair(),
|
||||||
|
(edn::Value::Boolean(false), ValueType::Boolean)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
TypedValue::Boolean(true).to_edn_value_pair(),
|
||||||
|
(edn::Value::Boolean(true), ValueType::Boolean)
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(TypedValue::Long(0).to_edn_value_pair(), (edn::Value::Integer(0), ValueType::Long));
|
assert_eq!(
|
||||||
assert_eq!(TypedValue::Long(1234).to_edn_value_pair(), (edn::Value::Integer(1234), ValueType::Long));
|
TypedValue::Long(0).to_edn_value_pair(),
|
||||||
|
(edn::Value::Integer(0), ValueType::Long)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
TypedValue::Long(1234).to_edn_value_pair(),
|
||||||
|
(edn::Value::Integer(1234), ValueType::Long)
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(TypedValue::Double(OrderedFloat(0.0)).to_edn_value_pair(), (edn::Value::Float(OrderedFloat(0.0)), ValueType::Double));
|
assert_eq!(
|
||||||
assert_eq!(TypedValue::Double(OrderedFloat(0.5)).to_edn_value_pair(), (edn::Value::Float(OrderedFloat(0.5)), ValueType::Double));
|
TypedValue::Double(OrderedFloat(0.0)).to_edn_value_pair(),
|
||||||
|
(edn::Value::Float(OrderedFloat(0.0)), ValueType::Double)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
TypedValue::Double(OrderedFloat(0.5)).to_edn_value_pair(),
|
||||||
|
(edn::Value::Float(OrderedFloat(0.5)), ValueType::Double)
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(TypedValue::typed_string(":db/keyword").to_edn_value_pair(), (edn::Value::Text(":db/keyword".into()), ValueType::String));
|
assert_eq!(
|
||||||
assert_eq!(TypedValue::typed_ns_keyword("db", "keyword").to_edn_value_pair(), (edn::Value::Keyword(symbols::Keyword::namespaced("db", "keyword")), ValueType::Keyword));
|
TypedValue::typed_string(":db/keyword").to_edn_value_pair(),
|
||||||
|
(edn::Value::Text(":db/keyword".into()), ValueType::String)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
TypedValue::typed_ns_keyword("db", "keyword").to_edn_value_pair(),
|
||||||
|
(
|
||||||
|
edn::Value::Keyword(symbols::Keyword::namespaced("db", "keyword")),
|
||||||
|
ValueType::Keyword
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ source "https://rubygems.org"
|
||||||
# gem "jekyll", "~> 3.7.3"
|
# gem "jekyll", "~> 3.7.3"
|
||||||
|
|
||||||
# This is the default theme for new Jekyll sites. You may change this to anything you like.
|
# This is the default theme for new Jekyll sites. You may change this to anything you like.
|
||||||
gem "minima", "~> 2.0"
|
gem "minima", "~> 2.5.1"
|
||||||
|
|
||||||
# If you want to use GitHub Pages, remove the "gem "jekyll"" above and
|
# If you want to use GitHub Pages, remove the "gem "jekyll"" above and
|
||||||
# uncomment the line below. To upgrade, run `bundle update github-pages`.
|
# uncomment the line below. To upgrade, run `bundle update github-pages`.
|
||||||
|
@ -19,9 +19,9 @@ gem "minima", "~> 2.0"
|
||||||
|
|
||||||
# If you have any plugins, put them here!
|
# If you have any plugins, put them here!
|
||||||
group :jekyll_plugins do
|
group :jekyll_plugins do
|
||||||
gem "jekyll-feed", "~> 0.9.3"
|
gem "jekyll-feed", "~> 0.15.1"
|
||||||
gem "github-pages", "~> 186"
|
gem "github-pages", "~> 215"
|
||||||
gem "jekyll-commonmark-ghpages", "~> 0.1.5"
|
gem "jekyll-commonmark-ghpages", "~> 0.1.6"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
||||||
|
|
|
@ -1,148 +1,161 @@
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
activesupport (4.2.10)
|
activesupport (6.0.4)
|
||||||
i18n (~> 0.7)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
|
i18n (>= 0.7, < 2)
|
||||||
minitest (~> 5.1)
|
minitest (~> 5.1)
|
||||||
thread_safe (~> 0.3, >= 0.3.4)
|
|
||||||
tzinfo (~> 1.1)
|
tzinfo (~> 1.1)
|
||||||
addressable (2.5.2)
|
zeitwerk (~> 2.2, >= 2.2.2)
|
||||||
public_suffix (>= 2.0.2, < 4.0)
|
addressable (2.8.0)
|
||||||
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
coffee-script (2.4.1)
|
coffee-script (2.4.1)
|
||||||
coffee-script-source
|
coffee-script-source
|
||||||
execjs
|
execjs
|
||||||
coffee-script-source (1.11.1)
|
coffee-script-source (1.11.1)
|
||||||
colorator (1.1.0)
|
colorator (1.1.0)
|
||||||
commonmarker (0.17.9)
|
commonmarker (0.17.13)
|
||||||
ruby-enum (~> 0.5)
|
ruby-enum (~> 0.5)
|
||||||
concurrent-ruby (1.0.5)
|
concurrent-ruby (1.1.9)
|
||||||
dnsruby (1.60.2)
|
dnsruby (1.61.7)
|
||||||
em-websocket (0.5.1)
|
simpleidn (~> 0.1)
|
||||||
|
em-websocket (0.5.2)
|
||||||
eventmachine (>= 0.12.9)
|
eventmachine (>= 0.12.9)
|
||||||
http_parser.rb (~> 0.6.0)
|
http_parser.rb (~> 0.6.0)
|
||||||
ethon (0.11.0)
|
ethon (0.14.0)
|
||||||
ffi (>= 1.3.0)
|
ffi (>= 1.15.0)
|
||||||
eventmachine (1.2.7)
|
eventmachine (1.2.7)
|
||||||
execjs (2.7.0)
|
execjs (2.8.1)
|
||||||
faraday (0.15.2)
|
faraday (1.4.3)
|
||||||
|
faraday-em_http (~> 1.0)
|
||||||
|
faraday-em_synchrony (~> 1.0)
|
||||||
|
faraday-excon (~> 1.1)
|
||||||
|
faraday-net_http (~> 1.0)
|
||||||
|
faraday-net_http_persistent (~> 1.1)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
ffi (1.9.25)
|
ruby2_keywords (>= 0.0.4)
|
||||||
|
faraday-em_http (1.0.0)
|
||||||
|
faraday-em_synchrony (1.0.0)
|
||||||
|
faraday-excon (1.1.0)
|
||||||
|
faraday-net_http (1.0.1)
|
||||||
|
faraday-net_http_persistent (1.1.0)
|
||||||
|
ffi (1.15.3)
|
||||||
forwardable-extended (2.6.0)
|
forwardable-extended (2.6.0)
|
||||||
gemoji (3.0.0)
|
gemoji (3.0.1)
|
||||||
github-pages (186)
|
github-pages (215)
|
||||||
activesupport (= 4.2.10)
|
github-pages-health-check (= 1.17.2)
|
||||||
github-pages-health-check (= 1.8.1)
|
jekyll (= 3.9.0)
|
||||||
jekyll (= 3.7.3)
|
jekyll-avatar (= 0.7.0)
|
||||||
jekyll-avatar (= 0.5.0)
|
|
||||||
jekyll-coffeescript (= 1.1.1)
|
jekyll-coffeescript (= 1.1.1)
|
||||||
jekyll-commonmark-ghpages (= 0.1.5)
|
jekyll-commonmark-ghpages (= 0.1.6)
|
||||||
jekyll-default-layout (= 0.1.4)
|
jekyll-default-layout (= 0.1.4)
|
||||||
jekyll-feed (= 0.9.3)
|
jekyll-feed (= 0.15.1)
|
||||||
jekyll-gist (= 1.5.0)
|
jekyll-gist (= 1.5.0)
|
||||||
jekyll-github-metadata (= 2.9.4)
|
jekyll-github-metadata (= 2.13.0)
|
||||||
jekyll-mentions (= 1.3.0)
|
jekyll-mentions (= 1.6.0)
|
||||||
jekyll-optional-front-matter (= 0.3.0)
|
jekyll-optional-front-matter (= 0.3.2)
|
||||||
jekyll-paginate (= 1.1.0)
|
jekyll-paginate (= 1.1.0)
|
||||||
jekyll-readme-index (= 0.2.0)
|
jekyll-readme-index (= 0.3.0)
|
||||||
jekyll-redirect-from (= 0.13.0)
|
jekyll-redirect-from (= 0.16.0)
|
||||||
jekyll-relative-links (= 0.5.3)
|
jekyll-relative-links (= 0.6.1)
|
||||||
jekyll-remote-theme (= 0.3.1)
|
jekyll-remote-theme (= 0.4.3)
|
||||||
jekyll-sass-converter (= 1.5.2)
|
jekyll-sass-converter (= 1.5.2)
|
||||||
jekyll-seo-tag (= 2.4.0)
|
jekyll-seo-tag (= 2.7.1)
|
||||||
jekyll-sitemap (= 1.2.0)
|
jekyll-sitemap (= 1.4.0)
|
||||||
jekyll-swiss (= 0.4.0)
|
jekyll-swiss (= 1.0.0)
|
||||||
jekyll-theme-architect (= 0.1.1)
|
jekyll-theme-architect (= 0.1.1)
|
||||||
jekyll-theme-cayman (= 0.1.1)
|
jekyll-theme-cayman (= 0.1.1)
|
||||||
jekyll-theme-dinky (= 0.1.1)
|
jekyll-theme-dinky (= 0.1.1)
|
||||||
jekyll-theme-hacker (= 0.1.1)
|
jekyll-theme-hacker (= 0.1.2)
|
||||||
jekyll-theme-leap-day (= 0.1.1)
|
jekyll-theme-leap-day (= 0.1.1)
|
||||||
jekyll-theme-merlot (= 0.1.1)
|
jekyll-theme-merlot (= 0.1.1)
|
||||||
jekyll-theme-midnight (= 0.1.1)
|
jekyll-theme-midnight (= 0.1.1)
|
||||||
jekyll-theme-minimal (= 0.1.1)
|
jekyll-theme-minimal (= 0.1.1)
|
||||||
jekyll-theme-modernist (= 0.1.1)
|
jekyll-theme-modernist (= 0.1.1)
|
||||||
jekyll-theme-primer (= 0.5.3)
|
jekyll-theme-primer (= 0.5.4)
|
||||||
jekyll-theme-slate (= 0.1.1)
|
jekyll-theme-slate (= 0.1.1)
|
||||||
jekyll-theme-tactile (= 0.1.1)
|
jekyll-theme-tactile (= 0.1.1)
|
||||||
jekyll-theme-time-machine (= 0.1.1)
|
jekyll-theme-time-machine (= 0.1.1)
|
||||||
jekyll-titles-from-headings (= 0.5.1)
|
jekyll-titles-from-headings (= 0.5.3)
|
||||||
jemoji (= 0.9.0)
|
jemoji (= 0.12.0)
|
||||||
kramdown (= 1.16.2)
|
kramdown (= 2.3.1)
|
||||||
liquid (= 4.0.0)
|
kramdown-parser-gfm (= 1.1.0)
|
||||||
listen (= 3.1.5)
|
liquid (= 4.0.3)
|
||||||
mercenary (~> 0.3)
|
mercenary (~> 0.3)
|
||||||
minima (= 2.4.1)
|
minima (= 2.5.1)
|
||||||
nokogiri (>= 1.8.2, < 2.0)
|
nokogiri (>= 1.10.4, < 2.0)
|
||||||
rouge (= 2.2.1)
|
rouge (= 3.26.0)
|
||||||
terminal-table (~> 1.4)
|
terminal-table (~> 1.4)
|
||||||
github-pages-health-check (1.8.1)
|
github-pages-health-check (1.17.2)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
dnsruby (~> 1.60)
|
dnsruby (~> 1.60)
|
||||||
octokit (~> 4.0)
|
octokit (~> 4.0)
|
||||||
public_suffix (~> 2.0)
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
typhoeus (~> 1.3)
|
typhoeus (~> 1.3)
|
||||||
html-pipeline (2.8.0)
|
html-pipeline (2.14.0)
|
||||||
activesupport (>= 2)
|
activesupport (>= 2)
|
||||||
nokogiri (>= 1.4)
|
nokogiri (>= 1.4)
|
||||||
http_parser.rb (0.6.0)
|
http_parser.rb (0.6.0)
|
||||||
i18n (0.9.5)
|
i18n (0.9.5)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
jekyll (3.7.3)
|
jekyll (3.9.0)
|
||||||
addressable (~> 2.4)
|
addressable (~> 2.4)
|
||||||
colorator (~> 1.0)
|
colorator (~> 1.0)
|
||||||
em-websocket (~> 0.5)
|
em-websocket (~> 0.5)
|
||||||
i18n (~> 0.7)
|
i18n (~> 0.7)
|
||||||
jekyll-sass-converter (~> 1.0)
|
jekyll-sass-converter (~> 1.0)
|
||||||
jekyll-watch (~> 2.0)
|
jekyll-watch (~> 2.0)
|
||||||
kramdown (~> 1.14)
|
kramdown (>= 1.17, < 3)
|
||||||
liquid (~> 4.0)
|
liquid (~> 4.0)
|
||||||
mercenary (~> 0.3.3)
|
mercenary (~> 0.3.3)
|
||||||
pathutil (~> 0.9)
|
pathutil (~> 0.9)
|
||||||
rouge (>= 1.7, < 4)
|
rouge (>= 1.7, < 4)
|
||||||
safe_yaml (~> 1.0)
|
safe_yaml (~> 1.0)
|
||||||
jekyll-avatar (0.5.0)
|
jekyll-avatar (0.7.0)
|
||||||
jekyll (~> 3.0)
|
jekyll (>= 3.0, < 5.0)
|
||||||
jekyll-coffeescript (1.1.1)
|
jekyll-coffeescript (1.1.1)
|
||||||
coffee-script (~> 2.2)
|
coffee-script (~> 2.2)
|
||||||
coffee-script-source (~> 1.11.1)
|
coffee-script-source (~> 1.11.1)
|
||||||
jekyll-commonmark (1.2.0)
|
jekyll-commonmark (1.3.1)
|
||||||
commonmarker (~> 0.14)
|
commonmarker (~> 0.14)
|
||||||
jekyll (>= 3.0, < 4.0)
|
jekyll (>= 3.7, < 5.0)
|
||||||
jekyll-commonmark-ghpages (0.1.5)
|
jekyll-commonmark-ghpages (0.1.6)
|
||||||
commonmarker (~> 0.17.6)
|
commonmarker (~> 0.17.6)
|
||||||
jekyll-commonmark (~> 1)
|
jekyll-commonmark (~> 1.2)
|
||||||
rouge (~> 2)
|
rouge (>= 2.0, < 4.0)
|
||||||
jekyll-default-layout (0.1.4)
|
jekyll-default-layout (0.1.4)
|
||||||
jekyll (~> 3.0)
|
jekyll (~> 3.0)
|
||||||
jekyll-feed (0.9.3)
|
jekyll-feed (0.15.1)
|
||||||
jekyll (~> 3.3)
|
jekyll (>= 3.7, < 5.0)
|
||||||
jekyll-gist (1.5.0)
|
jekyll-gist (1.5.0)
|
||||||
octokit (~> 4.2)
|
octokit (~> 4.2)
|
||||||
jekyll-github-metadata (2.9.4)
|
jekyll-github-metadata (2.13.0)
|
||||||
jekyll (~> 3.1)
|
jekyll (>= 3.4, < 5.0)
|
||||||
octokit (~> 4.0, != 4.4.0)
|
octokit (~> 4.0, != 4.4.0)
|
||||||
jekyll-mentions (1.3.0)
|
jekyll-mentions (1.6.0)
|
||||||
activesupport (~> 4.0)
|
|
||||||
html-pipeline (~> 2.3)
|
html-pipeline (~> 2.3)
|
||||||
jekyll (~> 3.0)
|
jekyll (>= 3.7, < 5.0)
|
||||||
jekyll-optional-front-matter (0.3.0)
|
jekyll-optional-front-matter (0.3.2)
|
||||||
jekyll (~> 3.0)
|
jekyll (>= 3.0, < 5.0)
|
||||||
jekyll-paginate (1.1.0)
|
jekyll-paginate (1.1.0)
|
||||||
jekyll-readme-index (0.2.0)
|
jekyll-readme-index (0.3.0)
|
||||||
jekyll (~> 3.0)
|
jekyll (>= 3.0, < 5.0)
|
||||||
jekyll-redirect-from (0.13.0)
|
jekyll-redirect-from (0.16.0)
|
||||||
jekyll (~> 3.3)
|
jekyll (>= 3.3, < 5.0)
|
||||||
jekyll-relative-links (0.5.3)
|
jekyll-relative-links (0.6.1)
|
||||||
jekyll (~> 3.3)
|
jekyll (>= 3.3, < 5.0)
|
||||||
jekyll-remote-theme (0.3.1)
|
jekyll-remote-theme (0.4.3)
|
||||||
jekyll (~> 3.5)
|
addressable (~> 2.0)
|
||||||
rubyzip (>= 1.2.1, < 3.0)
|
jekyll (>= 3.5, < 5.0)
|
||||||
|
jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0)
|
||||||
|
rubyzip (>= 1.3.0, < 3.0)
|
||||||
jekyll-sass-converter (1.5.2)
|
jekyll-sass-converter (1.5.2)
|
||||||
sass (~> 3.4)
|
sass (~> 3.4)
|
||||||
jekyll-seo-tag (2.4.0)
|
jekyll-seo-tag (2.7.1)
|
||||||
jekyll (~> 3.3)
|
jekyll (>= 3.8, < 5.0)
|
||||||
jekyll-sitemap (1.2.0)
|
jekyll-sitemap (1.4.0)
|
||||||
jekyll (~> 3.3)
|
jekyll (>= 3.7, < 5.0)
|
||||||
jekyll-swiss (0.4.0)
|
jekyll-swiss (1.0.0)
|
||||||
jekyll-theme-architect (0.1.1)
|
jekyll-theme-architect (0.1.1)
|
||||||
jekyll (~> 3.5)
|
jekyll (~> 3.5)
|
||||||
jekyll-seo-tag (~> 2.0)
|
jekyll-seo-tag (~> 2.0)
|
||||||
|
@ -152,8 +165,8 @@ GEM
|
||||||
jekyll-theme-dinky (0.1.1)
|
jekyll-theme-dinky (0.1.1)
|
||||||
jekyll (~> 3.5)
|
jekyll (~> 3.5)
|
||||||
jekyll-seo-tag (~> 2.0)
|
jekyll-seo-tag (~> 2.0)
|
||||||
jekyll-theme-hacker (0.1.1)
|
jekyll-theme-hacker (0.1.2)
|
||||||
jekyll (~> 3.5)
|
jekyll (> 3.5, < 5.0)
|
||||||
jekyll-seo-tag (~> 2.0)
|
jekyll-seo-tag (~> 2.0)
|
||||||
jekyll-theme-leap-day (0.1.1)
|
jekyll-theme-leap-day (0.1.1)
|
||||||
jekyll (~> 3.5)
|
jekyll (~> 3.5)
|
||||||
|
@ -170,8 +183,8 @@ GEM
|
||||||
jekyll-theme-modernist (0.1.1)
|
jekyll-theme-modernist (0.1.1)
|
||||||
jekyll (~> 3.5)
|
jekyll (~> 3.5)
|
||||||
jekyll-seo-tag (~> 2.0)
|
jekyll-seo-tag (~> 2.0)
|
||||||
jekyll-theme-primer (0.5.3)
|
jekyll-theme-primer (0.5.4)
|
||||||
jekyll (~> 3.5)
|
jekyll (> 3.5, < 5.0)
|
||||||
jekyll-github-metadata (~> 2.9)
|
jekyll-github-metadata (~> 2.9)
|
||||||
jekyll-seo-tag (~> 2.0)
|
jekyll-seo-tag (~> 2.0)
|
||||||
jekyll-theme-slate (0.1.1)
|
jekyll-theme-slate (0.1.1)
|
||||||
|
@ -183,71 +196,82 @@ GEM
|
||||||
jekyll-theme-time-machine (0.1.1)
|
jekyll-theme-time-machine (0.1.1)
|
||||||
jekyll (~> 3.5)
|
jekyll (~> 3.5)
|
||||||
jekyll-seo-tag (~> 2.0)
|
jekyll-seo-tag (~> 2.0)
|
||||||
jekyll-titles-from-headings (0.5.1)
|
jekyll-titles-from-headings (0.5.3)
|
||||||
jekyll (~> 3.3)
|
jekyll (>= 3.3, < 5.0)
|
||||||
jekyll-watch (2.0.0)
|
jekyll-watch (2.2.1)
|
||||||
listen (~> 3.0)
|
listen (~> 3.0)
|
||||||
jemoji (0.9.0)
|
jemoji (0.12.0)
|
||||||
activesupport (~> 4.0, >= 4.2.9)
|
|
||||||
gemoji (~> 3.0)
|
gemoji (~> 3.0)
|
||||||
html-pipeline (~> 2.2)
|
html-pipeline (~> 2.2)
|
||||||
jekyll (~> 3.0)
|
jekyll (>= 3.0, < 5.0)
|
||||||
kramdown (1.16.2)
|
kramdown (2.3.1)
|
||||||
liquid (4.0.0)
|
rexml
|
||||||
listen (3.1.5)
|
kramdown-parser-gfm (1.1.0)
|
||||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
kramdown (~> 2.0)
|
||||||
rb-inotify (~> 0.9, >= 0.9.7)
|
liquid (4.0.3)
|
||||||
ruby_dep (~> 1.2)
|
listen (3.5.1)
|
||||||
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||||
|
rb-inotify (~> 0.9, >= 0.9.10)
|
||||||
mercenary (0.3.6)
|
mercenary (0.3.6)
|
||||||
mini_portile2 (2.3.0)
|
mini_portile2 (2.6.1)
|
||||||
minima (2.4.1)
|
minima (2.5.1)
|
||||||
jekyll (~> 3.5)
|
jekyll (>= 3.5, < 5.0)
|
||||||
jekyll-feed (~> 0.9)
|
jekyll-feed (~> 0.9)
|
||||||
jekyll-seo-tag (~> 2.1)
|
jekyll-seo-tag (~> 2.1)
|
||||||
minitest (5.11.3)
|
minitest (5.14.4)
|
||||||
multipart-post (2.0.0)
|
multipart-post (2.1.1)
|
||||||
nokogiri (1.8.3)
|
nokogiri (1.12.5)
|
||||||
mini_portile2 (~> 2.3.0)
|
mini_portile2 (~> 2.6.1)
|
||||||
octokit (4.9.0)
|
racc (~> 1.4)
|
||||||
|
octokit (4.21.0)
|
||||||
|
faraday (>= 0.9)
|
||||||
sawyer (~> 0.8.0, >= 0.5.3)
|
sawyer (~> 0.8.0, >= 0.5.3)
|
||||||
pathutil (0.16.1)
|
pathutil (0.16.2)
|
||||||
forwardable-extended (~> 2.6)
|
forwardable-extended (~> 2.6)
|
||||||
public_suffix (2.0.5)
|
public_suffix (4.0.6)
|
||||||
rb-fsevent (0.10.3)
|
racc (1.5.2)
|
||||||
rb-inotify (0.9.10)
|
rb-fsevent (0.11.0)
|
||||||
ffi (>= 0.5.0, < 2)
|
rb-inotify (0.10.1)
|
||||||
rouge (2.2.1)
|
ffi (~> 1.0)
|
||||||
ruby-enum (0.7.2)
|
rexml (3.2.5)
|
||||||
|
rouge (3.26.0)
|
||||||
|
ruby-enum (0.9.0)
|
||||||
i18n
|
i18n
|
||||||
ruby_dep (1.5.0)
|
ruby2_keywords (0.0.4)
|
||||||
rubyzip (1.2.1)
|
rubyzip (2.3.0)
|
||||||
safe_yaml (1.0.4)
|
safe_yaml (1.0.5)
|
||||||
sass (3.5.6)
|
sass (3.7.4)
|
||||||
sass-listen (~> 4.0.0)
|
sass-listen (~> 4.0.0)
|
||||||
sass-listen (4.0.0)
|
sass-listen (4.0.0)
|
||||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||||
rb-inotify (~> 0.9, >= 0.9.7)
|
rb-inotify (~> 0.9, >= 0.9.7)
|
||||||
sawyer (0.8.1)
|
sawyer (0.8.2)
|
||||||
addressable (>= 2.3.5, < 2.6)
|
addressable (>= 2.3.5)
|
||||||
faraday (~> 0.8, < 1.0)
|
faraday (> 0.8, < 2.0)
|
||||||
|
simpleidn (0.2.1)
|
||||||
|
unf (~> 0.1.4)
|
||||||
terminal-table (1.8.0)
|
terminal-table (1.8.0)
|
||||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||||
thread_safe (0.3.6)
|
thread_safe (0.3.6)
|
||||||
typhoeus (1.3.0)
|
typhoeus (1.4.0)
|
||||||
ethon (>= 0.9.0)
|
ethon (>= 0.9.0)
|
||||||
tzinfo (1.2.5)
|
tzinfo (1.2.9)
|
||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.1)
|
||||||
unicode-display_width (1.4.0)
|
unf (0.1.4)
|
||||||
|
unf_ext
|
||||||
|
unf_ext (0.0.7.7)
|
||||||
|
unicode-display_width (1.7.0)
|
||||||
|
zeitwerk (2.4.2)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
ruby
|
ruby
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
github-pages (~> 186)
|
github-pages (~> 215)
|
||||||
jekyll-commonmark-ghpages (~> 0.1.5)
|
jekyll-commonmark-ghpages (~> 0.1.6)
|
||||||
jekyll-feed (~> 0.9.3)
|
jekyll-feed (~> 0.15.1)
|
||||||
minima (~> 2.0)
|
minima (~> 2.5.1)
|
||||||
tzinfo-data
|
tzinfo-data
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
1.16.2
|
2.2.21
|
||||||
|
|
177
docs/tutorial.md
Normal file
177
docs/tutorial.md
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
Mentat is a transactional, relational storage system built on top of SQLite. The abstractions it offers allow you to easily tackle some things that are tricky in other storage systems:
|
||||||
|
|
||||||
|
- Have multiple components share storage and collaborate.
|
||||||
|
- Evolve schema.
|
||||||
|
- Track change over time.
|
||||||
|
- Synchronize data correctly.
|
||||||
|
- Store data with rich, checked types.
|
||||||
|
|
||||||
|
Mentat offers a programmatic Rust API for managing stores, retrieving data, and _transacting_ new data. It offers a Datalog-based query engine, with queries expressed in EDN, a rich textual data format similar to JSON. And it offers an EDN data format for transacting new data.
|
||||||
|
|
||||||
|
This tutorial covers all of these APIs, along with defining vocabulary.
|
||||||
|
|
||||||
|
We'll begin by introducing some concepts, and then we'll walk through some examples.
|
||||||
|
|
||||||
|
|
||||||
|
## What does Mentat store?
|
||||||
|
|
||||||
|
Depending on your perspective, Mentat looks like a relational store, a graph store, or a tuple store.
|
||||||
|
|
||||||
|
Mentat stores relationships between _entities_ and other entities or _values_. An entity is related to other things by an _attribute_.
|
||||||
|
|
||||||
|
All entities have an _entity ID_ (abbreviated to _entid_).
|
||||||
|
|
||||||
|
Some entities additionally have an identifier called an _ident_, which is a keyword. That looks something like `:bookmark/title`.
|
||||||
|
|
||||||
|
A value is a primitive piece of data. Mentat supports the following:
|
||||||
|
|
||||||
|
* Strings
|
||||||
|
* Long integers
|
||||||
|
* Double-precision floating point numbers
|
||||||
|
* Millisecond-precision timestamps
|
||||||
|
* UUIDs
|
||||||
|
* Booleans
|
||||||
|
* Keywords (a special kind of string that we use for idents).
|
||||||
|
|
||||||
|
There are two special kinds of entities: _attributes_ and _transactions_.
|
||||||
|
|
||||||
|
Attributes are themselves entities with a particular set of properties that define their meaning. They have identifiers, so you can refer to them easily. They have a _value type_, which is the type of value Mentat expects to be on the right hand side of the relationship. And they have a _cardinality_ (whether one or many values can exist for a particular entity), whether values are _unique_, a documentation string, and some indexing options.
|
||||||
|
|
||||||
|
An attribute looks something like this:
|
||||||
|
|
||||||
|
```edn
|
||||||
|
{:db/ident :bookmark/title
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db/valueType :db.type/string
|
||||||
|
:db/fulltext true
|
||||||
|
:db/doc "The title of a bookmark."}
|
||||||
|
```
|
||||||
|
|
||||||
|
Transactions are special entities that can be described however you wish. By default they track the timestamp at which they were written.
|
||||||
|
|
||||||
|
The relationship between an entity, an attribute, and a value, occurring in a _transaction_ (which is just another kind of entity!) — a tuple of five values — is called a _datom_.
|
||||||
|
|
||||||
|
A single datom might look something like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
[:db/add 65536 :bookmark/title "mozilla.org" 268435456]
|
||||||
|
^ ^ ^ ^ ^
|
||||||
|
\ Add or retract. | | | |
|
||||||
|
\ The entity. | | |
|
||||||
|
\ The attribute. | |
|
||||||
|
\ The value, a string. |
|
||||||
|
\ The transaction ID.
|
||||||
|
```
|
||||||
|
|
||||||
|
which is equivalent to saying "in transaction 268435456 we assert that entity 65536 is a bookmark with the title 'mozilla.org'".
|
||||||
|
|
||||||
|
When we transact that — which means to add it as a fact to the store — Mentat also describes the transaction itself on our behalf:
|
||||||
|
|
||||||
|
```edn
|
||||||
|
[:db/add 268435456 :db/txInstant "2018-01-25 20:07:04.408358 UTC" 268435456]
|
||||||
|
```
|
||||||
|
|
||||||
|
# A simple app
|
||||||
|
|
||||||
|
Let's get started with some Rust code.
|
||||||
|
|
||||||
|
First, the imports we'll need. The comments here briefly explain what each thing is.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// So you can define keywords with neater syntax.
|
||||||
|
#[macro_use(kw)]
|
||||||
|
extern crate mentat;
|
||||||
|
|
||||||
|
use mentat::{
|
||||||
|
Store, // A single database connection and in-memory metadata.
|
||||||
|
}
|
||||||
|
|
||||||
|
use mentat::vocabulary::attribute; // Properties of attributes.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Defining a simple vocabulary
|
||||||
|
|
||||||
|
All data in Mentat — even the terms we used above, like `:db/cardinality` — are defined in the store itself. So that's where we start. In Rust, we define a _vocabulary definition_, and ask the store to ensure that it exists.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
|
||||||
|
fn set_up(mut store: Store) -> Result<()> {
|
||||||
|
// Start a write transaction.
|
||||||
|
let mut in_progress = store.begin_transaction()?;
|
||||||
|
|
||||||
|
// Make sure the core vocabulary exists. This is good practice if a user,
|
||||||
|
// an external tool, or another component might have touched the file
|
||||||
|
// since you last wrote it.
|
||||||
|
in_progress.verify_core_schema()?;
|
||||||
|
|
||||||
|
// Make sure our vocabulary is installed, and install if necessary.
|
||||||
|
// This defines some attributes that we can use to describe people.
|
||||||
|
in_progress.ensure_vocabulary(&Definition {
|
||||||
|
name: kw!(:example/people),
|
||||||
|
version: 1,
|
||||||
|
attributes: vec![
|
||||||
|
(kw!(:person/name),
|
||||||
|
vocabulary::AttributeBuilder::default()
|
||||||
|
.value_type(ValueType::String)
|
||||||
|
.multival(true)
|
||||||
|
.build()),
|
||||||
|
(kw!(:person/age),
|
||||||
|
vocabulary::AttributeBuilder::default()
|
||||||
|
.value_type(ValueType::Long)
|
||||||
|
.multival(false)
|
||||||
|
.build()),
|
||||||
|
(kw!(:person/email),
|
||||||
|
vocabulary::AttributeBuilder::default()
|
||||||
|
.value_type(ValueType::String)
|
||||||
|
.multival(true)
|
||||||
|
.unique(attribute::Unique::Identity)
|
||||||
|
.build()),
|
||||||
|
],
|
||||||
|
})?;
|
||||||
|
|
||||||
|
in_progress.commit()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
We open a store and configure its vocabulary like this:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let path = "/path/to/file.db";
|
||||||
|
let store = Store::open(path)?;
|
||||||
|
set_up(store)?;
|
||||||
|
```
|
||||||
|
|
||||||
|
If this code returns successfully, we're good to go.
|
||||||
|
|
||||||
|
## Transactions
|
||||||
|
|
||||||
|
You'll see in our `set_up` function that we begin and end a transaction, which we call `in_progress`. A read-only transaction is begun via `begin_read`. The resulting objects — `InProgress` and `InProgressRead` support various kinds of read and write operations. Transactions are automatically rolled back when dropped, so remember to call `commit`!
|
||||||
|
|
||||||
|
## Adding some data
|
||||||
|
|
||||||
|
There are two ways to add data to Mentat: programmatically or textually.
|
||||||
|
|
||||||
|
The textual form accepts EDN, a simple relative of JSON that supports richer types and more flexible syntax. You saw this in the introduction. Here's an example:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
in_progress.transact(r#"[
|
||||||
|
{:person/name "Alice"
|
||||||
|
:person/age 32
|
||||||
|
:person/email "alice@example.org"}
|
||||||
|
]"#)?;
|
||||||
|
```
|
||||||
|
|
||||||
|
You can implicitly _upsert_ data when you have a unique attribute to use:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Alice's age is now 33. Note that we don't need to find out an entid,
|
||||||
|
// nor explicitly INSERT OR REPLACE or UPDATE OR INSERT or similar.
|
||||||
|
in_progress.transact(r#"[
|
||||||
|
{:person/age 33
|
||||||
|
:person/email "alice@example.org"}
|
||||||
|
]"#)?;
|
||||||
|
```
|
||||||
|
|
|
@ -7,25 +7,24 @@ workspace = ".."
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
repository = "https://github.com/mozilla/mentat"
|
repository = "https://github.com/mozilla/mentat"
|
||||||
description = "EDN parser for Project Mentat"
|
description = "EDN parser for Project Mentat"
|
||||||
build = "build.rs"
|
|
||||||
readme = "./README.md"
|
readme = "./README.md"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4"
|
chrono = "~0.4"
|
||||||
itertools = "0.7"
|
itertools = "~0.10"
|
||||||
num = "0.1"
|
num = "~0.4"
|
||||||
ordered-float = "0.5"
|
ordered-float = "~2.8"
|
||||||
pretty = "0.2"
|
pretty = "~0.12"
|
||||||
uuid = { version = "0.5", features = ["v4", "serde"] }
|
uuid = { version = "~1", features = ["v4", "serde"] }
|
||||||
serde = { version = "1.0", optional = true }
|
serde = { version = "~1.0", optional = true }
|
||||||
serde_derive = { version = "1.0", optional = true }
|
serde_derive = { version = "~1.0", optional = true }
|
||||||
|
peg = "~0.8"
|
||||||
|
bytes = "1.0.1"
|
||||||
|
hex = "0.4.3"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde_test = "1.0"
|
serde_test = "~1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "~1.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
serde_support = ["serde", "serde_derive"]
|
serde_support = ["serde", "serde_derive"]
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
peg = "0.5"
|
|
||||||
|
|
|
@ -1,491 +0,0 @@
|
||||||
/* -*- comment-start: "//"; -*- */
|
|
||||||
/* vim: set filetype=rust.rustpeg */
|
|
||||||
|
|
||||||
// Copyright 2016 Mozilla
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
||||||
// this file except in compliance with the License. You may obtain a copy of the
|
|
||||||
// License at http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
// Unless required by applicable law or agreed to in writing, software distributed
|
|
||||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
||||||
// specific language governing permissions and limitations under the License.
|
|
||||||
|
|
||||||
use std::collections::{BTreeSet, BTreeMap, LinkedList};
|
|
||||||
use std::iter::FromIterator;
|
|
||||||
use std::f64::{NAN, INFINITY, NEG_INFINITY};
|
|
||||||
|
|
||||||
use chrono::{
|
|
||||||
DateTime,
|
|
||||||
TimeZone,
|
|
||||||
Utc
|
|
||||||
};
|
|
||||||
use num::BigInt;
|
|
||||||
use ordered_float::OrderedFloat;
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use entities::*;
|
|
||||||
use query;
|
|
||||||
use query::FromValue;
|
|
||||||
use symbols::*;
|
|
||||||
use types::{SpannedValue, Span, ValueAndSpan};
|
|
||||||
|
|
||||||
// Goal: Be able to parse https://github.com/edn-format/edn
|
|
||||||
// Also extensible to help parse http://docs.datomic.com/query.html
|
|
||||||
|
|
||||||
// Debugging hint: test using `cargo test --features peg/trace -- --nocapture`
|
|
||||||
// to trace where the parser is failing
|
|
||||||
|
|
||||||
// TODO: Support tagged elements
|
|
||||||
// TODO: Support discard
|
|
||||||
|
|
||||||
pub nil -> SpannedValue = "nil" { SpannedValue::Nil }
|
|
||||||
pub nan -> SpannedValue = "#f" whitespace+ "NaN" { SpannedValue::Float(OrderedFloat(NAN)) }
|
|
||||||
|
|
||||||
pub infinity -> SpannedValue = "#f" whitespace+ s:$(sign) "Infinity"
|
|
||||||
{ SpannedValue::Float(OrderedFloat(if s == "+" { INFINITY } else { NEG_INFINITY })) }
|
|
||||||
|
|
||||||
pub boolean -> SpannedValue
|
|
||||||
= "true" { SpannedValue::Boolean(true) }
|
|
||||||
/ "false" { SpannedValue::Boolean(false) }
|
|
||||||
|
|
||||||
digit = [0-9]
|
|
||||||
alphanumeric = [0-9a-zA-Z]
|
|
||||||
octaldigit = [0-7]
|
|
||||||
validbase = [3][0-6] / [12][0-9] / [2-9]
|
|
||||||
hex = [0-9a-fA-F]
|
|
||||||
sign = [+-]
|
|
||||||
|
|
||||||
pub raw_bigint -> BigInt = b:$( sign? digit+ ) "N"
|
|
||||||
{ b.parse::<BigInt>().unwrap() }
|
|
||||||
pub raw_octalinteger -> i64 = "0" i:$( octaldigit+ )
|
|
||||||
{ i64::from_str_radix(i, 8).unwrap() }
|
|
||||||
pub raw_hexinteger -> i64 = "0x" i:$( hex+ )
|
|
||||||
{ i64::from_str_radix(i, 16).unwrap() }
|
|
||||||
pub raw_basedinteger -> i64 = b:$( validbase ) "r" i:$( alphanumeric+ )
|
|
||||||
{ i64::from_str_radix(i, b.parse::<u32>().unwrap()).unwrap() }
|
|
||||||
pub raw_integer -> i64 = i:$( sign? digit+ ) !("." / ([eE]))
|
|
||||||
{ i.parse::<i64>().unwrap() }
|
|
||||||
pub raw_float -> OrderedFloat<f64> = f:$(sign? digit+ ("." digit+)? ([eE] sign? digit+)?)
|
|
||||||
{ OrderedFloat(f.parse::<f64>().unwrap()) }
|
|
||||||
|
|
||||||
pub bigint -> SpannedValue = v:raw_bigint { SpannedValue::BigInteger(v) }
|
|
||||||
pub octalinteger -> SpannedValue = v:raw_octalinteger { SpannedValue::Integer(v) }
|
|
||||||
pub hexinteger -> SpannedValue = v:raw_hexinteger { SpannedValue::Integer(v) }
|
|
||||||
pub basedinteger -> SpannedValue = v:raw_basedinteger { SpannedValue::Integer(v) }
|
|
||||||
pub integer -> SpannedValue = v:raw_integer { SpannedValue::Integer(v) }
|
|
||||||
pub float -> SpannedValue = v:raw_float { SpannedValue::Float(v) }
|
|
||||||
|
|
||||||
number -> SpannedValue = ( bigint / basedinteger / hexinteger / octalinteger / integer / float )
|
|
||||||
|
|
||||||
// TODO: standalone characters: \<char>, \newline, \return, \space and \tab.
|
|
||||||
|
|
||||||
string_special_char -> &'input str = "\\" $([\\"ntr])
|
|
||||||
string_normal_chars -> &'input str = $([^"\\]+)
|
|
||||||
|
|
||||||
// This is what we need to do in order to unescape. We can't just match the entire string slice:
|
|
||||||
// we get a Vec<&str> from rust-peg, where some of the parts might be unescaped special characters,
|
|
||||||
// and we join it together to form an output string.
|
|
||||||
// E.g., input = r#"\"foo\\\\bar\""#
|
|
||||||
// output = [quote, "foo", backslash, "bar", quote]
|
|
||||||
// result = r#""foo\\bar""#
|
|
||||||
// For the typical case, string_normal_chars will match multiple, leading to a single-element vec.
|
|
||||||
pub raw_text -> String = "\"" t:((string_special_char / string_normal_chars)*) "\""
|
|
||||||
{ t.join(&"").to_string() }
|
|
||||||
|
|
||||||
pub text -> SpannedValue
|
|
||||||
= v:raw_text { SpannedValue::Text(v) }
|
|
||||||
|
|
||||||
// RFC 3339 timestamps. #inst "1985-04-12T23:20:50.52Z"
|
|
||||||
// We accept an arbitrary depth of decimals.
|
|
||||||
// Note that we discard the timezone information -- all times are translated to UTC.
|
|
||||||
inst_string -> DateTime<Utc> =
|
|
||||||
"#inst" whitespace+ "\"" d:$( [0-9]*<4> "-" [0-2][0-9] "-" [0-3][0-9]
|
|
||||||
"T"
|
|
||||||
[0-2][0-9] ":" [0-5][0-9] ":" [0-6][0-9]
|
|
||||||
("." [0-9]+)?
|
|
||||||
("Z" / (("+" / "-") [0-2][0-9] ":" [0-5][0-9]))
|
|
||||||
)
|
|
||||||
"\"" {?
|
|
||||||
DateTime::parse_from_rfc3339(d)
|
|
||||||
.map(|t| t.with_timezone(&Utc))
|
|
||||||
.map_err(|_| "invalid datetime") // Oh, rustpeg.
|
|
||||||
}
|
|
||||||
|
|
||||||
inst_micros -> DateTime<Utc> =
|
|
||||||
"#instmicros" whitespace+ d:$( digit+ ) {
|
|
||||||
let micros = d.parse::<i64>().unwrap();
|
|
||||||
let seconds: i64 = micros / 1000000;
|
|
||||||
let nanos: u32 = ((micros % 1000000).abs() as u32) * 1000;
|
|
||||||
Utc.timestamp(seconds, nanos)
|
|
||||||
}
|
|
||||||
|
|
||||||
inst_millis -> DateTime<Utc> =
|
|
||||||
"#instmillis" whitespace+ d:$( digit+ ) {
|
|
||||||
let millis = d.parse::<i64>().unwrap();
|
|
||||||
let seconds: i64 = millis / 1000;
|
|
||||||
let nanos: u32 = ((millis % 1000).abs() as u32) * 1000000;
|
|
||||||
Utc.timestamp(seconds, nanos)
|
|
||||||
}
|
|
||||||
|
|
||||||
inst -> SpannedValue = t:(inst_millis / inst_micros / inst_string)
|
|
||||||
{ SpannedValue::Instant(t) }
|
|
||||||
|
|
||||||
uuid_string -> Uuid =
|
|
||||||
"\"" u:$( [a-f0-9]*<8> "-" [a-f0-9]*<4> "-" [a-f0-9]*<4> "-" [a-f0-9]*<4> "-" [a-f0-9]*<12> ) "\"" {
|
|
||||||
Uuid::parse_str(u).expect("this is a valid UUID string")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub uuid -> SpannedValue = "#uuid" whitespace+ u:uuid_string
|
|
||||||
{ SpannedValue::Uuid(u) }
|
|
||||||
|
|
||||||
namespace_divider = "."
|
|
||||||
namespace_separator = "/"
|
|
||||||
|
|
||||||
// TODO: Be more picky here
|
|
||||||
// Keywords follow the rules of symbols, except they can (and must) begin with :
|
|
||||||
// e.g. :fred or :my/fred. See https://github.com/edn-format/edn#keywords
|
|
||||||
symbol_char_initial = [a-zA-Z0-9*!_?$%&=<>]
|
|
||||||
symbol_char_subsequent = [a-zA-Z0-9*!_?$%&=<>-]
|
|
||||||
|
|
||||||
symbol_namespace = symbol_char_initial symbol_char_subsequent* (namespace_divider symbol_char_subsequent+)*
|
|
||||||
symbol_name = ( symbol_char_initial+ symbol_char_subsequent* )
|
|
||||||
plain_symbol_name = symbol_name / "..." / "."
|
|
||||||
|
|
||||||
keyword_prefix = ":"
|
|
||||||
|
|
||||||
pub symbol -> SpannedValue =
|
|
||||||
ns:( sns:$(symbol_namespace) namespace_separator { sns })?
|
|
||||||
n:$(plain_symbol_name)
|
|
||||||
{ SpannedValue::from_symbol(ns, n) }
|
|
||||||
/ #expected("symbol")
|
|
||||||
|
|
||||||
pub keyword -> SpannedValue =
|
|
||||||
keyword_prefix
|
|
||||||
ns:( sns:$(symbol_namespace) namespace_separator { sns })?
|
|
||||||
n:$(symbol_name)
|
|
||||||
{ SpannedValue::from_keyword(ns, n) }
|
|
||||||
/ #expected("keyword")
|
|
||||||
|
|
||||||
pub list -> SpannedValue = "(" __ v:(value)* __ ")"
|
|
||||||
{ SpannedValue::List(LinkedList::from_iter(v)) }
|
|
||||||
|
|
||||||
pub vector -> SpannedValue = "[" __ v:(value)* __ "]"
|
|
||||||
{ SpannedValue::Vector(v) }
|
|
||||||
|
|
||||||
pub set -> SpannedValue = "#{" __ v:(value)* __ "}"
|
|
||||||
{ SpannedValue::Set(BTreeSet::from_iter(v)) }
|
|
||||||
|
|
||||||
pair -> (ValueAndSpan, ValueAndSpan) =
|
|
||||||
k:(value) v:(value) {
|
|
||||||
(k, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub map -> SpannedValue = "{" __ v:(pair)* __ "}"
|
|
||||||
{ SpannedValue::Map(BTreeMap::from_iter(v)) }
|
|
||||||
|
|
||||||
// It's important that float comes before integer or the parser assumes that
|
|
||||||
// floats are integers and fails to parse
|
|
||||||
pub value -> ValueAndSpan =
|
|
||||||
__ start:#position v:(nil / nan / infinity / boolean / number / inst / uuid / text / keyword / symbol / list / vector / map / set) end:#position __ {
|
|
||||||
ValueAndSpan {
|
|
||||||
inner: v,
|
|
||||||
span: Span::new(start, end)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/ #expected("value")
|
|
||||||
|
|
||||||
atom -> ValueAndSpan
|
|
||||||
= v:value {? if v.is_atom() { Ok(v) } else { Err("expected atom") } }
|
|
||||||
|
|
||||||
// Clojure (and thus EDN) regards commas as whitespace, and thus the two-element vectors [1 2] and
|
|
||||||
// [1,,,,2] are equivalent, as are the maps {:a 1, :b 2} and {:a 1 :b 2}.
|
|
||||||
whitespace = #quiet<[ \r\n\t,]>
|
|
||||||
comment = #quiet<";" [^\r\n]* [\r\n]?>
|
|
||||||
|
|
||||||
__ = (whitespace / comment)*
|
|
||||||
|
|
||||||
// Transaction entity parser starts here.
|
|
||||||
|
|
||||||
pub op -> OpType
|
|
||||||
= ":db/add" { OpType::Add }
|
|
||||||
/ ":db/retract" { OpType::Retract }
|
|
||||||
|
|
||||||
raw_keyword -> Keyword =
|
|
||||||
keyword_prefix
|
|
||||||
ns:( sns:$(symbol_namespace) namespace_separator { sns })?
|
|
||||||
n:$(symbol_name) {
|
|
||||||
match ns {
|
|
||||||
Some(ns) => Keyword::namespaced(ns, n),
|
|
||||||
None => Keyword::plain(n),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/ #expected("keyword")
|
|
||||||
|
|
||||||
raw_forward_keyword -> Keyword
|
|
||||||
= v:raw_keyword {? if v.is_forward() { Ok(v) } else { Err("expected :forward or :forward/keyword") } }
|
|
||||||
|
|
||||||
raw_backward_keyword -> Keyword
|
|
||||||
= v:raw_keyword {? if v.is_backward() { Ok(v) } else { Err("expected :_backword or :backward/_keyword") } }
|
|
||||||
|
|
||||||
raw_namespaced_keyword -> Keyword
|
|
||||||
= keyword_prefix ns:$(symbol_namespace) namespace_separator n:$(symbol_name) { Keyword::namespaced(ns, n) }
|
|
||||||
/ #expected("namespaced keyword")
|
|
||||||
|
|
||||||
raw_forward_namespaced_keyword -> Keyword
|
|
||||||
= v:raw_namespaced_keyword {? if v.is_forward() { Ok(v) } else { Err("expected namespaced :forward/keyword") } }
|
|
||||||
|
|
||||||
raw_backward_namespaced_keyword -> Keyword
|
|
||||||
= v:raw_namespaced_keyword {? if v.is_backward() { Ok(v) } else { Err("expected namespaced :backward/_keyword") } }
|
|
||||||
|
|
||||||
entid -> EntidOrIdent
|
|
||||||
= v:( raw_basedinteger / raw_hexinteger / raw_octalinteger / raw_integer ) { EntidOrIdent::Entid(v) }
|
|
||||||
/ v:raw_namespaced_keyword { EntidOrIdent::Ident(v) }
|
|
||||||
/ #expected("entid")
|
|
||||||
|
|
||||||
forward_entid -> EntidOrIdent
|
|
||||||
= v:( raw_basedinteger / raw_hexinteger / raw_octalinteger / raw_integer ) { EntidOrIdent::Entid(v) }
|
|
||||||
/ v:raw_forward_namespaced_keyword { EntidOrIdent::Ident(v) }
|
|
||||||
/ #expected("forward entid")
|
|
||||||
|
|
||||||
backward_entid -> EntidOrIdent
|
|
||||||
= v:raw_backward_namespaced_keyword { EntidOrIdent::Ident(v.to_reversed()) }
|
|
||||||
/ #expected("backward entid")
|
|
||||||
|
|
||||||
lookup_ref -> LookupRef<ValueAndSpan>
|
|
||||||
= "(" __ "lookup-ref" __ a:(entid) __ v:(value) __ ")" { LookupRef { a: AttributePlace::Entid(a), v } }
|
|
||||||
/ #expected("lookup-ref")
|
|
||||||
|
|
||||||
tx_function -> TxFunction
|
|
||||||
= "(" __ n:$(symbol_name) __ ")" { TxFunction { op: PlainSymbol::plain(n) } }
|
|
||||||
|
|
||||||
entity_place -> EntityPlace<ValueAndSpan>
|
|
||||||
= v:raw_text { EntityPlace::TempId(TempId::External(v).into()) }
|
|
||||||
/ v:entid { EntityPlace::Entid(v) }
|
|
||||||
/ v:lookup_ref { EntityPlace::LookupRef(v) }
|
|
||||||
/ v:tx_function { EntityPlace::TxFunction(v) }
|
|
||||||
|
|
||||||
value_place_pair -> (EntidOrIdent, ValuePlace<ValueAndSpan>)
|
|
||||||
= k:(entid) __ v:(value_place) { (k, v) }
|
|
||||||
|
|
||||||
map_notation -> MapNotation<ValueAndSpan>
|
|
||||||
= "{" __ kvs:(value_place_pair*) __ "}" { kvs.into_iter().collect() }
|
|
||||||
|
|
||||||
value_place -> ValuePlace<ValueAndSpan>
|
|
||||||
= __ v:lookup_ref __ { ValuePlace::LookupRef(v) }
|
|
||||||
/ __ v:tx_function __ { ValuePlace::TxFunction(v) }
|
|
||||||
/ __ "[" __ vs:(value_place*) __ "]" __ { ValuePlace::Vector(vs) }
|
|
||||||
/ __ v:map_notation __ { ValuePlace::MapNotation(v) }
|
|
||||||
/ __ v:atom __ { ValuePlace::Atom(v) }
|
|
||||||
|
|
||||||
pub entity -> Entity<ValueAndSpan>
|
|
||||||
= __ "[" __ op:(op) __ e:(entity_place) __ a:(forward_entid) __ v:(value_place) __ "]" __ { Entity::AddOrRetract { op, e: e, a: AttributePlace::Entid(a), v: v } }
|
|
||||||
/ __ "[" __ op:(op) __ e:(value_place) __ a:(backward_entid) __ v:(entity_place) __ "]" __ { Entity::AddOrRetract { op, e: v, a: AttributePlace::Entid(a), v: e } }
|
|
||||||
/ __ map:map_notation __ { Entity::MapNotation(map) }
|
|
||||||
/ #expected("entity")
|
|
||||||
|
|
||||||
pub entities -> Vec<Entity<ValueAndSpan>>
|
|
||||||
= __ "[" __ es:(entity*) __ "]" __ { es }
|
|
||||||
|
|
||||||
// Query parser starts here.
|
|
||||||
//
|
|
||||||
// We expect every rule except the `raw_*` rules to eat whitespace
|
|
||||||
// (with `__`) at its start and finish. That means that every string
|
|
||||||
// pattern (say "[") should be bracketed on either side with either a
|
|
||||||
// whitespace-eating rule or an explicit whitespace eating `__`.
|
|
||||||
|
|
||||||
query_function -> query::QueryFunction
|
|
||||||
= __ n:$(symbol_name) __ {? query::QueryFunction::from_symbol(&PlainSymbol::plain(n)).ok_or("expected query function") }
|
|
||||||
|
|
||||||
fn_arg -> query::FnArg
|
|
||||||
= v:value {? query::FnArg::from_value(&v).ok_or("expected query function argument") }
|
|
||||||
/ __ "[" args:fn_arg+ "]" __ { query::FnArg::Vector(args) }
|
|
||||||
|
|
||||||
find_elem -> query::Element
|
|
||||||
= __ v:variable __ { query::Element::Variable(v) }
|
|
||||||
/ __ "(" __ "the" v:variable ")" __ { query::Element::Corresponding(v) }
|
|
||||||
/ __ "(" __ "pull" var:variable "[" patterns:pull_attribute+ "]" __ ")" __ { query::Element::Pull(query::Pull { var, patterns }) }
|
|
||||||
/ __ "(" func:query_function args:fn_arg* ")" __ { query::Element::Aggregate(query::Aggregate { func, args }) }
|
|
||||||
|
|
||||||
find_spec -> query::FindSpec
|
|
||||||
= f:find_elem "." __ { query::FindSpec::FindScalar(f) }
|
|
||||||
/ fs:find_elem+ { query::FindSpec::FindRel(fs) }
|
|
||||||
/ __ "[" f:find_elem __ "..." __ "]" __ { query::FindSpec::FindColl(f) }
|
|
||||||
/ __ "[" fs:find_elem+ "]" __ { query::FindSpec::FindTuple(fs) }
|
|
||||||
|
|
||||||
pull_attribute -> query::PullAttributeSpec
|
|
||||||
= __ "*" __ { query::PullAttributeSpec::Wildcard }
|
|
||||||
/ __ k:raw_forward_namespaced_keyword __ alias:(":as" __ alias:raw_forward_keyword __ { alias })? {
|
|
||||||
let attribute = query::PullConcreteAttribute::Ident(::std::rc::Rc::new(k));
|
|
||||||
let alias = alias.map(|alias| ::std::rc::Rc::new(alias));
|
|
||||||
query::PullAttributeSpec::Attribute(
|
|
||||||
query::NamedPullAttribute {
|
|
||||||
attribute,
|
|
||||||
alias: alias,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
limit -> query::Limit
|
|
||||||
= __ v:variable __ { query::Limit::Variable(v) }
|
|
||||||
/ __ n:(raw_octalinteger / raw_hexinteger / raw_basedinteger / raw_integer) __ {?
|
|
||||||
if n > 0 {
|
|
||||||
Ok(query::Limit::Fixed(n as u64))
|
|
||||||
} else {
|
|
||||||
Err("expected positive integer")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
order -> query::Order
|
|
||||||
= __ "(" __ "asc" v:variable ")" __ { query::Order(query::Direction::Ascending, v) }
|
|
||||||
/ __ "(" __ "desc" v:variable ")" __ { query::Order(query::Direction::Descending, v) }
|
|
||||||
/ v:variable { query::Order(query::Direction::Ascending, v) }
|
|
||||||
|
|
||||||
|
|
||||||
pattern_value_place -> query::PatternValuePlace
|
|
||||||
= v:value {? query::PatternValuePlace::from_value(&v).ok_or("expected pattern_value_place") }
|
|
||||||
|
|
||||||
pattern_non_value_place -> query::PatternNonValuePlace
|
|
||||||
= v:value {? query::PatternNonValuePlace::from_value(&v).ok_or("expected pattern_non_value_place") }
|
|
||||||
|
|
||||||
pattern -> query::WhereClause
|
|
||||||
= __ "["
|
|
||||||
src:src_var?
|
|
||||||
e:pattern_non_value_place
|
|
||||||
a:pattern_non_value_place
|
|
||||||
v:pattern_value_place?
|
|
||||||
tx:pattern_non_value_place?
|
|
||||||
"]" __
|
|
||||||
{?
|
|
||||||
let v = v.unwrap_or(query::PatternValuePlace::Placeholder);
|
|
||||||
let tx = tx.unwrap_or(query::PatternNonValuePlace::Placeholder);
|
|
||||||
|
|
||||||
// Pattern::new takes care of reversal of reversed
|
|
||||||
// attributes: [?x :foo/_bar ?y] turns into
|
|
||||||
// [?y :foo/bar ?x].
|
|
||||||
//
|
|
||||||
// This is a bit messy: the inner conversion to a Pattern can
|
|
||||||
// fail if the input is something like
|
|
||||||
//
|
|
||||||
// ```edn
|
|
||||||
// [?x :foo/_reversed 23.4]
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
// because
|
|
||||||
//
|
|
||||||
// ```edn
|
|
||||||
// [23.4 :foo/reversed ?x]
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
// is nonsense. That leaves us with a nested optional, which we unwrap here.
|
|
||||||
query::Pattern::new(src, e, a, v, tx)
|
|
||||||
.map(query::WhereClause::Pattern)
|
|
||||||
.ok_or("expected pattern")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: this shouldn't be checked at parse time.
|
|
||||||
rule_vars -> BTreeSet<query::Variable>
|
|
||||||
= vs:variable+ {?
|
|
||||||
let given = vs.len();
|
|
||||||
let set: BTreeSet<query::Variable> = vs.into_iter().collect();
|
|
||||||
if given != set.len() {
|
|
||||||
Err("expected unique variables")
|
|
||||||
} else {
|
|
||||||
Ok(set)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
or_pattern_clause -> query::OrWhereClause
|
|
||||||
= clause:where_clause { query::OrWhereClause::Clause(clause) }
|
|
||||||
|
|
||||||
or_and_clause -> query::OrWhereClause
|
|
||||||
= __ "(" __ "and" clauses:where_clause+ ")" __ { query::OrWhereClause::And(clauses) }
|
|
||||||
|
|
||||||
or_where_clause -> query::OrWhereClause
|
|
||||||
= or_pattern_clause
|
|
||||||
/ or_and_clause
|
|
||||||
|
|
||||||
or_clause -> query::WhereClause
|
|
||||||
= __ "(" __ "or" clauses:or_where_clause+ ")" __ {
|
|
||||||
query::WhereClause::OrJoin(query::OrJoin::new(query::UnifyVars::Implicit, clauses))
|
|
||||||
}
|
|
||||||
|
|
||||||
or_join_clause -> query::WhereClause
|
|
||||||
= __ "(" __ "or-join" __ "[" vars:rule_vars "]" clauses:or_where_clause+ ")" __ {
|
|
||||||
query::WhereClause::OrJoin(query::OrJoin::new(query::UnifyVars::Explicit(vars), clauses))
|
|
||||||
}
|
|
||||||
|
|
||||||
not_clause -> query::WhereClause
|
|
||||||
= __ "(" __ "not" clauses:where_clause+ ")" __ {
|
|
||||||
query::WhereClause::NotJoin(query::NotJoin::new(query::UnifyVars::Implicit, clauses))
|
|
||||||
}
|
|
||||||
|
|
||||||
not_join_clause -> query::WhereClause
|
|
||||||
= __ "(" __ "not-join" __ "[" vars:rule_vars "]" clauses:where_clause+ ")" __ {
|
|
||||||
query::WhereClause::NotJoin(query::NotJoin::new(query::UnifyVars::Explicit(vars), clauses))
|
|
||||||
}
|
|
||||||
|
|
||||||
type_annotation -> query::WhereClause
|
|
||||||
= __ "[" __ "(" __ "type" var:variable __ ty:raw_keyword __ ")" __ "]" __ {
|
|
||||||
query::WhereClause::TypeAnnotation(
|
|
||||||
query::TypeAnnotation {
|
|
||||||
value_type: ty,
|
|
||||||
variable: var,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pred -> query::WhereClause
|
|
||||||
= __ "[" __ "(" func:query_function args:fn_arg* ")" __ "]" __ {
|
|
||||||
query::WhereClause::Pred(
|
|
||||||
query::Predicate {
|
|
||||||
operator: func.0,
|
|
||||||
args: args,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub where_fn -> query::WhereClause
|
|
||||||
= __ "[" __ "(" func:query_function args:fn_arg* ")" __ binding:binding "]" __ {
|
|
||||||
query::WhereClause::WhereFn(
|
|
||||||
query::WhereFn {
|
|
||||||
operator: func.0,
|
|
||||||
args: args,
|
|
||||||
binding,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
where_clause -> query::WhereClause
|
|
||||||
// Right now we only support patterns and predicates. See #239 for more.
|
|
||||||
= pattern
|
|
||||||
/ or_join_clause
|
|
||||||
/ or_clause
|
|
||||||
/ not_join_clause
|
|
||||||
/ not_clause
|
|
||||||
/ type_annotation
|
|
||||||
/ pred
|
|
||||||
/ where_fn
|
|
||||||
|
|
||||||
query_part -> query::QueryPart
|
|
||||||
= __ ":find" fs:find_spec { query::QueryPart::FindSpec(fs) }
|
|
||||||
/ __ ":in" in_vars:variable+ { query::QueryPart::InVars(in_vars) }
|
|
||||||
/ __ ":limit" l:limit { query::QueryPart::Limit(l) }
|
|
||||||
/ __ ":order" os:order+ { query::QueryPart::Order(os) }
|
|
||||||
/ __ ":where" ws:where_clause+ { query::QueryPart::WhereClauses(ws) }
|
|
||||||
/ __ ":with" with_vars:variable+ { query::QueryPart::WithVars(with_vars) }
|
|
||||||
|
|
||||||
pub parse_query -> query::ParsedQuery
|
|
||||||
= __ "[" qps:query_part+ "]" __ {? query::ParsedQuery::from_parts(qps) }
|
|
||||||
|
|
||||||
variable -> query::Variable
|
|
||||||
= v:value {? query::Variable::from_value(&v).ok_or("expected variable") }
|
|
||||||
|
|
||||||
src_var -> query::SrcVar
|
|
||||||
= v:value {? query::SrcVar::from_value(&v).ok_or("expected src_var") }
|
|
||||||
|
|
||||||
variable_or_placeholder -> query::VariableOrPlaceholder
|
|
||||||
= v:variable { query::VariableOrPlaceholder::Variable(v) }
|
|
||||||
/ __ "_" __ { query::VariableOrPlaceholder::Placeholder }
|
|
||||||
|
|
||||||
binding -> query::Binding
|
|
||||||
= __ "[" __ "[" vs:variable_or_placeholder+ "]" __ "]" __ { query::Binding::BindRel(vs) }
|
|
||||||
/ __ "[" v:variable "..." __ "]" __ { query::Binding::BindColl(v) }
|
|
||||||
/ __ "[" vs:variable_or_placeholder+ "]" __ { query::Binding::BindTuple(vs) }
|
|
||||||
/ v:variable { query::Binding::BindScalar(v) }
|
|
|
@ -13,18 +13,11 @@
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use value_rc::{
|
use crate::value_rc::ValueRc;
|
||||||
ValueRc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use symbols::{
|
use crate::symbols::{Keyword, PlainSymbol};
|
||||||
Keyword,
|
|
||||||
PlainSymbol,
|
|
||||||
};
|
|
||||||
|
|
||||||
use types::{
|
use crate::types::ValueAndSpan;
|
||||||
ValueAndSpan,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// `EntityPlace` and `ValuePlace` embed values, either directly (i.e., `ValuePlace::Atom`) or
|
/// `EntityPlace` and `ValuePlace` embed values, either directly (i.e., `ValuePlace::Atom`) or
|
||||||
/// indirectly (i.e., `EntityPlace::LookupRef`). In order to maintain the graph of `Into` and
|
/// indirectly (i.e., `EntityPlace::LookupRef`). In order to maintain the graph of `Into` and
|
||||||
|
@ -56,8 +49,8 @@ impl TempId {
|
||||||
impl fmt::Display for TempId {
|
impl fmt::Display for TempId {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
match self {
|
match self {
|
||||||
&TempId::External(ref s) => write!(f, "{}", s),
|
TempId::External(ref s) => write!(f, "{}", s),
|
||||||
&TempId::Internal(x) => write!(f, "<tempid {}>", x),
|
TempId::Internal(x) => write!(f, "<tempid {}>", x),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,8 +76,8 @@ impl From<Keyword> for EntidOrIdent {
|
||||||
impl EntidOrIdent {
|
impl EntidOrIdent {
|
||||||
pub fn unreversed(&self) -> Option<EntidOrIdent> {
|
pub fn unreversed(&self) -> Option<EntidOrIdent> {
|
||||||
match self {
|
match self {
|
||||||
&EntidOrIdent::Entid(_) => None,
|
EntidOrIdent::Entid(_) => None,
|
||||||
&EntidOrIdent::Ident(ref a) => a.unreversed().map(EntidOrIdent::Ident),
|
EntidOrIdent::Ident(ref a) => a.unreversed().map(EntidOrIdent::Ident),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,14 +12,9 @@
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::ops::{
|
use std::ops::{Deref, DerefMut};
|
||||||
Deref,
|
|
||||||
DerefMut,
|
|
||||||
};
|
|
||||||
|
|
||||||
use ::{
|
use crate::ValueRc;
|
||||||
ValueRc,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// An `InternSet` allows to "intern" some potentially large values, maintaining a single value
|
/// An `InternSet` allows to "intern" some potentially large values, maintaining a single value
|
||||||
/// instance owned by the `InternSet` and leaving consumers with lightweight ref-counted handles to
|
/// instance owned by the `InternSet` and leaving consumers with lightweight ref-counted handles to
|
||||||
|
@ -29,11 +24,17 @@ use ::{
|
||||||
///
|
///
|
||||||
/// See https://en.wikipedia.org/wiki/String_interning for discussion.
|
/// See https://en.wikipedia.org/wiki/String_interning for discussion.
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||||
pub struct InternSet<T> where T: Eq + Hash {
|
pub struct InternSet<T>
|
||||||
|
where
|
||||||
|
T: Eq + Hash,
|
||||||
|
{
|
||||||
inner: HashSet<ValueRc<T>>,
|
inner: HashSet<ValueRc<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Deref for InternSet<T> where T: Eq + Hash {
|
impl<T> Deref for InternSet<T>
|
||||||
|
where
|
||||||
|
T: Eq + Hash,
|
||||||
|
{
|
||||||
type Target = HashSet<ValueRc<T>>;
|
type Target = HashSet<ValueRc<T>>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
|
@ -41,13 +42,19 @@ impl<T> Deref for InternSet<T> where T: Eq + Hash {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> DerefMut for InternSet<T> where T: Eq + Hash {
|
impl<T> DerefMut for InternSet<T>
|
||||||
|
where
|
||||||
|
T: Eq + Hash,
|
||||||
|
{
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
&mut self.inner
|
&mut self.inner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> InternSet<T> where T: Eq + Hash {
|
impl<T> InternSet<T>
|
||||||
|
where
|
||||||
|
T: Eq + Hash,
|
||||||
|
{
|
||||||
pub fn new() -> InternSet<T> {
|
pub fn new() -> InternSet<T> {
|
||||||
InternSet {
|
InternSet {
|
||||||
inner: HashSet::new(),
|
inner: HashSet::new(),
|
||||||
|
|
524
edn/src/lib.rs
524
edn/src/lib.rs
|
@ -8,10 +8,13 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
|
extern crate bytes;
|
||||||
extern crate chrono;
|
extern crate chrono;
|
||||||
|
extern crate hex;
|
||||||
extern crate itertools;
|
extern crate itertools;
|
||||||
extern crate num;
|
extern crate num;
|
||||||
extern crate ordered_float;
|
extern crate ordered_float;
|
||||||
|
extern crate peg;
|
||||||
extern crate pretty;
|
extern crate pretty;
|
||||||
extern crate uuid;
|
extern crate uuid;
|
||||||
|
|
||||||
|
@ -24,50 +27,511 @@ extern crate serde_derive;
|
||||||
|
|
||||||
pub mod entities;
|
pub mod entities;
|
||||||
pub mod intern_set;
|
pub mod intern_set;
|
||||||
pub use intern_set::{
|
pub use crate::intern_set::InternSet;
|
||||||
InternSet,
|
|
||||||
};
|
|
||||||
// Intentionally not pub.
|
// Intentionally not pub.
|
||||||
|
pub mod matcher;
|
||||||
mod namespaceable_name;
|
mod namespaceable_name;
|
||||||
|
pub mod pretty_print;
|
||||||
pub mod query;
|
pub mod query;
|
||||||
pub mod symbols;
|
pub mod symbols;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
pub mod pretty_print;
|
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod matcher;
|
|
||||||
pub mod value_rc;
|
pub mod value_rc;
|
||||||
pub use value_rc::{
|
pub use crate::value_rc::{Cloned, FromRc, ValueRc};
|
||||||
Cloned,
|
|
||||||
FromRc,
|
|
||||||
ValueRc,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod parse {
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/edn.rs"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-export the types we use.
|
// Re-export the types we use.
|
||||||
|
use bytes::Bytes;
|
||||||
pub use chrono::{DateTime, Utc};
|
pub use chrono::{DateTime, Utc};
|
||||||
|
use hex::decode;
|
||||||
pub use num::BigInt;
|
pub use num::BigInt;
|
||||||
pub use ordered_float::OrderedFloat;
|
pub use ordered_float::OrderedFloat;
|
||||||
pub use uuid::Uuid;
|
pub use uuid::Uuid;
|
||||||
|
|
||||||
// Export from our modules.
|
// Export from our modules.
|
||||||
pub use parse::ParseError;
|
pub use crate::types::{
|
||||||
pub use uuid::ParseError as UuidParseError;
|
FromMicros, FromMillis, Span, SpannedValue, ToMicros, ToMillis, Value, ValueAndSpan,
|
||||||
pub use types::{
|
|
||||||
FromMicros,
|
|
||||||
FromMillis,
|
|
||||||
Span,
|
|
||||||
SpannedValue,
|
|
||||||
ToMicros,
|
|
||||||
ToMillis,
|
|
||||||
Value,
|
|
||||||
ValueAndSpan,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use symbols::{
|
pub use crate::symbols::{Keyword, NamespacedSymbol, PlainSymbol};
|
||||||
Keyword,
|
|
||||||
NamespacedSymbol,
|
use std::collections::{BTreeMap, BTreeSet, LinkedList};
|
||||||
PlainSymbol,
|
use std::f64::{INFINITY, NAN, NEG_INFINITY};
|
||||||
};
|
use std::iter::FromIterator;
|
||||||
|
|
||||||
|
use chrono::TimeZone;
|
||||||
|
|
||||||
|
use crate::entities::*;
|
||||||
|
use crate::query::FromValue;
|
||||||
|
|
||||||
|
// Goal: Be able to parse https://github.com/edn-format/edn
|
||||||
|
// Also extensible to help parse http://docs.datomic.com/query.html
|
||||||
|
|
||||||
|
// Debugging hint: test using `cargo test --features peg/trace -- --nocapture`
|
||||||
|
// to trace where the parser is failing
|
||||||
|
|
||||||
|
// TODO: Support tagged elements
|
||||||
|
// TODO: Support discard
|
||||||
|
|
||||||
|
pub type ParseError = peg::error::ParseError<peg::str::LineCol>;
|
||||||
|
|
||||||
|
peg::parser!(pub grammar parse() for str {
|
||||||
|
|
||||||
|
pub rule nil() -> SpannedValue = "nil" { SpannedValue::Nil }
|
||||||
|
pub rule nan() -> SpannedValue = "#f" whitespace()+ "NaN" { SpannedValue::Float(OrderedFloat(NAN)) }
|
||||||
|
|
||||||
|
pub rule infinity() -> SpannedValue = "#f" whitespace()+ s:$(sign()) "Infinity"
|
||||||
|
{ SpannedValue::Float(OrderedFloat(if s == "+" { INFINITY } else { NEG_INFINITY })) }
|
||||||
|
|
||||||
|
pub rule boolean() -> SpannedValue
|
||||||
|
= "true" { SpannedValue::Boolean(true) }
|
||||||
|
/ "false" { SpannedValue::Boolean(false) }
|
||||||
|
|
||||||
|
rule digit() = ['0'..='9']
|
||||||
|
rule alphanumeric() = ['0'..='9' | 'a'..='z' | 'A'..='Z']
|
||||||
|
rule octaldigit() = ['0'..='7']
|
||||||
|
rule validbase() = ['3']['0'..='6'] / ['1' | '2']['0'..='9'] / ['2'..='9']
|
||||||
|
rule hex() = ['0'..='9' | 'a'..='f' | 'A'..='F']
|
||||||
|
rule sign() = ['+' | '-']
|
||||||
|
|
||||||
|
pub rule raw_bigint() -> BigInt = b:$( sign()? digit()+ ) "N"
|
||||||
|
{ b.parse::<BigInt>().unwrap() }
|
||||||
|
pub rule raw_octalinteger() -> i64 = "0" i:$( octaldigit()+ )
|
||||||
|
{ i64::from_str_radix(i, 8).unwrap() }
|
||||||
|
pub rule raw_hexinteger() -> i64 = "0x" i:$( hex()+ )
|
||||||
|
{ i64::from_str_radix(i, 16).unwrap() }
|
||||||
|
pub rule raw_basedinteger() -> i64 = b:$( validbase() ) "r" i:$( alphanumeric()+ )
|
||||||
|
{ i64::from_str_radix(i, b.parse::<u32>().unwrap()).unwrap() }
|
||||||
|
pub rule raw_integer() -> i64 = i:$( sign()? digit()+ ) !("." / (['e' | 'E']))
|
||||||
|
{ i.parse::<i64>().unwrap() }
|
||||||
|
pub rule raw_float() -> OrderedFloat<f64> = f:$(sign()? digit()+ ("." digit()+)? (['e' | 'E'] sign()? digit()+)?)
|
||||||
|
{ OrderedFloat(f.parse::<f64>().unwrap()) }
|
||||||
|
|
||||||
|
pub rule bigint() -> SpannedValue = v:raw_bigint() { SpannedValue::BigInteger(v) }
|
||||||
|
pub rule octalinteger() -> SpannedValue = v:raw_octalinteger() { SpannedValue::Integer(v) }
|
||||||
|
pub rule hexinteger() -> SpannedValue = v:raw_hexinteger() { SpannedValue::Integer(v) }
|
||||||
|
pub rule basedinteger() -> SpannedValue = v:raw_basedinteger() { SpannedValue::Integer(v) }
|
||||||
|
pub rule integer() -> SpannedValue = v:raw_integer() { SpannedValue::Integer(v) }
|
||||||
|
pub rule float() -> SpannedValue = v:raw_float() { SpannedValue::Float(v) }
|
||||||
|
|
||||||
|
rule number() -> SpannedValue = ( bigint() / basedinteger() / hexinteger() / octalinteger() / integer() / float() )
|
||||||
|
|
||||||
|
// TODO: standalone characters: \<char>, \newline, \return, \space and \tab.
|
||||||
|
// rule string_standalone_chars() ->
|
||||||
|
rule string_special_char() -> &'input str = "\\" c:$(['\\' | '"' | 'n' | 't' | 'r']) { c }
|
||||||
|
rule string_normal_chars() -> &'input str = c:$((!['\"' | '\\'][_])+) { c }
|
||||||
|
|
||||||
|
// This is what we need to do in order to unescape. We can't just match the entire string slice:
|
||||||
|
// we get a Vec<&str> from rust-peg, where some parts might be unescaped special characters and
|
||||||
|
// we join it together to form an output string.
|
||||||
|
// E.g., input = r#"\"foo\\\\bar\""#
|
||||||
|
// output = [quote, "foo", backslash, "bar", quote]
|
||||||
|
// result = r#""foo\\bar""#
|
||||||
|
// For the typical case, string_normal_chars will match multiple, leading to a single-element vec.
|
||||||
|
pub rule raw_text() -> String = "\"" t:((string_special_char() / string_normal_chars())*) "\""
|
||||||
|
{ t.join("") }
|
||||||
|
|
||||||
|
pub rule text() -> SpannedValue
|
||||||
|
= v:raw_text() { SpannedValue::Text(v) }
|
||||||
|
|
||||||
|
// RFC 3339 timestamps. #inst "1985-04-12T23:20:50.52Z"
|
||||||
|
// We accept an arbitrary depth of decimals.
|
||||||
|
// TODO: Note that we discard the timezone information -- all times are translated to UTC. Should we?
|
||||||
|
rule inst_string() -> DateTime<Utc> =
|
||||||
|
"#inst" whitespace()+ "\"" d:$( ['0'..='9']*<4> "-" ['0'..='2']['0'..='9'] "-" ['0'..='3']['0'..='9']
|
||||||
|
"T"
|
||||||
|
['0'..='2']['0'..='9'] ":" ['0'..='5']['0'..='9'] ":" ['0'..='6']['0'..='9']
|
||||||
|
("." ['0'..='9']+)?
|
||||||
|
("Z" / (("+" / "-") ['0'..='2']['0'..='9'] ":" ['0'..='5']['0'..='9']))
|
||||||
|
)
|
||||||
|
"\"" {?
|
||||||
|
DateTime::parse_from_rfc3339(d)
|
||||||
|
.map(|t| t.with_timezone(&Utc))
|
||||||
|
.map_err(|_| "invalid datetime") // TODO Oh, rustpeg.
|
||||||
|
}
|
||||||
|
|
||||||
|
rule inst_micros() -> DateTime<Utc> =
|
||||||
|
"#instmicros" whitespace()+ d:$( digit()+ ) {
|
||||||
|
let micros = d.parse::<i64>().unwrap();
|
||||||
|
let seconds: i64 = micros / 1_000_000;
|
||||||
|
let nanos: u32 = ((micros % 1_000_000).unsigned_abs() as u32) * 1000;
|
||||||
|
Utc.timestamp_opt(seconds, nanos).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
rule inst_millis() -> DateTime<Utc> =
|
||||||
|
"#instmillis" whitespace()+ d:$( digit()+ ) {
|
||||||
|
let millis = d.parse::<i64>().unwrap();
|
||||||
|
let seconds: i64 = millis / 1000;
|
||||||
|
let nanos: u32 = ((millis % 1000).unsigned_abs() as u32) * 1_000_000;
|
||||||
|
Utc.timestamp_opt(seconds, nanos).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
rule inst() -> SpannedValue = t:(inst_millis() / inst_micros() / inst_string())
|
||||||
|
{ SpannedValue::Instant(t) }
|
||||||
|
|
||||||
|
rule uuid_string() -> Uuid =
|
||||||
|
"\"" u:$( ['a'..='f' | '0'..='9']*<8> "-" ['a'..='f' | '0'..='9']*<4> "-" ['a'..='f' | '0'..='9']*<4> "-" ['a'..='f' | '0'..='9']*<4> "-" ['a'..='f' | '0'..='9']*<12> ) "\"" {
|
||||||
|
Uuid::parse_str(u).expect("this is a valid UUID string")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub rule uuid() -> SpannedValue = "#uuid" whitespace()+ u:uuid_string()
|
||||||
|
{ SpannedValue::Uuid(u) }
|
||||||
|
|
||||||
|
rule byte_buffer() -> Bytes =
|
||||||
|
u:$( hex()+ ) {
|
||||||
|
let b = decode(u).expect("this is a valid hex byte string");
|
||||||
|
Bytes::copy_from_slice(&b)
|
||||||
|
}
|
||||||
|
pub rule bytes() -> SpannedValue = "#bytes" whitespace()+ u:byte_buffer()
|
||||||
|
{ SpannedValue::Bytes(u) }
|
||||||
|
|
||||||
|
rule namespace_divider() = "."
|
||||||
|
rule namespace_separator() = "/"
|
||||||
|
|
||||||
|
// TODO: Be more picky here.
|
||||||
|
// Keywords follow the rules of symbols, except they can (and must) begin with :
|
||||||
|
// e.g. :fred or :my/fred. See https://github.com/edn-format/edn#keywords
|
||||||
|
rule symbol_char_initial() = ['a'..='z' | 'A'..='Z' | '0'..='9' | '*' | '!' | '_' | '?' | '$' | '%' | '&' | '=' | '<' | '>']
|
||||||
|
rule symbol_char_subsequent() = ['+' | 'a'..='z' | 'A'..='Z' | '0'..='9' | '*' | '!' | '_' | '?' | '$' | '%' | '&' | '=' | '<' | '>' | '-']
|
||||||
|
|
||||||
|
rule symbol_namespace() = symbol_char_initial() symbol_char_subsequent()* (namespace_divider() symbol_char_subsequent()+)*
|
||||||
|
rule symbol_name() = ( symbol_char_initial()+ symbol_char_subsequent()* )
|
||||||
|
rule plain_symbol_name() = symbol_name() / "..." / "."
|
||||||
|
|
||||||
|
rule keyword_prefix() = ":"
|
||||||
|
|
||||||
|
pub rule symbol() -> SpannedValue =
|
||||||
|
ns:( sns:$(symbol_namespace()) namespace_separator() { sns })?
|
||||||
|
n:$(plain_symbol_name())
|
||||||
|
{ SpannedValue::from_symbol(ns, n) }
|
||||||
|
/ expected!("symbol")
|
||||||
|
|
||||||
|
pub rule keyword() -> SpannedValue =
|
||||||
|
keyword_prefix()
|
||||||
|
ns:( sns:$(symbol_namespace()) namespace_separator() { sns })?
|
||||||
|
n:$(symbol_name())
|
||||||
|
{ SpannedValue::from_keyword(ns, n) }
|
||||||
|
/ expected!("keyword")
|
||||||
|
|
||||||
|
pub rule list() -> SpannedValue = "(" __ v:(value())* __ ")"
|
||||||
|
{ SpannedValue::List(LinkedList::from_iter(v)) }
|
||||||
|
|
||||||
|
pub rule vector() -> SpannedValue = "[" __ v:(value())* __ "]"
|
||||||
|
{ SpannedValue::Vector(v) }
|
||||||
|
|
||||||
|
pub rule set() -> SpannedValue = "#{" __ v:(value())* __ "}"
|
||||||
|
{ SpannedValue::Set(BTreeSet::from_iter(v)) }
|
||||||
|
|
||||||
|
pub rule pair() -> (ValueAndSpan, ValueAndSpan) =
|
||||||
|
k:(value()) v:(value()) {
|
||||||
|
(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub rule map() -> SpannedValue = "{" __ v:(pair())* __ "}"
|
||||||
|
{ SpannedValue::Map(BTreeMap::from_iter(v)) }
|
||||||
|
|
||||||
|
// Note: It's important that float comes before integer or the parser assumes that floats are integers and fails to parse.
|
||||||
|
pub rule value() -> ValueAndSpan =
|
||||||
|
__ start:position!() v:(nil() / nan() / infinity() / boolean() / number() / inst() / uuid() / bytes() / text() / keyword() / symbol() / list() / vector() / map() / set() ) end:position!() __ {
|
||||||
|
ValueAndSpan {
|
||||||
|
inner: v,
|
||||||
|
span: Span::new(start, end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/ expected!("value")
|
||||||
|
|
||||||
|
rule atom() -> ValueAndSpan
|
||||||
|
= v:value() {? if v.is_atom() { Ok(v) } else { Err("expected atom") } }
|
||||||
|
|
||||||
|
// Clojure (and thus EDN) regards commas as whitespace, and thus the two-element vectors [1 2] and
|
||||||
|
// [1,,,,2] are equivalent, as are the maps {:a 1, :b 2} and {:a 1 :b 2}.
|
||||||
|
rule whitespace() = quiet!{[' ' | '\r' | '\n' | '\t' | ',']}
|
||||||
|
rule comment() = quiet!{";" (!['\r' | '\n'][_])* ['\r' | '\n']?}
|
||||||
|
|
||||||
|
rule __() = (whitespace() / comment())*
|
||||||
|
|
||||||
|
// Transaction entity parser starts here.
|
||||||
|
|
||||||
|
pub rule op() -> OpType
|
||||||
|
= ":db/add" { OpType::Add }
|
||||||
|
/ ":db/retract" { OpType::Retract }
|
||||||
|
|
||||||
|
rule raw_keyword() -> Keyword =
|
||||||
|
keyword_prefix()
|
||||||
|
ns:( sns:$(symbol_namespace()) namespace_separator() { sns })?
|
||||||
|
n:$(symbol_name()) {
|
||||||
|
match ns {
|
||||||
|
Some(ns) => Keyword::namespaced(ns, n),
|
||||||
|
None => Keyword::plain(n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/ expected!("keyword")
|
||||||
|
|
||||||
|
rule raw_forward_keyword() -> Keyword
|
||||||
|
= v:raw_keyword() {? if v.is_forward() { Ok(v) } else { Err("expected :forward or :forward/keyword") } }
|
||||||
|
|
||||||
|
rule raw_backward_keyword() -> Keyword
|
||||||
|
= v:raw_keyword() {? if v.is_backward() { Ok(v) } else { Err("expected :_backward or :backward/_keyword") } }
|
||||||
|
|
||||||
|
rule raw_namespaced_keyword() -> Keyword
|
||||||
|
= keyword_prefix() ns:$(symbol_namespace()) namespace_separator() n:$(symbol_name()) { Keyword::namespaced(ns, n) }
|
||||||
|
/ expected!("namespaced keyword")
|
||||||
|
|
||||||
|
rule raw_forward_namespaced_keyword() -> Keyword
|
||||||
|
= v:raw_namespaced_keyword() {? if v.is_forward() { Ok(v) } else { Err("expected namespaced :forward/keyword") } }
|
||||||
|
|
||||||
|
rule raw_backward_namespaced_keyword() -> Keyword
|
||||||
|
= v:raw_namespaced_keyword() {? if v.is_backward() { Ok(v) } else { Err("expected namespaced :backward/_keyword") } }
|
||||||
|
|
||||||
|
rule entid() -> EntidOrIdent
|
||||||
|
= v:( raw_basedinteger() / raw_hexinteger() / raw_octalinteger() / raw_integer() ) { EntidOrIdent::Entid(v) }
|
||||||
|
/ v:raw_namespaced_keyword() { EntidOrIdent::Ident(v) }
|
||||||
|
/ expected!("entid")
|
||||||
|
|
||||||
|
rule forward_entid() -> EntidOrIdent
|
||||||
|
= v:( raw_basedinteger() / raw_hexinteger() / raw_octalinteger() / raw_integer() ) { EntidOrIdent::Entid(v) }
|
||||||
|
/ v:raw_forward_namespaced_keyword() { EntidOrIdent::Ident(v) }
|
||||||
|
/ expected!("forward entid")
|
||||||
|
|
||||||
|
rule backward_entid() -> EntidOrIdent
|
||||||
|
= v:raw_backward_namespaced_keyword() { EntidOrIdent::Ident(v.to_reversed()) }
|
||||||
|
/ expected!("backward entid")
|
||||||
|
|
||||||
|
rule lookup_ref() -> LookupRef<ValueAndSpan>
|
||||||
|
= "(" __ "lookup-ref" __ a:(entid()) __ v:(value()) __ ")" { LookupRef { a: AttributePlace::Entid(a), v } }
|
||||||
|
/ expected!("lookup-ref")
|
||||||
|
|
||||||
|
rule tx_function() -> TxFunction
|
||||||
|
= "(" __ n:$(symbol_name()) __ ")" { TxFunction { op: PlainSymbol::plain(n) } }
|
||||||
|
|
||||||
|
rule entity_place() -> EntityPlace<ValueAndSpan>
|
||||||
|
= v:raw_text() { EntityPlace::TempId(TempId::External(v).into()) }
|
||||||
|
/ v:entid() { EntityPlace::Entid(v) }
|
||||||
|
/ v:lookup_ref() { EntityPlace::LookupRef(v) }
|
||||||
|
/ v:tx_function() { EntityPlace::TxFunction(v) }
|
||||||
|
|
||||||
|
rule value_place_pair() -> (EntidOrIdent, ValuePlace<ValueAndSpan>)
|
||||||
|
= k:(entid()) __ v:(value_place()) { (k, v) }
|
||||||
|
|
||||||
|
rule map_notation() -> MapNotation<ValueAndSpan>
|
||||||
|
= "{" __ kvs:(value_place_pair()*) __ "}" { kvs.into_iter().collect() }
|
||||||
|
|
||||||
|
rule value_place() -> ValuePlace<ValueAndSpan>
|
||||||
|
= __ v:lookup_ref() __ { ValuePlace::LookupRef(v) }
|
||||||
|
/ __ v:tx_function() __ { ValuePlace::TxFunction(v) }
|
||||||
|
/ __ "[" __ vs:(value_place()*) __ "]" __ { ValuePlace::Vector(vs) }
|
||||||
|
/ __ v:map_notation() __ { ValuePlace::MapNotation(v) }
|
||||||
|
/ __ v:atom() __ { ValuePlace::Atom(v) }
|
||||||
|
|
||||||
|
pub rule entity() -> Entity<ValueAndSpan>
|
||||||
|
= __ "[" __ op:(op()) __ e:(entity_place()) __ a:(forward_entid()) __ v:(value_place()) __ "]" __ { Entity::AddOrRetract { op, e, a: AttributePlace::Entid(a), v } }
|
||||||
|
/ __ "[" __ op:(op()) __ e:(value_place()) __ a:(backward_entid()) __ v:(entity_place()) __ "]" __ { Entity::AddOrRetract { op, e: v, a: AttributePlace::Entid(a), v: e } }
|
||||||
|
/ __ map:map_notation() __ { Entity::MapNotation(map) }
|
||||||
|
/ expected!("entity")
|
||||||
|
|
||||||
|
pub rule entities() -> Vec<Entity<ValueAndSpan>>
|
||||||
|
= __ "[" __ es:(entity()*) __ "]" __ { es }
|
||||||
|
|
||||||
|
// Query parser starts here.
|
||||||
|
//
|
||||||
|
// We expect every rule except the `raw_*` rules to eat whitespace
|
||||||
|
// (with `__`) at its start and finish. That means that every string
|
||||||
|
// pattern (say "[") should be bracketed on either side with either a
|
||||||
|
// whitespace-eating rule or an explicit whitespace eating `__`.
|
||||||
|
|
||||||
|
rule query_function() -> query::QueryFunction
|
||||||
|
= __ n:$(symbol_name()) __ {? query::QueryFunction::from_symbol(&PlainSymbol::plain(n)).ok_or("expected query function") }
|
||||||
|
|
||||||
|
rule fn_arg() -> query::FnArg
|
||||||
|
= v:value() {? query::FnArg::from_value(&v).ok_or("expected query function argument") }
|
||||||
|
/ __ "[" args:fn_arg()+ "]" __ { query::FnArg::Vector(args) }
|
||||||
|
|
||||||
|
rule find_elem() -> query::Element
|
||||||
|
= __ v:variable() __ { query::Element::Variable(v) }
|
||||||
|
/ __ "(" __ "the" v:variable() ")" __ { query::Element::Corresponding(v) }
|
||||||
|
/ __ "(" __ "pull" var:variable() "[" patterns:pull_attribute()+ "]" __ ")" __ { query::Element::Pull(query::Pull { var, patterns }) }
|
||||||
|
/ __ "(" func:query_function() args:fn_arg()* ")" __ { query::Element::Aggregate(query::Aggregate { func, args }) }
|
||||||
|
|
||||||
|
rule find_spec() -> query::FindSpec
|
||||||
|
= f:find_elem() "." __ { query::FindSpec::FindScalar(f) }
|
||||||
|
/ fs:find_elem()+ { query::FindSpec::FindRel(fs) }
|
||||||
|
/ __ "[" f:find_elem() __ "..." __ "]" __ { query::FindSpec::FindColl(f) }
|
||||||
|
/ __ "[" fs:find_elem()+ "]" __ { query::FindSpec::FindTuple(fs) }
|
||||||
|
|
||||||
|
rule pull_attribute() -> query::PullAttributeSpec
|
||||||
|
= __ "*" __ { query::PullAttributeSpec::Wildcard }
|
||||||
|
/ __ k:raw_forward_namespaced_keyword() __ alias:(":as" __ alias:raw_forward_keyword() __ { alias })? {
|
||||||
|
let attribute = query::PullConcreteAttribute::Ident(::std::rc::Rc::new(k));
|
||||||
|
let alias = alias.map(::std::rc::Rc::new);
|
||||||
|
query::PullAttributeSpec::Attribute(
|
||||||
|
query::NamedPullAttribute {
|
||||||
|
attribute,
|
||||||
|
alias,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
rule limit() -> query::Limit
|
||||||
|
= __ v:variable() __ { query::Limit::Variable(v) }
|
||||||
|
/ __ n:(raw_octalinteger() / raw_hexinteger() / raw_basedinteger() / raw_integer()) __ {?
|
||||||
|
if n > 0 {
|
||||||
|
Ok(query::Limit::Fixed(n as u64))
|
||||||
|
} else {
|
||||||
|
Err("expected positive integer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rule order() -> query::Order
|
||||||
|
= __ "(" __ "asc" v:variable() ")" __ { query::Order(query::Direction::Ascending, v) }
|
||||||
|
/ __ "(" __ "desc" v:variable() ")" __ { query::Order(query::Direction::Descending, v) }
|
||||||
|
/ v:variable() { query::Order(query::Direction::Ascending, v) }
|
||||||
|
|
||||||
|
|
||||||
|
rule pattern_value_place() -> query::PatternValuePlace
|
||||||
|
= v:value() {? query::PatternValuePlace::from_value(&v).ok_or("expected pattern_value_place") }
|
||||||
|
|
||||||
|
rule pattern_non_value_place() -> query::PatternNonValuePlace
|
||||||
|
= v:value() {? query::PatternNonValuePlace::from_value(&v).ok_or("expected pattern_non_value_place") }
|
||||||
|
|
||||||
|
rule pattern() -> query::WhereClause
|
||||||
|
= __ "["
|
||||||
|
src:src_var()?
|
||||||
|
e:pattern_non_value_place()
|
||||||
|
a:pattern_non_value_place()
|
||||||
|
v:pattern_value_place()?
|
||||||
|
tx:pattern_non_value_place()?
|
||||||
|
"]" __
|
||||||
|
{?
|
||||||
|
let v = v.unwrap_or(query::PatternValuePlace::Placeholder);
|
||||||
|
let tx = tx.unwrap_or(query::PatternNonValuePlace::Placeholder);
|
||||||
|
|
||||||
|
// Pattern::new takes care of reversal of reversed
|
||||||
|
// attributes: [?x :foo/_bar ?y] turns into
|
||||||
|
// [?y :foo/bar ?x].
|
||||||
|
//
|
||||||
|
// This is a bit messy: the inner conversion to a Pattern can
|
||||||
|
// fail if the input is something like
|
||||||
|
//
|
||||||
|
// ```edn
|
||||||
|
// [?x :foo/_reversed 23.4]
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// because
|
||||||
|
//
|
||||||
|
// ```edn
|
||||||
|
// [23.4 :foo/reversed ?x]
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// is nonsense. That leaves us with a nested optional, which we unwrap here.
|
||||||
|
query::Pattern::new(src, e, a, v, tx)
|
||||||
|
.map(query::WhereClause::Pattern)
|
||||||
|
.ok_or("expected pattern")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This shouldn't be checked at parse time.
|
||||||
|
rule rule_vars() -> BTreeSet<query::Variable>
|
||||||
|
= vs:variable()+ {?
|
||||||
|
let given = vs.len();
|
||||||
|
let set: BTreeSet<query::Variable> = vs.into_iter().collect();
|
||||||
|
if given != set.len() {
|
||||||
|
Err("expected unique variables")
|
||||||
|
} else {
|
||||||
|
Ok(set)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rule or_pattern_clause() -> query::OrWhereClause
|
||||||
|
= clause:where_clause() { query::OrWhereClause::Clause(clause) }
|
||||||
|
|
||||||
|
rule or_and_clause() -> query::OrWhereClause
|
||||||
|
= __ "(" __ "and" clauses:where_clause()+ ")" __ { query::OrWhereClause::And(clauses) }
|
||||||
|
|
||||||
|
rule or_where_clause() -> query::OrWhereClause
|
||||||
|
= or_pattern_clause()
|
||||||
|
/ or_and_clause()
|
||||||
|
|
||||||
|
rule or_clause() -> query::WhereClause
|
||||||
|
= __ "(" __ "or" clauses:or_where_clause()+ ")" __ {
|
||||||
|
query::WhereClause::OrJoin(query::OrJoin::new(query::UnifyVars::Implicit, clauses))
|
||||||
|
}
|
||||||
|
|
||||||
|
rule or_join_clause() -> query::WhereClause
|
||||||
|
= __ "(" __ "or-join" __ "[" vars:rule_vars() "]" clauses:or_where_clause()+ ")" __ {
|
||||||
|
query::WhereClause::OrJoin(query::OrJoin::new(query::UnifyVars::Explicit(vars), clauses))
|
||||||
|
}
|
||||||
|
|
||||||
|
rule not_clause() -> query::WhereClause
|
||||||
|
= __ "(" __ "not" clauses:where_clause()+ ")" __ {
|
||||||
|
query::WhereClause::NotJoin(query::NotJoin::new(query::UnifyVars::Implicit, clauses))
|
||||||
|
}
|
||||||
|
|
||||||
|
rule not_join_clause() -> query::WhereClause
|
||||||
|
= __ "(" __ "not-join" __ "[" vars:rule_vars() "]" clauses:where_clause()+ ")" __ {
|
||||||
|
query::WhereClause::NotJoin(query::NotJoin::new(query::UnifyVars::Explicit(vars), clauses))
|
||||||
|
}
|
||||||
|
|
||||||
|
rule type_annotation() -> query::WhereClause
|
||||||
|
= __ "[" __ "(" __ "type" var:variable() __ ty:raw_keyword() __ ")" __ "]" __ {
|
||||||
|
query::WhereClause::TypeAnnotation(
|
||||||
|
query::TypeAnnotation {
|
||||||
|
value_type: ty,
|
||||||
|
variable: var,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
rule pred() -> query::WhereClause
|
||||||
|
= __ "[" __ "(" func:query_function() args:fn_arg()* ")" __ "]" __ {
|
||||||
|
query::WhereClause::Pred(
|
||||||
|
query::Predicate {
|
||||||
|
operator: func.0,
|
||||||
|
args,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub rule where_fn() -> query::WhereClause
|
||||||
|
= __ "[" __ "(" func:query_function() args:fn_arg()* ")" __ binding:binding() "]" __ {
|
||||||
|
query::WhereClause::WhereFn(
|
||||||
|
query::WhereFn {
|
||||||
|
operator: func.0,
|
||||||
|
args,
|
||||||
|
binding,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
rule where_clause() -> query::WhereClause
|
||||||
|
// Right now we only support patterns and predicates. See #239 for more.
|
||||||
|
= pattern()
|
||||||
|
/ or_join_clause()
|
||||||
|
/ or_clause()
|
||||||
|
/ not_join_clause()
|
||||||
|
/ not_clause()
|
||||||
|
/ type_annotation()
|
||||||
|
/ pred()
|
||||||
|
/ where_fn()
|
||||||
|
|
||||||
|
rule query_part() -> query::QueryPart
|
||||||
|
= __ ":find" fs:find_spec() { query::QueryPart::FindSpec(fs) }
|
||||||
|
/ __ ":in" in_vars:variable()+ { query::QueryPart::InVars(in_vars) }
|
||||||
|
/ __ ":limit" l:limit() { query::QueryPart::Limit(l) }
|
||||||
|
/ __ ":order" os:order()+ { query::QueryPart::Order(os) }
|
||||||
|
/ __ ":where" ws:where_clause()+ { query::QueryPart::WhereClauses(ws) }
|
||||||
|
/ __ ":with" with_vars:variable()+ { query::QueryPart::WithVars(with_vars) }
|
||||||
|
|
||||||
|
pub rule parse_query() -> query::ParsedQuery
|
||||||
|
= __ "[" qps:query_part()+ "]" __ {? query::ParsedQuery::from_parts(qps) }
|
||||||
|
|
||||||
|
rule variable() -> query::Variable
|
||||||
|
= v:value() {? query::Variable::from_value(&v).ok_or("expected variable") }
|
||||||
|
|
||||||
|
rule src_var() -> query::SrcVar
|
||||||
|
= v:value() {? query::SrcVar::from_value(&v).ok_or("expected src_var") }
|
||||||
|
|
||||||
|
rule variable_or_placeholder() -> query::VariableOrPlaceholder
|
||||||
|
= v:variable() { query::VariableOrPlaceholder::Variable(v) }
|
||||||
|
/ __ "_" __ { query::VariableOrPlaceholder::Placeholder }
|
||||||
|
|
||||||
|
rule binding() -> query::Binding
|
||||||
|
= __ "[" __ "[" vs:variable_or_placeholder()+ "]" __ "]" __ { query::Binding::BindRel(vs) }
|
||||||
|
/ __ "[" v:variable() "..." __ "]" __ { query::Binding::BindColl(v) }
|
||||||
|
/ __ "[" vs:variable_or_placeholder()+ "]" __ { query::Binding::BindTuple(vs) }
|
||||||
|
/ v:variable() { query::Binding::BindScalar(v) }
|
||||||
|
|
||||||
|
});
|
||||||
|
|
|
@ -8,12 +8,12 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use itertools::diff_with;
|
use itertools::diff_with;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use symbols;
|
use crate::symbols;
|
||||||
use types::Value;
|
use crate::types::Value;
|
||||||
|
|
||||||
/// A trait defining pattern matching rules for any given pattern of type `T`.
|
/// A trait defining pattern matching rules for any given pattern of type `T`.
|
||||||
trait PatternMatchingRules<'a, T> {
|
trait PatternMatchingRules<'a, T> {
|
||||||
|
@ -21,7 +21,7 @@ trait PatternMatchingRules<'a, T> {
|
||||||
fn matches_any(pattern: &T) -> bool;
|
fn matches_any(pattern: &T) -> bool;
|
||||||
|
|
||||||
/// Return the placeholder name if the given pattern matches a placeholder.
|
/// Return the placeholder name if the given pattern matches a placeholder.
|
||||||
fn matches_placeholder(pattern: &'a T) -> Option<(&'a String)>;
|
fn matches_placeholder(pattern: &'a T) -> Option<&'a String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A default type implementing `PatternMatchingRules` specialized on
|
/// A default type implementing `PatternMatchingRules` specialized on
|
||||||
|
@ -34,14 +34,20 @@ impl<'a> PatternMatchingRules<'a, Value> for DefaultPatternMatchingRules {
|
||||||
fn matches_any(pattern: &Value) -> bool {
|
fn matches_any(pattern: &Value) -> bool {
|
||||||
match *pattern {
|
match *pattern {
|
||||||
Value::PlainSymbol(symbols::PlainSymbol(ref s)) => s.starts_with('_'),
|
Value::PlainSymbol(symbols::PlainSymbol(ref s)) => s.starts_with('_'),
|
||||||
_ => false
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matches_placeholder(pattern: &'a Value) -> Option<(&'a String)> {
|
fn matches_placeholder(pattern: &'a Value) -> Option<&'a String> {
|
||||||
match *pattern {
|
match *pattern {
|
||||||
Value::PlainSymbol(symbols::PlainSymbol(ref s)) => if s.starts_with('?') { Some(s) } else { None },
|
Value::PlainSymbol(symbols::PlainSymbol(ref s)) => {
|
||||||
_ => None
|
if s.starts_with('?') {
|
||||||
|
Some(s)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,14 +58,14 @@ impl<'a> PatternMatchingRules<'a, Value> for DefaultPatternMatchingRules {
|
||||||
/// * `[_ _]` matches an arbitrary two-element vector;
|
/// * `[_ _]` matches an arbitrary two-element vector;
|
||||||
/// * `[?x ?x]` matches `[1 1]` and `[#{} #{}]` but not `[1 2]` or `[[] #{}]`;
|
/// * `[?x ?x]` matches `[1 1]` and `[#{} #{}]` but not `[1 2]` or `[[] #{}]`;
|
||||||
struct Matcher<'a> {
|
struct Matcher<'a> {
|
||||||
placeholders: RefCell<HashMap<&'a String, &'a Value>>
|
placeholders: RefCell<HashMap<&'a String, &'a Value>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Matcher<'a> {
|
impl<'a> Matcher<'a> {
|
||||||
/// Creates a Matcher instance.
|
/// Creates a Matcher instance.
|
||||||
fn new() -> Matcher<'a> {
|
fn new() -> Matcher<'a> {
|
||||||
Matcher {
|
Matcher {
|
||||||
placeholders: RefCell::default()
|
placeholders: RefCell::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +73,9 @@ impl<'a> Matcher<'a> {
|
||||||
/// and `pattern`) utilizing a specified pattern matching ruleset `T`.
|
/// and `pattern`) utilizing a specified pattern matching ruleset `T`.
|
||||||
/// Returns true if matching succeeds.
|
/// Returns true if matching succeeds.
|
||||||
fn match_with_rules<T>(value: &'a Value, pattern: &'a Value) -> bool
|
fn match_with_rules<T>(value: &'a Value, pattern: &'a Value) -> bool
|
||||||
where T: PatternMatchingRules<'a, Value> {
|
where
|
||||||
|
T: PatternMatchingRules<'a, Value>,
|
||||||
|
{
|
||||||
let matcher = Matcher::new();
|
let matcher = Matcher::new();
|
||||||
matcher.match_internal::<T>(value, pattern)
|
matcher.match_internal::<T>(value, pattern)
|
||||||
}
|
}
|
||||||
|
@ -76,8 +84,10 @@ impl<'a> Matcher<'a> {
|
||||||
/// performing pattern matching. Note that the internal `placeholders` cache
|
/// performing pattern matching. Note that the internal `placeholders` cache
|
||||||
/// might not be empty on invocation.
|
/// might not be empty on invocation.
|
||||||
fn match_internal<T>(&self, value: &'a Value, pattern: &'a Value) -> bool
|
fn match_internal<T>(&self, value: &'a Value, pattern: &'a Value) -> bool
|
||||||
where T: PatternMatchingRules<'a, Value> {
|
where
|
||||||
use Value::*;
|
T: PatternMatchingRules<'a, Value>,
|
||||||
|
{
|
||||||
|
use crate::Value::*;
|
||||||
|
|
||||||
if T::matches_any(pattern) {
|
if T::matches_any(pattern) {
|
||||||
true
|
true
|
||||||
|
@ -86,19 +96,35 @@ impl<'a> Matcher<'a> {
|
||||||
value == *placeholders.entry(symbol).or_insert(value)
|
value == *placeholders.entry(symbol).or_insert(value)
|
||||||
} else {
|
} else {
|
||||||
match (value, pattern) {
|
match (value, pattern) {
|
||||||
(&Vector(ref v), &Vector(ref p)) =>
|
(&Vector(ref v), &Vector(ref p)) => {
|
||||||
diff_with(v, p, |a, b| self.match_internal::<T>(a, b)).is_none(),
|
diff_with(v, p, |a, b| self.match_internal::<T>(a, b)).is_none()
|
||||||
(&List(ref v), &List(ref p)) =>
|
}
|
||||||
diff_with(v, p, |a, b| self.match_internal::<T>(a, b)).is_none(),
|
(&List(ref v), &List(ref p)) => {
|
||||||
(&Set(ref v), &Set(ref p)) =>
|
diff_with(v, p, |a, b| self.match_internal::<T>(a, b)).is_none()
|
||||||
v.len() == p.len() &&
|
}
|
||||||
v.iter().all(|a| p.iter().any(|b| self.match_internal::<T>(a, b))) &&
|
(&Set(ref v), &Set(ref p)) => {
|
||||||
p.iter().all(|b| v.iter().any(|a| self.match_internal::<T>(a, b))),
|
v.len() == p.len()
|
||||||
(&Map(ref v), &Map(ref p)) =>
|
&& v.iter()
|
||||||
v.len() == p.len() &&
|
.all(|a| p.iter().any(|b| self.match_internal::<T>(a, b)))
|
||||||
v.iter().all(|a| p.iter().any(|b| self.match_internal::<T>(a.0, b.0) && self.match_internal::<T>(a.1, b.1))) &&
|
&& p.iter()
|
||||||
p.iter().all(|b| v.iter().any(|a| self.match_internal::<T>(a.0, b.0) && self.match_internal::<T>(a.1, b.1))),
|
.all(|b| v.iter().any(|a| self.match_internal::<T>(a, b)))
|
||||||
_ => value == pattern
|
}
|
||||||
|
(&Map(ref v), &Map(ref p)) => {
|
||||||
|
v.len() == p.len()
|
||||||
|
&& v.iter().all(|a| {
|
||||||
|
p.iter().any(|b| {
|
||||||
|
self.match_internal::<T>(a.0, b.0)
|
||||||
|
&& self.match_internal::<T>(a.1, b.1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
&& p.iter().all(|b| {
|
||||||
|
v.iter().any(|a| {
|
||||||
|
self.match_internal::<T>(a.0, b.0)
|
||||||
|
&& self.match_internal::<T>(a.1, b.1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => value == pattern,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,7 +140,7 @@ impl Value {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use parse;
|
use crate::parse;
|
||||||
|
|
||||||
macro_rules! assert_match {
|
macro_rules! assert_match {
|
||||||
( $pattern:tt, $value:tt, $expected:expr ) => {
|
( $pattern:tt, $value:tt, $expected:expr ) => {
|
||||||
|
@ -127,7 +153,7 @@ mod test {
|
||||||
};
|
};
|
||||||
( $pattern:tt !~ $value:tt ) => {
|
( $pattern:tt !~ $value:tt ) => {
|
||||||
assert_match!($pattern, $value, false);
|
assert_match!($pattern, $value, false);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -8,25 +8,14 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
use std::cmp::{
|
use std::cmp::{Ord, Ordering, PartialOrd};
|
||||||
Ord,
|
|
||||||
Ordering,
|
|
||||||
PartialOrd,
|
|
||||||
};
|
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
#[cfg(feature = "serde_support")]
|
#[cfg(feature = "serde_support")]
|
||||||
use serde::de::{
|
use serde::de::{self, Deserialize, Deserializer};
|
||||||
self,
|
|
||||||
Deserialize,
|
|
||||||
Deserializer
|
|
||||||
};
|
|
||||||
#[cfg(feature = "serde_support")]
|
#[cfg(feature = "serde_support")]
|
||||||
use serde::ser::{
|
use serde::ser::{Serialize, Serializer};
|
||||||
Serialize,
|
|
||||||
Serializer,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Data storage for both NamespaceableKeyword and NamespaceableSymbol.
|
// Data storage for both NamespaceableKeyword and NamespaceableSymbol.
|
||||||
#[derive(Clone, Eq, Hash, PartialEq)]
|
#[derive(Clone, Eq, Hash, PartialEq)]
|
||||||
|
@ -56,7 +45,10 @@ pub struct NamespaceableName {
|
||||||
|
|
||||||
impl NamespaceableName {
|
impl NamespaceableName {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn plain<T>(name: T) -> Self where T: Into<String> {
|
pub fn plain<T>(name: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
let n = name.into();
|
let n = name.into();
|
||||||
assert!(!n.is_empty(), "Symbols and keywords cannot be unnamed.");
|
assert!(!n.is_empty(), "Symbols and keywords cannot be unnamed.");
|
||||||
|
|
||||||
|
@ -67,14 +59,21 @@ impl NamespaceableName {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn namespaced<N, T>(namespace: N, name: T) -> Self where N: AsRef<str>, T: AsRef<str> {
|
pub fn namespaced<N, T>(namespace: N, name: T) -> Self
|
||||||
|
where
|
||||||
|
N: AsRef<str>,
|
||||||
|
T: AsRef<str>,
|
||||||
|
{
|
||||||
let n = name.as_ref();
|
let n = name.as_ref();
|
||||||
let ns = namespace.as_ref();
|
let ns = namespace.as_ref();
|
||||||
|
|
||||||
// Note: These invariants are not required for safety. That is, if we
|
// Note: These invariants are not required for safety. That is, if we
|
||||||
// decide to allow these we can safely remove them.
|
// decide to allow these we can safely remove them.
|
||||||
assert!(!n.is_empty(), "Symbols and keywords cannot be unnamed.");
|
assert!(!n.is_empty(), "Symbols and keywords cannot be unnamed.");
|
||||||
assert!(!ns.is_empty(), "Symbols and keywords cannot have an empty non-null namespace.");
|
assert!(
|
||||||
|
!ns.is_empty(),
|
||||||
|
"Symbols and keywords cannot have an empty non-null namespace."
|
||||||
|
);
|
||||||
|
|
||||||
let mut dest = String::with_capacity(n.len() + ns.len());
|
let mut dest = String::with_capacity(n.len() + ns.len());
|
||||||
|
|
||||||
|
@ -86,11 +85,15 @@ impl NamespaceableName {
|
||||||
|
|
||||||
NamespaceableName {
|
NamespaceableName {
|
||||||
components: dest,
|
components: dest,
|
||||||
boundary: boundary,
|
boundary,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new<N, T>(namespace: Option<N>, name: T) -> Self where N: AsRef<str>, T: AsRef<str> {
|
fn new<N, T>(namespace: Option<N>, name: T) -> Self
|
||||||
|
where
|
||||||
|
N: AsRef<str>,
|
||||||
|
T: AsRef<str>,
|
||||||
|
{
|
||||||
if let Some(ns) = namespace {
|
if let Some(ns) = namespace {
|
||||||
Self::namespaced(ns, name)
|
Self::namespaced(ns, name)
|
||||||
} else {
|
} else {
|
||||||
|
@ -118,7 +121,7 @@ impl NamespaceableName {
|
||||||
if name.starts_with('_') {
|
if name.starts_with('_') {
|
||||||
Self::new(self.namespace(), &name[1..])
|
Self::new(self.namespace(), &name[1..])
|
||||||
} else {
|
} else {
|
||||||
Self::new(self.namespace(), &format!("_{}", name))
|
Self::new(self.namespace(), format!("_{}", name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,13 +144,14 @@ impl NamespaceableName {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn components<'a>(&'a self) -> (&'a str, &'a str) {
|
pub fn components(&self) -> (&str, &str) {
|
||||||
if self.boundary > 0 {
|
if self.boundary > 0 {
|
||||||
(&self.components[0..self.boundary],
|
(
|
||||||
&self.components[(self.boundary + 1)..])
|
&self.components[0..self.boundary],
|
||||||
|
&self.components[(self.boundary + 1)..],
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
(&self.components[0..0],
|
(&self.components[0..0], &self.components)
|
||||||
&self.components)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,7 +167,7 @@ impl PartialOrd for NamespaceableName {
|
||||||
(_, _) => {
|
(_, _) => {
|
||||||
// Just use a lexicographic ordering.
|
// Just use a lexicographic ordering.
|
||||||
self.components().partial_cmp(&other.components())
|
self.components().partial_cmp(&other.components())
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -201,8 +205,8 @@ impl fmt::Display for NamespaceableName {
|
||||||
// friendly and automatic (e.g. `derive`d), and just pass all work off to it in our custom
|
// friendly and automatic (e.g. `derive`d), and just pass all work off to it in our custom
|
||||||
// implementation of Serialize and Deserialize.
|
// implementation of Serialize and Deserialize.
|
||||||
#[cfg(feature = "serde_support")]
|
#[cfg(feature = "serde_support")]
|
||||||
#[cfg_attr(feature = "serde_support", serde(rename = "NamespaceableName"))]
|
|
||||||
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "serde_support", serde(rename = "NamespaceableName"))]
|
||||||
struct SerializedNamespaceableName<'a> {
|
struct SerializedNamespaceableName<'a> {
|
||||||
namespace: Option<&'a str>,
|
namespace: Option<&'a str>,
|
||||||
name: &'a str,
|
name: &'a str,
|
||||||
|
@ -210,14 +214,19 @@ struct SerializedNamespaceableName<'a> {
|
||||||
|
|
||||||
#[cfg(feature = "serde_support")]
|
#[cfg(feature = "serde_support")]
|
||||||
impl<'de> Deserialize<'de> for NamespaceableName {
|
impl<'de> Deserialize<'de> for NamespaceableName {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
let separated = SerializedNamespaceableName::deserialize(deserializer)?;
|
let separated = SerializedNamespaceableName::deserialize(deserializer)?;
|
||||||
if separated.name.len() == 0 {
|
if separated.name.is_empty() {
|
||||||
return Err(de::Error::custom("Empty name in keyword or symbol"));
|
return Err(de::Error::custom("Empty name in keyword or symbol"));
|
||||||
}
|
}
|
||||||
if let Some(ns) = separated.namespace {
|
if let Some(ns) = separated.namespace {
|
||||||
if ns.len() == 0 {
|
if ns.is_empty() {
|
||||||
Err(de::Error::custom("Empty but present namespace in keyword or symbol"))
|
Err(de::Error::custom(
|
||||||
|
"Empty but present namespace in keyword or symbol",
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
Ok(NamespaceableName::namespaced(ns, separated.name))
|
Ok(NamespaceableName::namespaced(ns, separated.name))
|
||||||
}
|
}
|
||||||
|
@ -229,7 +238,10 @@ impl<'de> Deserialize<'de> for NamespaceableName {
|
||||||
|
|
||||||
#[cfg(feature = "serde_support")]
|
#[cfg(feature = "serde_support")]
|
||||||
impl Serialize for NamespaceableName {
|
impl Serialize for NamespaceableName {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
let ser = SerializedNamespaceableName {
|
let ser = SerializedNamespaceableName {
|
||||||
namespace: self.namespace(),
|
namespace: self.namespace(),
|
||||||
name: self.name(),
|
name: self.name(),
|
||||||
|
@ -245,12 +257,18 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_new_invariants_maintained() {
|
fn test_new_invariants_maintained() {
|
||||||
assert!(panic::catch_unwind(|| NamespaceableName::namespaced("", "foo")).is_err(),
|
assert!(
|
||||||
"Empty namespace should panic");
|
panic::catch_unwind(|| NamespaceableName::namespaced("", "foo")).is_err(),
|
||||||
assert!(panic::catch_unwind(|| NamespaceableName::namespaced("foo", "")).is_err(),
|
"Empty namespace should panic"
|
||||||
"Empty name should panic");
|
);
|
||||||
assert!(panic::catch_unwind(|| NamespaceableName::namespaced("", "")).is_err(),
|
assert!(
|
||||||
"Should panic if both fields are empty");
|
panic::catch_unwind(|| NamespaceableName::namespaced("foo", "")).is_err(),
|
||||||
|
"Empty name should panic"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
panic::catch_unwind(|| NamespaceableName::namespaced("", "")).is_err(),
|
||||||
|
"Should panic if both fields are empty"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -286,19 +304,11 @@ mod test {
|
||||||
n3.clone(),
|
n3.clone(),
|
||||||
n2.clone(),
|
n2.clone(),
|
||||||
n1.clone(),
|
n1.clone(),
|
||||||
n4.clone()
|
n4.clone(),
|
||||||
];
|
];
|
||||||
|
|
||||||
arr.sort();
|
arr.sort();
|
||||||
|
|
||||||
assert_eq!(arr, [
|
assert_eq!(arr, [n0, n2, n1, n3, n4, n5, n6,]);
|
||||||
n0.clone(),
|
|
||||||
n2.clone(),
|
|
||||||
n1.clone(),
|
|
||||||
n3.clone(),
|
|
||||||
n4.clone(),
|
|
||||||
n5.clone(),
|
|
||||||
n6.clone(),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,17 +8,15 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
use chrono::{
|
use chrono::SecondsFormat;
|
||||||
SecondsFormat,
|
|
||||||
};
|
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use pretty;
|
use pretty;
|
||||||
|
|
||||||
use std::io;
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
use types::Value;
|
use crate::types::Value;
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
/// Return a pretty string representation of this `Value`.
|
/// Return a pretty string representation of this `Value`.
|
||||||
|
@ -29,7 +27,10 @@ impl Value {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a pretty representation of this `Value` to the given writer.
|
/// Write a pretty representation of this `Value` to the given writer.
|
||||||
fn write_pretty<W>(&self, width: usize, out: &mut W) -> Result<(), io::Error> where W: io::Write {
|
fn write_pretty<W>(&self, width: usize, out: &mut W) -> Result<(), io::Error>
|
||||||
|
where
|
||||||
|
W: io::Write,
|
||||||
|
{
|
||||||
self.as_doc(&pretty::BoxAllocator).1.render(width, out)
|
self.as_doc(&pretty::BoxAllocator).1.render(width, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,28 +42,54 @@ impl Value {
|
||||||
/// [1,
|
/// [1,
|
||||||
/// 2,
|
/// 2,
|
||||||
/// 3].
|
/// 3].
|
||||||
fn bracket<'a, A, T, I>(&'a self, allocator: &'a A, open: T, vs: I, close: T) -> pretty::DocBuilder<'a, A>
|
fn bracket<'a, A, T, I>(
|
||||||
where A: pretty::DocAllocator<'a>, T: Into<Cow<'a, str>>, I: IntoIterator<Item=&'a Value> {
|
&'a self,
|
||||||
|
allocator: &'a A,
|
||||||
|
open: T,
|
||||||
|
vs: I,
|
||||||
|
close: T,
|
||||||
|
) -> pretty::DocBuilder<'a, A>
|
||||||
|
where
|
||||||
|
A: pretty::DocAllocator<'a>,
|
||||||
|
<A as pretty::DocAllocator<'a>>::Doc: std::clone::Clone,
|
||||||
|
T: Into<Cow<'a, str>>,
|
||||||
|
I: IntoIterator<Item = &'a Value>,
|
||||||
|
{
|
||||||
let open = open.into();
|
let open = open.into();
|
||||||
let n = open.len();
|
let n = open.len() as isize;
|
||||||
let i = vs.into_iter().map(|v| v.as_doc(allocator)).intersperse(allocator.space());
|
let i = {
|
||||||
allocator.text(open)
|
let this = vs.into_iter().map(|v| v.as_doc(allocator));
|
||||||
|
let element = allocator.line();
|
||||||
|
Itertools::intersperse(this, element)
|
||||||
|
};
|
||||||
|
allocator
|
||||||
|
.text(open)
|
||||||
.append(allocator.concat(i).nest(n))
|
.append(allocator.concat(i).nest(n))
|
||||||
.append(allocator.text(close))
|
.append(allocator.text(close))
|
||||||
.group()
|
.group()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Recursively traverses this value and creates a pretty.rs document.
|
/// Recursively traverses this value and creates a pretty.rs document.
|
||||||
/// This pretty printing implementation is optimized for edn queries
|
/// A pretty printing implementation for edn queries optimized for
|
||||||
/// readability and limited whitespace expansion.
|
/// readability and limited whitespace expansion.
|
||||||
fn as_doc<'a, A>(&'a self, pp: &'a A) -> pretty::DocBuilder<'a, A>
|
fn as_doc<'a, A>(&'a self, pp: &'a A) -> pretty::DocBuilder<'a, A>
|
||||||
where A: pretty::DocAllocator<'a> {
|
where
|
||||||
|
A: pretty::DocAllocator<'a>,
|
||||||
|
<A as pretty::DocAllocator<'a>>::Doc: std::clone::Clone,
|
||||||
|
{
|
||||||
match *self {
|
match *self {
|
||||||
Value::Vector(ref vs) => self.bracket(pp, "[", vs, "]"),
|
Value::Vector(ref vs) => self.bracket(pp, "[", vs, "]"),
|
||||||
Value::List(ref vs) => self.bracket(pp, "(", vs, ")"),
|
Value::List(ref vs) => self.bracket(pp, "(", vs, ")"),
|
||||||
Value::Set(ref vs) => self.bracket(pp, "#{", vs, "}"),
|
Value::Set(ref vs) => self.bracket(pp, "#{", vs, "}"),
|
||||||
Value::Map(ref vs) => {
|
Value::Map(ref vs) => {
|
||||||
let xs = vs.iter().rev().map(|(k, v)| k.as_doc(pp).append(pp.space()).append(v.as_doc(pp)).group()).intersperse(pp.space());
|
let xs = {
|
||||||
|
let this = vs
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.map(|(k, v)| k.as_doc(pp).append(pp.line()).append(v.as_doc(pp)).group());
|
||||||
|
let element = pp.line();
|
||||||
|
Itertools::intersperse(this, element)
|
||||||
|
};
|
||||||
pp.text("{")
|
pp.text("{")
|
||||||
.append(pp.concat(xs).nest(1))
|
.append(pp.concat(xs).nest(1))
|
||||||
.append(pp.text("}"))
|
.append(pp.text("}"))
|
||||||
|
@ -72,16 +99,22 @@ impl Value {
|
||||||
Value::PlainSymbol(ref v) => pp.text(v.to_string()),
|
Value::PlainSymbol(ref v) => pp.text(v.to_string()),
|
||||||
Value::Keyword(ref v) => pp.text(v.to_string()),
|
Value::Keyword(ref v) => pp.text(v.to_string()),
|
||||||
Value::Text(ref v) => pp.text("\"").append(v.as_str()).append("\""),
|
Value::Text(ref v) => pp.text("\"").append(v.as_str()).append("\""),
|
||||||
Value::Uuid(ref u) => pp.text("#uuid \"").append(u.hyphenated().to_string()).append("\""),
|
Value::Uuid(ref u) => pp
|
||||||
Value::Instant(ref v) => pp.text("#inst \"").append(v.to_rfc3339_opts(SecondsFormat::AutoSi, true)).append("\""),
|
.text("#uuid \"")
|
||||||
_ => pp.text(self.to_string())
|
.append(u.hyphenated().to_string())
|
||||||
|
.append("\""),
|
||||||
|
Value::Instant(ref v) => pp
|
||||||
|
.text("#inst \"")
|
||||||
|
.append(v.to_rfc3339_opts(SecondsFormat::AutoSi, true))
|
||||||
|
.append("\""),
|
||||||
|
_ => pp.text(self.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use parse;
|
use crate::parse;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pp_io() {
|
fn test_pp_io() {
|
||||||
|
@ -105,13 +138,16 @@ mod test {
|
||||||
let data = parse::value(string).unwrap().without_spans();
|
let data = parse::value(string).unwrap().without_spans();
|
||||||
|
|
||||||
assert_eq!(data.to_pretty(20).unwrap(), "[1 2 3 4 5 6]");
|
assert_eq!(data.to_pretty(20).unwrap(), "[1 2 3 4 5 6]");
|
||||||
assert_eq!(data.to_pretty(10).unwrap(), "\
|
assert_eq!(
|
||||||
|
data.to_pretty(10).unwrap(),
|
||||||
|
"\
|
||||||
[1
|
[1
|
||||||
2
|
2
|
||||||
3
|
3
|
||||||
4
|
4
|
||||||
5
|
5
|
||||||
6]");
|
6]"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -120,10 +156,13 @@ mod test {
|
||||||
let data = parse::value(string).unwrap().without_spans();
|
let data = parse::value(string).unwrap().without_spans();
|
||||||
|
|
||||||
assert_eq!(data.to_pretty(20).unwrap(), "{:a 1 :b 2 :c 3}");
|
assert_eq!(data.to_pretty(20).unwrap(), "{:a 1 :b 2 :c 3}");
|
||||||
assert_eq!(data.to_pretty(10).unwrap(), "\
|
assert_eq!(
|
||||||
|
data.to_pretty(10).unwrap(),
|
||||||
|
"\
|
||||||
{:a 1
|
{:a 1
|
||||||
:b 2
|
:b 2
|
||||||
:c 3}");
|
:c 3}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -131,7 +170,9 @@ mod test {
|
||||||
let string = "[ 1 2 ( 3.14 ) #{ 4N } { foo/bar 42 :baz/boz 43 } [ ] :five :six/seven eight nine/ten true false nil #f NaN #f -Infinity #f +Infinity ]";
|
let string = "[ 1 2 ( 3.14 ) #{ 4N } { foo/bar 42 :baz/boz 43 } [ ] :five :six/seven eight nine/ten true false nil #f NaN #f -Infinity #f +Infinity ]";
|
||||||
let data = parse::value(string).unwrap().without_spans();
|
let data = parse::value(string).unwrap().without_spans();
|
||||||
|
|
||||||
assert_eq!(data.to_pretty(40).unwrap(), "\
|
assert_eq!(
|
||||||
|
data.to_pretty(40).unwrap(),
|
||||||
|
"\
|
||||||
[1
|
[1
|
||||||
2
|
2
|
||||||
(3.14)
|
(3.14)
|
||||||
|
@ -147,7 +188,8 @@ mod test {
|
||||||
nil
|
nil
|
||||||
#f NaN
|
#f NaN
|
||||||
#f -Infinity
|
#f -Infinity
|
||||||
#f +Infinity]");
|
#f +Infinity]"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -155,7 +197,9 @@ mod test {
|
||||||
let string = "[:find ?id ?bar ?baz :in $ :where [?id :session/keyword-foo ?symbol1 ?symbol2 \"some string\"] [?tx :db/tx ?ts]]";
|
let string = "[:find ?id ?bar ?baz :in $ :where [?id :session/keyword-foo ?symbol1 ?symbol2 \"some string\"] [?tx :db/tx ?ts]]";
|
||||||
let data = parse::value(string).unwrap().without_spans();
|
let data = parse::value(string).unwrap().without_spans();
|
||||||
|
|
||||||
assert_eq!(data.to_pretty(40).unwrap(), "\
|
assert_eq!(
|
||||||
|
data.to_pretty(40).unwrap(),
|
||||||
|
"\
|
||||||
[:find
|
[:find
|
||||||
?id
|
?id
|
||||||
?bar
|
?bar
|
||||||
|
@ -168,7 +212,8 @@ mod test {
|
||||||
?symbol1
|
?symbol1
|
||||||
?symbol2
|
?symbol2
|
||||||
\"some string\"]
|
\"some string\"]
|
||||||
[?tx :db/tx ?ts]]");
|
[?tx :db/tx ?ts]]"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -176,7 +221,9 @@ mod test {
|
||||||
let string = "[:find [?id ?bar ?baz] :in [$] :where [?id :session/keyword-foo ?symbol1 ?symbol2 \"some string\"] [?tx :db/tx ?ts] (not-join [?id] [?id :session/keyword-bar _])]";
|
let string = "[:find [?id ?bar ?baz] :in [$] :where [?id :session/keyword-foo ?symbol1 ?symbol2 \"some string\"] [?tx :db/tx ?ts] (not-join [?id] [?id :session/keyword-bar _])]";
|
||||||
let data = parse::value(string).unwrap().without_spans();
|
let data = parse::value(string).unwrap().without_spans();
|
||||||
|
|
||||||
assert_eq!(data.to_pretty(40).unwrap(), "\
|
assert_eq!(
|
||||||
|
data.to_pretty(40).unwrap(),
|
||||||
|
"\
|
||||||
[:find
|
[:find
|
||||||
[?id ?bar ?baz]
|
[?id ?bar ?baz]
|
||||||
:in
|
:in
|
||||||
|
@ -190,6 +237,7 @@ mod test {
|
||||||
[?tx :db/tx ?ts]
|
[?tx :db/tx ?ts]
|
||||||
(not-join
|
(not-join
|
||||||
[?id]
|
[?id]
|
||||||
[?id :session/keyword-bar _])]");
|
[?id :session/keyword-bar _])]"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
403
edn/src/query.rs
403
edn/src/query.rs
|
@ -29,35 +29,17 @@
|
||||||
///! inner type directly in conjunction with matching on the enum. Before diving
|
///! inner type directly in conjunction with matching on the enum. Before diving
|
||||||
///! deeply into this it's worth recognizing that this loss of 'sovereignty' is
|
///! deeply into this it's worth recognizing that this loss of 'sovereignty' is
|
||||||
///! a tradeoff against well-typed function signatures and other such boundaries.
|
///! a tradeoff against well-typed function signatures and other such boundaries.
|
||||||
|
use std::collections::{BTreeSet, HashSet};
|
||||||
use std::collections::{
|
|
||||||
BTreeSet,
|
|
||||||
HashSet,
|
|
||||||
};
|
|
||||||
|
|
||||||
use std;
|
use std;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::rc::{
|
use std::rc::Rc;
|
||||||
Rc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use ::{
|
use crate::{BigInt, DateTime, OrderedFloat, Utc, Uuid};
|
||||||
BigInt,
|
|
||||||
DateTime,
|
|
||||||
OrderedFloat,
|
|
||||||
Uuid,
|
|
||||||
Utc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use ::value_rc::{
|
use crate::value_rc::{FromRc, ValueRc};
|
||||||
FromRc,
|
|
||||||
ValueRc,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use ::{
|
pub use crate::{Keyword, PlainSymbol};
|
||||||
Keyword,
|
|
||||||
PlainSymbol,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub type SrcVarName = String; // Do not include the required syntactic '$'.
|
pub type SrcVarName = String; // Do not include the required syntactic '$'.
|
||||||
|
|
||||||
|
@ -69,10 +51,6 @@ impl Variable {
|
||||||
self.0.as_ref().0.as_str()
|
self.0.as_ref().0.as_str()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_string(&self) -> String {
|
|
||||||
self.0.as_ref().0.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn name(&self) -> PlainSymbol {
|
pub fn name(&self) -> PlainSymbol {
|
||||||
self.0.as_ref().clone()
|
self.0.as_ref().clone()
|
||||||
}
|
}
|
||||||
|
@ -86,15 +64,15 @@ impl Variable {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FromValue<T> {
|
pub trait FromValue<T> {
|
||||||
fn from_value(v: &::ValueAndSpan) -> Option<T>;
|
fn from_value(v: &crate::ValueAndSpan) -> Option<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If the provided EDN value is a PlainSymbol beginning with '?', return
|
/// If the provided EDN value is a PlainSymbol beginning with '?', return
|
||||||
/// it wrapped in a Variable. If not, return None.
|
/// it wrapped in a Variable. If not, return None.
|
||||||
/// TODO: intern strings. #398.
|
/// TODO: intern strings. #398.
|
||||||
impl FromValue<Variable> for Variable {
|
impl FromValue<Variable> for Variable {
|
||||||
fn from_value(v: &::ValueAndSpan) -> Option<Variable> {
|
fn from_value(v: &crate::ValueAndSpan) -> Option<Variable> {
|
||||||
if let ::SpannedValue::PlainSymbol(ref s) = v.inner {
|
if let crate::SpannedValue::PlainSymbol(ref s) = v.inner {
|
||||||
Variable::from_symbol(s)
|
Variable::from_symbol(s)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -105,7 +83,7 @@ impl FromValue<Variable> for Variable {
|
||||||
impl Variable {
|
impl Variable {
|
||||||
pub fn from_rc(sym: Rc<PlainSymbol>) -> Option<Variable> {
|
pub fn from_rc(sym: Rc<PlainSymbol>) -> Option<Variable> {
|
||||||
if sym.is_var_symbol() {
|
if sym.is_var_symbol() {
|
||||||
Some(Variable(sym.clone()))
|
Some(Variable(sym))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -137,8 +115,8 @@ impl std::fmt::Display for Variable {
|
||||||
pub struct QueryFunction(pub PlainSymbol);
|
pub struct QueryFunction(pub PlainSymbol);
|
||||||
|
|
||||||
impl FromValue<QueryFunction> for QueryFunction {
|
impl FromValue<QueryFunction> for QueryFunction {
|
||||||
fn from_value(v: &::ValueAndSpan) -> Option<QueryFunction> {
|
fn from_value(v: &crate::ValueAndSpan) -> Option<QueryFunction> {
|
||||||
if let ::SpannedValue::PlainSymbol(ref s) = v.inner {
|
if let crate::SpannedValue::PlainSymbol(ref s) = v.inner {
|
||||||
QueryFunction::from_symbol(s)
|
QueryFunction::from_symbol(s)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -176,8 +154,8 @@ pub enum SrcVar {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromValue<SrcVar> for SrcVar {
|
impl FromValue<SrcVar> for SrcVar {
|
||||||
fn from_value(v: &::ValueAndSpan) -> Option<SrcVar> {
|
fn from_value(v: &crate::ValueAndSpan) -> Option<SrcVar> {
|
||||||
if let ::SpannedValue::PlainSymbol(ref s) = v.inner {
|
if let crate::SpannedValue::PlainSymbol(ref s) = v.inner {
|
||||||
SrcVar::from_symbol(s)
|
SrcVar::from_symbol(s)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -235,37 +213,27 @@ pub enum FnArg {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromValue<FnArg> for FnArg {
|
impl FromValue<FnArg> for FnArg {
|
||||||
fn from_value(v: &::ValueAndSpan) -> Option<FnArg> {
|
fn from_value(v: &crate::ValueAndSpan) -> Option<FnArg> {
|
||||||
use ::SpannedValue::*;
|
use crate::SpannedValue::*;
|
||||||
match v.inner {
|
match v.inner {
|
||||||
Integer(x) =>
|
Integer(x) => Some(FnArg::EntidOrInteger(x)),
|
||||||
Some(FnArg::EntidOrInteger(x)),
|
PlainSymbol(ref x) if x.is_src_symbol() => SrcVar::from_symbol(x).map(FnArg::SrcVar),
|
||||||
PlainSymbol(ref x) if x.is_src_symbol() =>
|
PlainSymbol(ref x) if x.is_var_symbol() => {
|
||||||
SrcVar::from_symbol(x).map(FnArg::SrcVar),
|
Variable::from_symbol(x).map(FnArg::Variable)
|
||||||
PlainSymbol(ref x) if x.is_var_symbol() =>
|
}
|
||||||
Variable::from_symbol(x).map(FnArg::Variable),
|
|
||||||
PlainSymbol(_) => None,
|
PlainSymbol(_) => None,
|
||||||
Keyword(ref x) =>
|
Keyword(ref x) => Some(FnArg::IdentOrKeyword(x.clone())),
|
||||||
Some(FnArg::IdentOrKeyword(x.clone())),
|
Instant(x) => Some(FnArg::Constant(NonIntegerConstant::Instant(x))),
|
||||||
Instant(x) =>
|
Uuid(x) => Some(FnArg::Constant(NonIntegerConstant::Uuid(x))),
|
||||||
Some(FnArg::Constant(NonIntegerConstant::Instant(x))),
|
Boolean(x) => Some(FnArg::Constant(NonIntegerConstant::Boolean(x))),
|
||||||
Uuid(x) =>
|
Float(x) => Some(FnArg::Constant(NonIntegerConstant::Float(x))),
|
||||||
Some(FnArg::Constant(NonIntegerConstant::Uuid(x))),
|
BigInteger(ref x) => Some(FnArg::Constant(NonIntegerConstant::BigInteger(x.clone()))),
|
||||||
Boolean(x) =>
|
|
||||||
Some(FnArg::Constant(NonIntegerConstant::Boolean(x))),
|
|
||||||
Float(x) =>
|
|
||||||
Some(FnArg::Constant(NonIntegerConstant::Float(x))),
|
|
||||||
BigInteger(ref x) =>
|
|
||||||
Some(FnArg::Constant(NonIntegerConstant::BigInteger(x.clone()))),
|
|
||||||
Text(ref x) =>
|
Text(ref x) =>
|
||||||
// TODO: intern strings. #398.
|
// TODO: intern strings. #398.
|
||||||
Some(FnArg::Constant(x.clone().into())),
|
{
|
||||||
Nil |
|
Some(FnArg::Constant(x.clone().into()))
|
||||||
NamespacedSymbol(_) |
|
}
|
||||||
Vector(_) |
|
Nil | NamespacedSymbol(_) | Vector(_) | List(_) | Set(_) | Map(_) | Bytes(_) => None,
|
||||||
List(_) |
|
|
||||||
Set(_) |
|
|
||||||
Map(_) => None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -274,18 +242,18 @@ impl FromValue<FnArg> for FnArg {
|
||||||
impl std::fmt::Display for FnArg {
|
impl std::fmt::Display for FnArg {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
&FnArg::Variable(ref var) => write!(f, "{}", var),
|
FnArg::Variable(ref var) => write!(f, "{}", var),
|
||||||
&FnArg::SrcVar(ref var) => {
|
FnArg::SrcVar(ref var) => {
|
||||||
if var == &SrcVar::DefaultSrc {
|
if var == &SrcVar::DefaultSrc {
|
||||||
write!(f, "$")
|
write!(f, "$")
|
||||||
} else {
|
} else {
|
||||||
write!(f, "{:?}", var)
|
write!(f, "{:?}", var)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
&FnArg::EntidOrInteger(entid) => write!(f, "{}", entid),
|
FnArg::EntidOrInteger(entid) => write!(f, "{}", entid),
|
||||||
&FnArg::IdentOrKeyword(ref kw) => write!(f, "{}", kw),
|
FnArg::IdentOrKeyword(ref kw) => write!(f, "{}", kw),
|
||||||
&FnArg::Constant(ref constant) => write!(f, "{:?}", constant),
|
FnArg::Constant(ref constant) => write!(f, "{:?}", constant),
|
||||||
&FnArg::Vector(ref vec) => write!(f, "{:?}", vec),
|
FnArg::Vector(ref vec) => write!(f, "{:?}", vec),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -293,7 +261,7 @@ impl std::fmt::Display for FnArg {
|
||||||
impl FnArg {
|
impl FnArg {
|
||||||
pub fn as_variable(&self) -> Option<&Variable> {
|
pub fn as_variable(&self) -> Option<&Variable> {
|
||||||
match self {
|
match self {
|
||||||
&FnArg::Variable(ref v) => Some(v),
|
FnArg::Variable(ref v) => Some(v),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -348,24 +316,25 @@ impl PatternNonValuePlace {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromValue<PatternNonValuePlace> for PatternNonValuePlace {
|
impl FromValue<PatternNonValuePlace> for PatternNonValuePlace {
|
||||||
fn from_value(v: &::ValueAndSpan) -> Option<PatternNonValuePlace> {
|
fn from_value(v: &crate::ValueAndSpan) -> Option<PatternNonValuePlace> {
|
||||||
match v.inner {
|
match v.inner {
|
||||||
::SpannedValue::Integer(x) => if x >= 0 {
|
crate::SpannedValue::Integer(x) => {
|
||||||
|
if x >= 0 {
|
||||||
Some(PatternNonValuePlace::Entid(x))
|
Some(PatternNonValuePlace::Entid(x))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
}
|
||||||
::SpannedValue::PlainSymbol(ref x) => if x.0.as_str() == "_" {
|
}
|
||||||
|
crate::SpannedValue::PlainSymbol(ref x) => {
|
||||||
|
if x.0.as_str() == "_" {
|
||||||
Some(PatternNonValuePlace::Placeholder)
|
Some(PatternNonValuePlace::Placeholder)
|
||||||
} else {
|
} else if let Some(v) = Variable::from_symbol(x) {
|
||||||
if let Some(v) = Variable::from_symbol(x) {
|
|
||||||
Some(PatternNonValuePlace::Variable(v))
|
Some(PatternNonValuePlace::Variable(v))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
::SpannedValue::Keyword(ref x) =>
|
crate::SpannedValue::Keyword(ref x) => Some(x.clone().into()),
|
||||||
Some(x.clone().into()),
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -402,38 +371,46 @@ impl From<Keyword> for PatternValuePlace {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromValue<PatternValuePlace> for PatternValuePlace {
|
impl FromValue<PatternValuePlace> for PatternValuePlace {
|
||||||
fn from_value(v: &::ValueAndSpan) -> Option<PatternValuePlace> {
|
fn from_value(v: &crate::ValueAndSpan) -> Option<PatternValuePlace> {
|
||||||
match v.inner {
|
match v.inner {
|
||||||
::SpannedValue::Integer(x) =>
|
crate::SpannedValue::Integer(x) => Some(PatternValuePlace::EntidOrInteger(x)),
|
||||||
Some(PatternValuePlace::EntidOrInteger(x)),
|
crate::SpannedValue::PlainSymbol(ref x) if x.0.as_str() == "_" => {
|
||||||
::SpannedValue::PlainSymbol(ref x) if x.0.as_str() == "_" =>
|
Some(PatternValuePlace::Placeholder)
|
||||||
Some(PatternValuePlace::Placeholder),
|
}
|
||||||
::SpannedValue::PlainSymbol(ref x) =>
|
crate::SpannedValue::PlainSymbol(ref x) => {
|
||||||
Variable::from_symbol(x).map(PatternValuePlace::Variable),
|
Variable::from_symbol(x).map(PatternValuePlace::Variable)
|
||||||
::SpannedValue::Keyword(ref x) if x.is_namespaced() =>
|
}
|
||||||
Some(x.clone().into()),
|
crate::SpannedValue::Keyword(ref x) if x.is_namespaced() => Some(x.clone().into()),
|
||||||
::SpannedValue::Boolean(x) =>
|
crate::SpannedValue::Boolean(x) => {
|
||||||
Some(PatternValuePlace::Constant(NonIntegerConstant::Boolean(x))),
|
Some(PatternValuePlace::Constant(NonIntegerConstant::Boolean(x)))
|
||||||
::SpannedValue::Float(x) =>
|
}
|
||||||
Some(PatternValuePlace::Constant(NonIntegerConstant::Float(x))),
|
crate::SpannedValue::Float(x) => {
|
||||||
::SpannedValue::BigInteger(ref x) =>
|
Some(PatternValuePlace::Constant(NonIntegerConstant::Float(x)))
|
||||||
Some(PatternValuePlace::Constant(NonIntegerConstant::BigInteger(x.clone()))),
|
}
|
||||||
::SpannedValue::Instant(x) =>
|
crate::SpannedValue::BigInteger(ref x) => Some(PatternValuePlace::Constant(
|
||||||
Some(PatternValuePlace::Constant(NonIntegerConstant::Instant(x))),
|
NonIntegerConstant::BigInteger(x.clone()),
|
||||||
::SpannedValue::Text(ref x) =>
|
)),
|
||||||
|
crate::SpannedValue::Instant(x) => {
|
||||||
|
Some(PatternValuePlace::Constant(NonIntegerConstant::Instant(x)))
|
||||||
|
}
|
||||||
|
crate::SpannedValue::Text(ref x) =>
|
||||||
// TODO: intern strings. #398.
|
// TODO: intern strings. #398.
|
||||||
Some(PatternValuePlace::Constant(x.clone().into())),
|
{
|
||||||
::SpannedValue::Uuid(ref u) =>
|
Some(PatternValuePlace::Constant(x.clone().into()))
|
||||||
Some(PatternValuePlace::Constant(NonIntegerConstant::Uuid(u.clone()))),
|
}
|
||||||
|
crate::SpannedValue::Uuid(ref u) => {
|
||||||
|
Some(PatternValuePlace::Constant(NonIntegerConstant::Uuid(*u)))
|
||||||
|
}
|
||||||
|
|
||||||
// These don't appear in queries.
|
// These don't appear in queries.
|
||||||
::SpannedValue::Nil => None,
|
crate::SpannedValue::Nil => None,
|
||||||
::SpannedValue::NamespacedSymbol(_) => None,
|
crate::SpannedValue::NamespacedSymbol(_) => None,
|
||||||
::SpannedValue::Keyword(_) => None, // … yet.
|
crate::SpannedValue::Keyword(_) => None, // … yet.
|
||||||
::SpannedValue::Map(_) => None,
|
crate::SpannedValue::Map(_) => None,
|
||||||
::SpannedValue::List(_) => None,
|
crate::SpannedValue::List(_) => None,
|
||||||
::SpannedValue::Set(_) => None,
|
crate::SpannedValue::Set(_) => None,
|
||||||
::SpannedValue::Vector(_) => None,
|
crate::SpannedValue::Vector(_) => None,
|
||||||
|
crate::SpannedValue::Bytes(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -445,11 +422,13 @@ impl PatternValuePlace {
|
||||||
match self {
|
match self {
|
||||||
PatternValuePlace::Placeholder => Some(PatternNonValuePlace::Placeholder),
|
PatternValuePlace::Placeholder => Some(PatternNonValuePlace::Placeholder),
|
||||||
PatternValuePlace::Variable(x) => Some(PatternNonValuePlace::Variable(x)),
|
PatternValuePlace::Variable(x) => Some(PatternNonValuePlace::Variable(x)),
|
||||||
PatternValuePlace::EntidOrInteger(x) => if x >= 0 {
|
PatternValuePlace::EntidOrInteger(x) => {
|
||||||
|
if x >= 0 {
|
||||||
Some(PatternNonValuePlace::Entid(x))
|
Some(PatternNonValuePlace::Entid(x))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
}
|
||||||
|
}
|
||||||
PatternValuePlace::IdentOrKeyword(x) => Some(PatternNonValuePlace::Ident(x)),
|
PatternValuePlace::IdentOrKeyword(x) => Some(PatternNonValuePlace::Ident(x)),
|
||||||
PatternValuePlace::Constant(_) => None,
|
PatternValuePlace::Constant(_) => None,
|
||||||
}
|
}
|
||||||
|
@ -459,12 +438,16 @@ impl PatternValuePlace {
|
||||||
match *self {
|
match *self {
|
||||||
PatternValuePlace::Placeholder => Some(PatternNonValuePlace::Placeholder),
|
PatternValuePlace::Placeholder => Some(PatternNonValuePlace::Placeholder),
|
||||||
PatternValuePlace::Variable(ref x) => Some(PatternNonValuePlace::Variable(x.clone())),
|
PatternValuePlace::Variable(ref x) => Some(PatternNonValuePlace::Variable(x.clone())),
|
||||||
PatternValuePlace::EntidOrInteger(x) => if x >= 0 {
|
PatternValuePlace::EntidOrInteger(x) => {
|
||||||
|
if x >= 0 {
|
||||||
Some(PatternNonValuePlace::Entid(x))
|
Some(PatternNonValuePlace::Entid(x))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
}
|
||||||
PatternValuePlace::IdentOrKeyword(ref x) => Some(PatternNonValuePlace::Ident(x.clone())),
|
}
|
||||||
|
PatternValuePlace::IdentOrKeyword(ref x) => {
|
||||||
|
Some(PatternNonValuePlace::Ident(x.clone()))
|
||||||
|
}
|
||||||
PatternValuePlace::Constant(_) => None,
|
PatternValuePlace::Constant(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -510,19 +493,15 @@ pub enum PullAttributeSpec {
|
||||||
impl std::fmt::Display for PullConcreteAttribute {
|
impl std::fmt::Display for PullConcreteAttribute {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
&PullConcreteAttribute::Ident(ref k) => {
|
PullConcreteAttribute::Ident(ref k) => write!(f, "{}", k),
|
||||||
write!(f, "{}", k)
|
PullConcreteAttribute::Entid(i) => write!(f, "{}", i),
|
||||||
},
|
|
||||||
&PullConcreteAttribute::Entid(i) => {
|
|
||||||
write!(f, "{}", i)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for NamedPullAttribute {
|
impl std::fmt::Display for NamedPullAttribute {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
if let &Some(ref alias) = &self.alias {
|
if let Some(ref alias) = self.alias {
|
||||||
write!(f, "{} :as {}", self.attribute, alias)
|
write!(f, "{} :as {}", self.attribute, alias)
|
||||||
} else {
|
} else {
|
||||||
write!(f, "{}", self.attribute)
|
write!(f, "{}", self.attribute)
|
||||||
|
@ -530,21 +509,15 @@ impl std::fmt::Display for NamedPullAttribute {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl std::fmt::Display for PullAttributeSpec {
|
impl std::fmt::Display for PullAttributeSpec {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
&PullAttributeSpec::Wildcard => {
|
PullAttributeSpec::Wildcard => write!(f, "*"),
|
||||||
write!(f, "*")
|
PullAttributeSpec::Attribute(ref attr) => write!(f, "{}", attr),
|
||||||
},
|
|
||||||
&PullAttributeSpec::Attribute(ref attr) => {
|
|
||||||
write!(f, "{}", attr)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct Pull {
|
pub struct Pull {
|
||||||
pub var: Variable,
|
pub var: Variable,
|
||||||
|
@ -575,10 +548,10 @@ impl Element {
|
||||||
/// Returns true if the element must yield only one value.
|
/// Returns true if the element must yield only one value.
|
||||||
pub fn is_unit(&self) -> bool {
|
pub fn is_unit(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
&Element::Variable(_) => false,
|
Element::Variable(_) => false,
|
||||||
&Element::Pull(_) => false,
|
Element::Pull(_) => false,
|
||||||
&Element::Aggregate(_) => true,
|
Element::Aggregate(_) => true,
|
||||||
&Element::Corresponding(_) => true,
|
Element::Corresponding(_) => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -592,26 +565,23 @@ impl From<Variable> for Element {
|
||||||
impl std::fmt::Display for Element {
|
impl std::fmt::Display for Element {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
&Element::Variable(ref var) => {
|
Element::Variable(ref var) => write!(f, "{}", var),
|
||||||
write!(f, "{}", var)
|
Element::Pull(Pull {
|
||||||
},
|
ref var,
|
||||||
&Element::Pull(Pull { ref var, ref patterns }) => {
|
ref patterns,
|
||||||
|
}) => {
|
||||||
write!(f, "(pull {} [ ", var)?;
|
write!(f, "(pull {} [ ", var)?;
|
||||||
for p in patterns.iter() {
|
for p in patterns.iter() {
|
||||||
write!(f, "{} ", p)?;
|
write!(f, "{} ", p)?;
|
||||||
}
|
}
|
||||||
write!(f, "])")
|
write!(f, "])")
|
||||||
},
|
}
|
||||||
&Element::Aggregate(ref agg) => {
|
Element::Aggregate(ref agg) => match agg.args.len() {
|
||||||
match agg.args.len() {
|
|
||||||
0 => write!(f, "({})", agg.func),
|
0 => write!(f, "({})", agg.func),
|
||||||
1 => write!(f, "({} {})", agg.func, agg.args[0]),
|
1 => write!(f, "({} {})", agg.func, agg.args[0]),
|
||||||
_ => write!(f, "({} {:?})", agg.func, agg.args),
|
_ => write!(f, "({} {:?})", agg.func, agg.args),
|
||||||
}
|
|
||||||
},
|
|
||||||
&Element::Corresponding(ref var) => {
|
|
||||||
write!(f, "(the {})", var)
|
|
||||||
},
|
},
|
||||||
|
Element::Corresponding(ref var) => write!(f, "(the {})", var),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -634,9 +604,6 @@ pub enum Limit {
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use edn::query::{Element, FindSpec, Variable};
|
/// # use edn::query::{Element, FindSpec, Variable};
|
||||||
///
|
|
||||||
/// # fn main() {
|
|
||||||
///
|
|
||||||
/// let elements = vec![
|
/// let elements = vec![
|
||||||
/// Element::Variable(Variable::from_valid_name("?foo")),
|
/// Element::Variable(Variable::from_valid_name("?foo")),
|
||||||
/// Element::Variable(Variable::from_valid_name("?bar")),
|
/// Element::Variable(Variable::from_valid_name("?bar")),
|
||||||
|
@ -646,8 +613,6 @@ pub enum Limit {
|
||||||
/// if let FindSpec::FindRel(elements) = rel {
|
/// if let FindSpec::FindRel(elements) = rel {
|
||||||
/// assert_eq!(2, elements.len());
|
/// assert_eq!(2, elements.len());
|
||||||
/// }
|
/// }
|
||||||
///
|
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
@ -674,23 +639,22 @@ impl FindSpec {
|
||||||
pub fn is_unit_limited(&self) -> bool {
|
pub fn is_unit_limited(&self) -> bool {
|
||||||
use self::FindSpec::*;
|
use self::FindSpec::*;
|
||||||
match self {
|
match self {
|
||||||
&FindScalar(..) => true,
|
FindScalar(..) => true,
|
||||||
&FindTuple(..) => true,
|
FindTuple(..) => true,
|
||||||
&FindRel(..) => false,
|
FindRel(..) => false,
|
||||||
&FindColl(..) => false,
|
FindColl(..) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expected_column_count(&self) -> usize {
|
pub fn expected_column_count(&self) -> usize {
|
||||||
use self::FindSpec::*;
|
use self::FindSpec::*;
|
||||||
match self {
|
match self {
|
||||||
&FindScalar(..) => 1,
|
FindScalar(..) => 1,
|
||||||
&FindColl(..) => 1,
|
FindColl(..) => 1,
|
||||||
&FindTuple(ref elems) | &FindRel(ref elems) => elems.len(),
|
FindTuple(ref elems) | &FindRel(ref elems) => elems.len(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Returns true if the provided `FindSpec` cares about distinct results.
|
/// Returns true if the provided `FindSpec` cares about distinct results.
|
||||||
///
|
///
|
||||||
/// I use the words "cares about" because find is generally defined in terms of producing distinct
|
/// I use the words "cares about" because find is generally defined in terms of producing distinct
|
||||||
|
@ -713,13 +677,13 @@ impl FindSpec {
|
||||||
!self.is_unit_limited()
|
!self.is_unit_limited()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn columns<'s>(&'s self) -> Box<Iterator<Item=&Element> + 's> {
|
pub fn columns<'s>(&'s self) -> Box<dyn Iterator<Item = &Element> + 's> {
|
||||||
use self::FindSpec::*;
|
use self::FindSpec::*;
|
||||||
match self {
|
match self {
|
||||||
&FindScalar(ref e) => Box::new(std::iter::once(e)),
|
FindScalar(ref e) => Box::new(std::iter::once(e)),
|
||||||
&FindColl(ref e) => Box::new(std::iter::once(e)),
|
FindColl(ref e) => Box::new(std::iter::once(e)),
|
||||||
&FindTuple(ref v) => Box::new(v.iter()),
|
FindTuple(ref v) => Box::new(v.iter()),
|
||||||
&FindRel(ref v) => Box::new(v.iter()),
|
FindRel(ref v) => Box::new(v.iter()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -742,8 +706,8 @@ impl VariableOrPlaceholder {
|
||||||
|
|
||||||
pub fn var(&self) -> Option<&Variable> {
|
pub fn var(&self) -> Option<&Variable> {
|
||||||
match self {
|
match self {
|
||||||
&VariableOrPlaceholder::Placeholder => None,
|
VariableOrPlaceholder::Placeholder => None,
|
||||||
&VariableOrPlaceholder::Variable(ref var) => Some(var),
|
VariableOrPlaceholder::Variable(ref var) => Some(var),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -761,7 +725,9 @@ impl Binding {
|
||||||
pub fn variables(&self) -> Vec<Option<Variable>> {
|
pub fn variables(&self) -> Vec<Option<Variable>> {
|
||||||
match self {
|
match self {
|
||||||
&Binding::BindScalar(ref var) | &Binding::BindColl(ref var) => vec![Some(var.clone())],
|
&Binding::BindScalar(ref var) | &Binding::BindColl(ref var) => vec![Some(var.clone())],
|
||||||
&Binding::BindRel(ref vars) | &Binding::BindTuple(ref vars) => vars.iter().map(|x| x.var().cloned()).collect(),
|
&Binding::BindRel(ref vars) | &Binding::BindTuple(ref vars) => {
|
||||||
|
vars.iter().map(|x| x.var().cloned()).collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -769,7 +735,9 @@ impl Binding {
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
&Binding::BindScalar(_) | &Binding::BindColl(_) => false,
|
&Binding::BindScalar(_) | &Binding::BindColl(_) => false,
|
||||||
&Binding::BindRel(ref vars) | &Binding::BindTuple(ref vars) => vars.iter().all(|x| x.var().is_none()),
|
&Binding::BindRel(ref vars) | &Binding::BindTuple(ref vars) => {
|
||||||
|
vars.iter().all(|x| x.var().is_none())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -793,11 +761,11 @@ impl Binding {
|
||||||
/// ```
|
/// ```
|
||||||
pub fn is_valid(&self) -> bool {
|
pub fn is_valid(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
&Binding::BindScalar(_) | &Binding::BindColl(_) => true,
|
Binding::BindScalar(_) | &Binding::BindColl(_) => true,
|
||||||
&Binding::BindRel(ref vars) | &Binding::BindTuple(ref vars) => {
|
Binding::BindRel(ref vars) | &Binding::BindTuple(ref vars) => {
|
||||||
let mut acc = HashSet::<Variable>::new();
|
let mut acc = HashSet::<Variable>::new();
|
||||||
for var in vars {
|
for var in vars {
|
||||||
if let &VariableOrPlaceholder::Variable(ref var) = var {
|
if let VariableOrPlaceholder::Variable(ref var) = *var {
|
||||||
if !acc.insert(var.clone()) {
|
if !acc.insert(var.clone()) {
|
||||||
// It's invalid if there was an equal var already present in the set --
|
// It's invalid if there was an equal var already present in the set --
|
||||||
// i.e., we have a duplicate var.
|
// i.e., we have a duplicate var.
|
||||||
|
@ -826,17 +794,21 @@ pub struct Pattern {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pattern {
|
impl Pattern {
|
||||||
pub fn simple(e: PatternNonValuePlace,
|
pub fn simple(
|
||||||
a: PatternNonValuePlace,
|
|
||||||
v: PatternValuePlace) -> Option<Pattern> {
|
|
||||||
Pattern::new(None, e, a, v, PatternNonValuePlace::Placeholder)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(src: Option<SrcVar>,
|
|
||||||
e: PatternNonValuePlace,
|
e: PatternNonValuePlace,
|
||||||
a: PatternNonValuePlace,
|
a: PatternNonValuePlace,
|
||||||
v: PatternValuePlace,
|
v: PatternValuePlace,
|
||||||
tx: PatternNonValuePlace) -> Option<Pattern> {
|
) -> Option<Pattern> {
|
||||||
|
Pattern::new(None, e, a, v, PatternNonValuePlace::Placeholder)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
src: Option<SrcVar>,
|
||||||
|
e: PatternNonValuePlace,
|
||||||
|
a: PatternNonValuePlace,
|
||||||
|
v: PatternValuePlace,
|
||||||
|
tx: PatternNonValuePlace,
|
||||||
|
) -> Option<Pattern> {
|
||||||
let aa = a.clone(); // Too tired of fighting borrow scope for now.
|
let aa = a.clone(); // Too tired of fighting borrow scope for now.
|
||||||
if let PatternNonValuePlace::Ident(ref k) = aa {
|
if let PatternNonValuePlace::Ident(ref k) = aa {
|
||||||
if k.is_backward() {
|
if k.is_backward() {
|
||||||
|
@ -850,7 +822,7 @@ impl Pattern {
|
||||||
entity: v_e,
|
entity: v_e,
|
||||||
attribute: k.to_reversed().into(),
|
attribute: k.to_reversed().into(),
|
||||||
value: e_v,
|
value: e_v,
|
||||||
tx: tx,
|
tx,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return None;
|
return None;
|
||||||
|
@ -862,7 +834,7 @@ impl Pattern {
|
||||||
entity: e,
|
entity: e,
|
||||||
attribute: a,
|
attribute: a,
|
||||||
value: v,
|
value: v,
|
||||||
tx: tx,
|
tx,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -911,10 +883,7 @@ pub enum UnifyVars {
|
||||||
|
|
||||||
impl WhereClause {
|
impl WhereClause {
|
||||||
pub fn is_pattern(&self) -> bool {
|
pub fn is_pattern(&self) -> bool {
|
||||||
match self {
|
matches!(self, WhereClause::Pattern(_))
|
||||||
&WhereClause::Pattern(_) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -927,8 +896,8 @@ pub enum OrWhereClause {
|
||||||
impl OrWhereClause {
|
impl OrWhereClause {
|
||||||
pub fn is_pattern_or_patterns(&self) -> bool {
|
pub fn is_pattern_or_patterns(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
&OrWhereClause::Clause(WhereClause::Pattern(_)) => true,
|
OrWhereClause::Clause(WhereClause::Pattern(_)) => true,
|
||||||
&OrWhereClause::And(ref clauses) => clauses.iter().all(|clause| clause.is_pattern()),
|
OrWhereClause::And(ref clauses) => clauses.iter().all(|clause| clause.is_pattern()),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -952,8 +921,8 @@ pub struct NotJoin {
|
||||||
impl NotJoin {
|
impl NotJoin {
|
||||||
pub fn new(unify_vars: UnifyVars, clauses: Vec<WhereClause>) -> NotJoin {
|
pub fn new(unify_vars: UnifyVars, clauses: Vec<WhereClause>) -> NotJoin {
|
||||||
NotJoin {
|
NotJoin {
|
||||||
unify_vars: unify_vars,
|
unify_vars,
|
||||||
clauses: clauses,
|
clauses,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1005,7 +974,9 @@ pub(crate) enum QueryPart {
|
||||||
/// We split `ParsedQuery` from `FindQuery` because it's not easy to generalize over containers
|
/// We split `ParsedQuery` from `FindQuery` because it's not easy to generalize over containers
|
||||||
/// (here, `Vec` and `BTreeSet`) in Rust.
|
/// (here, `Vec` and `BTreeSet`) in Rust.
|
||||||
impl ParsedQuery {
|
impl ParsedQuery {
|
||||||
pub(crate) fn from_parts(parts: Vec<QueryPart>) -> std::result::Result<ParsedQuery, &'static str> {
|
pub(crate) fn from_parts(
|
||||||
|
parts: Vec<QueryPart>,
|
||||||
|
) -> std::result::Result<ParsedQuery, &'static str> {
|
||||||
let mut find_spec: Option<FindSpec> = None;
|
let mut find_spec: Option<FindSpec> = None;
|
||||||
let mut with: Option<Vec<Variable>> = None;
|
let mut with: Option<Vec<Variable>> = None;
|
||||||
let mut in_vars: Option<Vec<Variable>> = None;
|
let mut in_vars: Option<Vec<Variable>> = None;
|
||||||
|
@ -1020,45 +991,45 @@ impl ParsedQuery {
|
||||||
return Err("find query has repeated :find");
|
return Err("find query has repeated :find");
|
||||||
}
|
}
|
||||||
find_spec = Some(x)
|
find_spec = Some(x)
|
||||||
},
|
}
|
||||||
QueryPart::WithVars(x) => {
|
QueryPart::WithVars(x) => {
|
||||||
if with.is_some() {
|
if with.is_some() {
|
||||||
return Err("find query has repeated :with");
|
return Err("find query has repeated :with");
|
||||||
}
|
}
|
||||||
with = Some(x)
|
with = Some(x)
|
||||||
},
|
}
|
||||||
QueryPart::InVars(x) => {
|
QueryPart::InVars(x) => {
|
||||||
if in_vars.is_some() {
|
if in_vars.is_some() {
|
||||||
return Err("find query has repeated :in");
|
return Err("find query has repeated :in");
|
||||||
}
|
}
|
||||||
in_vars = Some(x)
|
in_vars = Some(x)
|
||||||
},
|
}
|
||||||
QueryPart::Limit(x) => {
|
QueryPart::Limit(x) => {
|
||||||
if limit.is_some() {
|
if limit.is_some() {
|
||||||
return Err("find query has repeated :limit");
|
return Err("find query has repeated :limit");
|
||||||
}
|
}
|
||||||
limit = Some(x)
|
limit = Some(x)
|
||||||
},
|
}
|
||||||
QueryPart::WhereClauses(x) => {
|
QueryPart::WhereClauses(x) => {
|
||||||
if where_clauses.is_some() {
|
if where_clauses.is_some() {
|
||||||
return Err("find query has repeated :where");
|
return Err("find query has repeated :where");
|
||||||
}
|
}
|
||||||
where_clauses = Some(x)
|
where_clauses = Some(x)
|
||||||
},
|
}
|
||||||
QueryPart::Order(x) => {
|
QueryPart::Order(x) => {
|
||||||
if order.is_some() {
|
if order.is_some() {
|
||||||
return Err("find query has repeated :order");
|
return Err("find query has repeated :order");
|
||||||
}
|
}
|
||||||
order = Some(x)
|
order = Some(x)
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ParsedQuery {
|
Ok(ParsedQuery {
|
||||||
find_spec: find_spec.ok_or("expected :find")?,
|
find_spec: find_spec.ok_or("expected :find")?,
|
||||||
default_source: SrcVar::DefaultSrc,
|
default_source: SrcVar::DefaultSrc,
|
||||||
with: with.unwrap_or(vec![]),
|
with: with.unwrap_or_default(),
|
||||||
in_vars: in_vars.unwrap_or(vec![]),
|
in_vars: in_vars.unwrap_or_default(),
|
||||||
in_sources: BTreeSet::default(),
|
in_sources: BTreeSet::default(),
|
||||||
limit: limit.unwrap_or(Limit::None),
|
limit: limit.unwrap_or(Limit::None),
|
||||||
where_clauses: where_clauses.ok_or("expected :where")?,
|
where_clauses: where_clauses.ok_or("expected :where")?,
|
||||||
|
@ -1070,8 +1041,8 @@ impl ParsedQuery {
|
||||||
impl OrJoin {
|
impl OrJoin {
|
||||||
pub fn new(unify_vars: UnifyVars, clauses: Vec<OrWhereClause>) -> OrJoin {
|
pub fn new(unify_vars: UnifyVars, clauses: Vec<OrWhereClause>) -> OrJoin {
|
||||||
OrJoin {
|
OrJoin {
|
||||||
unify_vars: unify_vars,
|
unify_vars,
|
||||||
clauses: clauses,
|
clauses,
|
||||||
mentioned_vars: None,
|
mentioned_vars: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1080,8 +1051,8 @@ impl OrJoin {
|
||||||
/// every variable mentioned inside the join is also mentioned in the `UnifyVars` list.
|
/// every variable mentioned inside the join is also mentioned in the `UnifyVars` list.
|
||||||
pub fn is_fully_unified(&self) -> bool {
|
pub fn is_fully_unified(&self) -> bool {
|
||||||
match &self.unify_vars {
|
match &self.unify_vars {
|
||||||
&UnifyVars::Implicit => true,
|
UnifyVars::Implicit => true,
|
||||||
&UnifyVars::Explicit(ref vars) => {
|
UnifyVars::Explicit(ref vars) => {
|
||||||
// We know that the join list must be a subset of the vars in the pattern, or
|
// We know that the join list must be a subset of the vars in the pattern, or
|
||||||
// it would have failed validation. That allows us to simply compare counts here.
|
// it would have failed validation. That allows us to simply compare counts here.
|
||||||
// TODO: in debug mode, do a full intersection, and verify that our count check
|
// TODO: in debug mode, do a full intersection, and verify that our count check
|
||||||
|
@ -1110,13 +1081,13 @@ impl ContainsVariables for WhereClause {
|
||||||
fn accumulate_mentioned_variables(&self, acc: &mut BTreeSet<Variable>) {
|
fn accumulate_mentioned_variables(&self, acc: &mut BTreeSet<Variable>) {
|
||||||
use self::WhereClause::*;
|
use self::WhereClause::*;
|
||||||
match self {
|
match self {
|
||||||
&OrJoin(ref o) => o.accumulate_mentioned_variables(acc),
|
OrJoin(ref o) => o.accumulate_mentioned_variables(acc),
|
||||||
&Pred(ref p) => p.accumulate_mentioned_variables(acc),
|
Pred(ref p) => p.accumulate_mentioned_variables(acc),
|
||||||
&Pattern(ref p) => p.accumulate_mentioned_variables(acc),
|
Pattern(ref p) => p.accumulate_mentioned_variables(acc),
|
||||||
&NotJoin(ref n) => n.accumulate_mentioned_variables(acc),
|
NotJoin(ref n) => n.accumulate_mentioned_variables(acc),
|
||||||
&WhereFn(ref f) => f.accumulate_mentioned_variables(acc),
|
WhereFn(ref f) => f.accumulate_mentioned_variables(acc),
|
||||||
&TypeAnnotation(ref a) => a.accumulate_mentioned_variables(acc),
|
TypeAnnotation(ref a) => a.accumulate_mentioned_variables(acc),
|
||||||
&RuleExpr => (),
|
RuleExpr => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1125,8 +1096,12 @@ impl ContainsVariables for OrWhereClause {
|
||||||
fn accumulate_mentioned_variables(&self, acc: &mut BTreeSet<Variable>) {
|
fn accumulate_mentioned_variables(&self, acc: &mut BTreeSet<Variable>) {
|
||||||
use self::OrWhereClause::*;
|
use self::OrWhereClause::*;
|
||||||
match self {
|
match self {
|
||||||
&And(ref clauses) => for clause in clauses { clause.accumulate_mentioned_variables(acc) },
|
And(ref clauses) => {
|
||||||
&Clause(ref clause) => clause.accumulate_mentioned_variables(acc),
|
for clause in clauses {
|
||||||
|
clause.accumulate_mentioned_variables(acc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Clause(ref clause) => clause.accumulate_mentioned_variables(acc),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1173,7 +1148,7 @@ impl ContainsVariables for NotJoin {
|
||||||
impl ContainsVariables for Predicate {
|
impl ContainsVariables for Predicate {
|
||||||
fn accumulate_mentioned_variables(&self, acc: &mut BTreeSet<Variable>) {
|
fn accumulate_mentioned_variables(&self, acc: &mut BTreeSet<Variable>) {
|
||||||
for arg in &self.args {
|
for arg in &self.args {
|
||||||
if let &FnArg::Variable(ref v) = arg {
|
if let FnArg::Variable(ref v) = *arg {
|
||||||
acc_ref(acc, v)
|
acc_ref(acc, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1189,16 +1164,14 @@ impl ContainsVariables for TypeAnnotation {
|
||||||
impl ContainsVariables for Binding {
|
impl ContainsVariables for Binding {
|
||||||
fn accumulate_mentioned_variables(&self, acc: &mut BTreeSet<Variable>) {
|
fn accumulate_mentioned_variables(&self, acc: &mut BTreeSet<Variable>) {
|
||||||
match self {
|
match self {
|
||||||
&Binding::BindScalar(ref v) | &Binding::BindColl(ref v) => {
|
Binding::BindScalar(ref v) | &Binding::BindColl(ref v) => acc_ref(acc, v),
|
||||||
acc_ref(acc, v)
|
Binding::BindRel(ref vs) | &Binding::BindTuple(ref vs) => {
|
||||||
},
|
|
||||||
&Binding::BindRel(ref vs) | &Binding::BindTuple(ref vs) => {
|
|
||||||
for v in vs {
|
for v in vs {
|
||||||
if let &VariableOrPlaceholder::Variable(ref v) = v {
|
if let VariableOrPlaceholder::Variable(ref v) = *v {
|
||||||
acc_ref(acc, v);
|
acc_ref(acc, v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1206,7 +1179,7 @@ impl ContainsVariables for Binding {
|
||||||
impl ContainsVariables for WhereFn {
|
impl ContainsVariables for WhereFn {
|
||||||
fn accumulate_mentioned_variables(&self, acc: &mut BTreeSet<Variable>) {
|
fn accumulate_mentioned_variables(&self, acc: &mut BTreeSet<Variable>) {
|
||||||
for arg in &self.args {
|
for arg in &self.args {
|
||||||
if let &FnArg::Variable(ref v) = arg {
|
if let FnArg::Variable(ref v) = *arg {
|
||||||
acc_ref(acc, v)
|
acc_ref(acc, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,19 +8,15 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
use std::fmt::{
|
use std::fmt::{Display, Formatter, Write};
|
||||||
Display,
|
|
||||||
Formatter,
|
|
||||||
Write,
|
|
||||||
};
|
|
||||||
|
|
||||||
use namespaceable_name::NamespaceableName;
|
use crate::namespaceable_name::NamespaceableName;
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! ns_keyword {
|
macro_rules! ns_keyword {
|
||||||
($ns: expr, $name: expr) => {{
|
($ns: expr, $name: expr) => {{
|
||||||
$crate::Keyword::namespaced($ns, $name)
|
$crate::Keyword::namespaced($ns, $name)
|
||||||
}}
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A simplification of Clojure's Symbol.
|
/// A simplification of Clojure's Symbol.
|
||||||
|
@ -72,7 +68,10 @@ pub struct NamespacedSymbol(NamespaceableName);
|
||||||
pub struct Keyword(NamespaceableName);
|
pub struct Keyword(NamespaceableName);
|
||||||
|
|
||||||
impl PlainSymbol {
|
impl PlainSymbol {
|
||||||
pub fn plain<T>(name: T) -> Self where T: Into<String> {
|
pub fn plain<T>(name: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
let n = name.into();
|
let n = name.into();
|
||||||
assert!(!n.is_empty(), "Symbols cannot be unnamed.");
|
assert!(!n.is_empty(), "Symbols cannot be unnamed.");
|
||||||
|
|
||||||
|
@ -107,9 +106,16 @@ impl PlainSymbol {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NamespacedSymbol {
|
impl NamespacedSymbol {
|
||||||
pub fn namespaced<N, T>(namespace: N, name: T) -> Self where N: AsRef<str>, T: AsRef<str> {
|
pub fn namespaced<N, T>(namespace: N, name: T) -> Self
|
||||||
|
where
|
||||||
|
N: AsRef<str>,
|
||||||
|
T: AsRef<str>,
|
||||||
|
{
|
||||||
let r = namespace.as_ref();
|
let r = namespace.as_ref();
|
||||||
assert!(!r.is_empty(), "Namespaced symbols cannot have an empty non-null namespace.");
|
assert!(
|
||||||
|
!r.is_empty(),
|
||||||
|
"Namespaced symbols cannot have an empty non-null namespace."
|
||||||
|
);
|
||||||
NamespacedSymbol(NamespaceableName::namespaced(r, name))
|
NamespacedSymbol(NamespaceableName::namespaced(r, name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,13 +130,16 @@ impl NamespacedSymbol {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn components<'a>(&'a self) -> (&'a str, &'a str) {
|
pub fn components(&self) -> (&str, &str) {
|
||||||
self.0.components()
|
self.0.components()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Keyword {
|
impl Keyword {
|
||||||
pub fn plain<T>(name: T) -> Self where T: Into<String> {
|
pub fn plain<T>(name: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
Keyword(NamespaceableName::plain(name))
|
Keyword(NamespaceableName::plain(name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,9 +156,16 @@ impl Keyword {
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// See also the `kw!` macro in the main `mentat` crate.
|
/// See also the `kw!` macro in the main `mentat` crate.
|
||||||
pub fn namespaced<N, T>(namespace: N, name: T) -> Self where N: AsRef<str>, T: AsRef<str> {
|
pub fn namespaced<N, T>(namespace: N, name: T) -> Self
|
||||||
|
where
|
||||||
|
N: AsRef<str>,
|
||||||
|
T: AsRef<str>,
|
||||||
|
{
|
||||||
let r = namespace.as_ref();
|
let r = namespace.as_ref();
|
||||||
assert!(!r.is_empty(), "Namespaced keywords cannot have an empty non-null namespace.");
|
assert!(
|
||||||
|
!r.is_empty(),
|
||||||
|
"Namespaced keywords cannot have an empty non-null namespace."
|
||||||
|
);
|
||||||
Keyword(NamespaceableName::namespaced(r, name))
|
Keyword(NamespaceableName::namespaced(r, name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,7 +180,7 @@ impl Keyword {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn components<'a>(&'a self) -> (&'a str, &'a str) {
|
pub fn components(&self) -> (&str, &str) {
|
||||||
self.0.components()
|
self.0.components()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,8 +323,12 @@ impl Display for Keyword {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ns_keyword_macro() {
|
fn test_ns_keyword_macro() {
|
||||||
assert_eq!(ns_keyword!("test", "name").to_string(),
|
assert_eq!(
|
||||||
Keyword::namespaced("test", "name").to_string());
|
ns_keyword!("test", "name").to_string(),
|
||||||
assert_eq!(ns_keyword!("ns", "_name").to_string(),
|
Keyword::namespaced("test", "name").to_string()
|
||||||
Keyword::namespaced("ns", "_name").to_string());
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ns_keyword!("ns", "_name").to_string(),
|
||||||
|
Keyword::namespaced("ns", "_name").to_string()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
205
edn/src/types.rs
205
edn/src/types.rs
|
@ -8,12 +8,12 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
#![cfg_attr(feature = "cargo-clippy", allow(linkedlist))]
|
#![allow(redundant_semicolons)]
|
||||||
|
|
||||||
use std::collections::{BTreeSet, BTreeMap, LinkedList};
|
use std::cmp::{Ord, Ordering, PartialOrd};
|
||||||
use std::cmp::{Ordering, Ord, PartialOrd};
|
use std::collections::{BTreeMap, BTreeSet, LinkedList};
|
||||||
use std::fmt::{Display, Formatter};
|
|
||||||
use std::f64;
|
use std::f64;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
use chrono::{
|
use chrono::{
|
||||||
DateTime,
|
DateTime,
|
||||||
|
@ -25,8 +25,10 @@ use num::BigInt;
|
||||||
use ordered_float::OrderedFloat;
|
use ordered_float::OrderedFloat;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use symbols;
|
use crate::symbols;
|
||||||
|
|
||||||
|
use bytes::Bytes;
|
||||||
|
use hex::encode;
|
||||||
/// Value represents one of the allowed values in an EDN string.
|
/// Value represents one of the allowed values in an EDN string.
|
||||||
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
|
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
|
@ -52,6 +54,7 @@ pub enum Value {
|
||||||
// See https://internals.rust-lang.org/t/implementing-hash-for-hashset-hashmap/3817/1
|
// See https://internals.rust-lang.org/t/implementing-hash-for-hashset-hashmap/3817/1
|
||||||
Set(BTreeSet<Value>),
|
Set(BTreeSet<Value>),
|
||||||
Map(BTreeMap<Value, Value>),
|
Map(BTreeMap<Value, Value>),
|
||||||
|
Bytes(Bytes),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `SpannedValue` is the parallel to `Value` but used in `ValueAndSpan`.
|
/// `SpannedValue` is the parallel to `Value` but used in `ValueAndSpan`.
|
||||||
|
@ -73,6 +76,7 @@ pub enum SpannedValue {
|
||||||
List(LinkedList<ValueAndSpan>),
|
List(LinkedList<ValueAndSpan>),
|
||||||
Set(BTreeSet<ValueAndSpan>),
|
Set(BTreeSet<ValueAndSpan>),
|
||||||
Map(BTreeMap<ValueAndSpan, ValueAndSpan>),
|
Map(BTreeMap<ValueAndSpan, ValueAndSpan>),
|
||||||
|
Bytes(Bytes),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Span represents the current offset (start, end) into the input string.
|
/// Span represents the current offset (start, end) into the input string.
|
||||||
|
@ -94,7 +98,10 @@ pub struct ValueAndSpan {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ValueAndSpan {
|
impl ValueAndSpan {
|
||||||
pub fn new<I>(spanned_value: SpannedValue, span: I) -> ValueAndSpan where I: Into<Option<Span>> {
|
pub fn new<I>(spanned_value: SpannedValue, span: I) -> ValueAndSpan
|
||||||
|
where
|
||||||
|
I: Into<Option<Span>>,
|
||||||
|
{
|
||||||
ValueAndSpan {
|
ValueAndSpan {
|
||||||
inner: spanned_value,
|
inner: spanned_value,
|
||||||
span: span.into().unwrap_or(Span(0, 0)), // TODO: consider if this has implications.
|
span: span.into().unwrap_or(Span(0, 0)), // TODO: consider if this has implications.
|
||||||
|
@ -136,7 +143,7 @@ impl Value {
|
||||||
/// But right now, it's used in the bootstrapper. We'll fix that soon.
|
/// But right now, it's used in the bootstrapper. We'll fix that soon.
|
||||||
pub fn with_spans(self) -> ValueAndSpan {
|
pub fn with_spans(self) -> ValueAndSpan {
|
||||||
let s = self.to_pretty(120).unwrap();
|
let s = self.to_pretty(120).unwrap();
|
||||||
use ::parse;
|
use crate::parse;
|
||||||
let with_spans = parse::value(&s).unwrap();
|
let with_spans = parse::value(&s).unwrap();
|
||||||
assert_eq!(self, with_spans.clone().without_spans());
|
assert_eq!(self, with_spans.clone().without_spans());
|
||||||
with_spans
|
with_spans
|
||||||
|
@ -157,10 +164,19 @@ impl From<SpannedValue> for Value {
|
||||||
SpannedValue::PlainSymbol(v) => Value::PlainSymbol(v),
|
SpannedValue::PlainSymbol(v) => Value::PlainSymbol(v),
|
||||||
SpannedValue::NamespacedSymbol(v) => Value::NamespacedSymbol(v),
|
SpannedValue::NamespacedSymbol(v) => Value::NamespacedSymbol(v),
|
||||||
SpannedValue::Keyword(v) => Value::Keyword(v),
|
SpannedValue::Keyword(v) => Value::Keyword(v),
|
||||||
SpannedValue::Vector(v) => Value::Vector(v.into_iter().map(|x| x.without_spans()).collect()),
|
SpannedValue::Vector(v) => {
|
||||||
SpannedValue::List(v) => Value::List(v.into_iter().map(|x| x.without_spans()).collect()),
|
Value::Vector(v.into_iter().map(|x| x.without_spans()).collect())
|
||||||
|
}
|
||||||
|
SpannedValue::List(v) => {
|
||||||
|
Value::List(v.into_iter().map(|x| x.without_spans()).collect())
|
||||||
|
}
|
||||||
SpannedValue::Set(v) => Value::Set(v.into_iter().map(|x| x.without_spans()).collect()),
|
SpannedValue::Set(v) => Value::Set(v.into_iter().map(|x| x.without_spans()).collect()),
|
||||||
SpannedValue::Map(v) => Value::Map(v.into_iter().map(|(x, y)| (x.without_spans(), y.without_spans())).collect()),
|
SpannedValue::Map(v) => Value::Map(
|
||||||
|
v.into_iter()
|
||||||
|
.map(|(x, y)| (x.without_spans(), y.without_spans()))
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
SpannedValue::Bytes(b) => Value::Bytes(b),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -198,9 +214,9 @@ macro_rules! def_from_option {
|
||||||
macro_rules! def_is {
|
macro_rules! def_is {
|
||||||
($name: ident, $pat: pat) => {
|
($name: ident, $pat: pat) => {
|
||||||
pub fn $name(&self) -> bool {
|
pub fn $name(&self) -> bool {
|
||||||
match *self { $pat => true, _ => false }
|
matches!(*self, $pat)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates `as_$TYPE` helper functions for Value or SpannedValue, like
|
/// Creates `as_$TYPE` helper functions for Value or SpannedValue, like
|
||||||
|
@ -220,9 +236,12 @@ macro_rules! def_as {
|
||||||
macro_rules! def_as_ref {
|
macro_rules! def_as_ref {
|
||||||
($name: ident, $kind: path, $t: ty) => {
|
($name: ident, $kind: path, $t: ty) => {
|
||||||
pub fn $name(&self) -> Option<&$t> {
|
pub fn $name(&self) -> Option<&$t> {
|
||||||
match *self { $kind(ref v) => Some(v), _ => None }
|
match *self {
|
||||||
|
$kind(ref v) => Some(v),
|
||||||
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates `into_$TYPE` helper functions for Value or SpannedValue, like
|
/// Creates `into_$TYPE` helper functions for Value or SpannedValue, like
|
||||||
|
@ -261,8 +280,9 @@ macro_rules! to_symbol {
|
||||||
( $namespace:expr, $name:expr, $t:tt ) => {
|
( $namespace:expr, $name:expr, $t:tt ) => {
|
||||||
$namespace.into().map_or_else(
|
$namespace.into().map_or_else(
|
||||||
|| $t::PlainSymbol(symbols::PlainSymbol::plain($name)),
|
|| $t::PlainSymbol(symbols::PlainSymbol::plain($name)),
|
||||||
|ns| $t::NamespacedSymbol(symbols::NamespacedSymbol::namespaced(ns, $name)))
|
|ns| $t::NamespacedSymbol(symbols::NamespacedSymbol::namespaced(ns, $name)),
|
||||||
}
|
)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts `name` into a plain or namespaced value keyword, depending on
|
/// Converts `name` into a plain or namespaced value keyword, depending on
|
||||||
|
@ -290,8 +310,9 @@ macro_rules! to_keyword {
|
||||||
( $namespace:expr, $name:expr, $t:tt ) => {
|
( $namespace:expr, $name:expr, $t:tt ) => {
|
||||||
$namespace.into().map_or_else(
|
$namespace.into().map_or_else(
|
||||||
|| $t::Keyword(symbols::Keyword::plain($name)),
|
|| $t::Keyword(symbols::Keyword::plain($name)),
|
||||||
|ns| $t::Keyword(symbols::Keyword::namespaced(ns, $name)))
|
|ns| $t::Keyword(symbols::Keyword::namespaced(ns, $name)),
|
||||||
}
|
)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implements multiple is*, as*, into* and from* methods common to
|
/// Implements multiple is*, as*, into* and from* methods common to
|
||||||
|
@ -312,17 +333,18 @@ macro_rules! def_common_value_methods {
|
||||||
def_is!(is_list, $t::List(_));
|
def_is!(is_list, $t::List(_));
|
||||||
def_is!(is_set, $t::Set(_));
|
def_is!(is_set, $t::Set(_));
|
||||||
def_is!(is_map, $t::Map(_));
|
def_is!(is_map, $t::Map(_));
|
||||||
|
def_is!(is_bytes, $t::Bytes(_));
|
||||||
|
|
||||||
pub fn is_keyword(&self) -> bool {
|
pub fn is_keyword(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
&$t::Keyword(ref k) => !k.is_namespaced(),
|
$t::Keyword(ref k) => !k.is_namespaced(),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_namespaced_keyword(&self) -> bool {
|
pub fn is_namespaced_keyword(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
&$t::Keyword(ref k) => k.is_namespaced(),
|
$t::Keyword(ref k) => k.is_namespaced(),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -344,24 +366,25 @@ macro_rules! def_common_value_methods {
|
||||||
def_as_ref!(as_uuid, $t::Uuid, Uuid);
|
def_as_ref!(as_uuid, $t::Uuid, Uuid);
|
||||||
def_as_ref!(as_symbol, $t::PlainSymbol, symbols::PlainSymbol);
|
def_as_ref!(as_symbol, $t::PlainSymbol, symbols::PlainSymbol);
|
||||||
def_as_ref!(as_namespaced_symbol, $t::NamespacedSymbol, symbols::NamespacedSymbol);
|
def_as_ref!(as_namespaced_symbol, $t::NamespacedSymbol, symbols::NamespacedSymbol);
|
||||||
|
def_as_ref!(as_bytes, $t::Bytes, Bytes);
|
||||||
|
|
||||||
pub fn as_keyword(&self) -> Option<&symbols::Keyword> {
|
pub fn as_keyword(&self) -> Option<&symbols::Keyword> {
|
||||||
match self {
|
match self {
|
||||||
&$t::Keyword(ref k) => Some(k),
|
$t::Keyword(ref k) => Some(k),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_plain_keyword(&self) -> Option<&symbols::Keyword> {
|
pub fn as_plain_keyword(&self) -> Option<&symbols::Keyword> {
|
||||||
match self {
|
match self {
|
||||||
&$t::Keyword(ref k) if !k.is_namespaced() => Some(k),
|
$t::Keyword(ref k) if !k.is_namespaced() => Some(k),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_namespaced_keyword(&self) -> Option<&symbols::Keyword> {
|
pub fn as_namespaced_keyword(&self) -> Option<&symbols::Keyword> {
|
||||||
match self {
|
match self {
|
||||||
&$t::Keyword(ref k) if k.is_namespaced() => Some(k),
|
$t::Keyword(ref k) if k.is_namespaced() => Some(k),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -381,6 +404,7 @@ macro_rules! def_common_value_methods {
|
||||||
def_into!(into_uuid, $t::Uuid, Uuid,);
|
def_into!(into_uuid, $t::Uuid, Uuid,);
|
||||||
def_into!(into_symbol, $t::PlainSymbol, symbols::PlainSymbol,);
|
def_into!(into_symbol, $t::PlainSymbol, symbols::PlainSymbol,);
|
||||||
def_into!(into_namespaced_symbol, $t::NamespacedSymbol, symbols::NamespacedSymbol,);
|
def_into!(into_namespaced_symbol, $t::NamespacedSymbol, symbols::NamespacedSymbol,);
|
||||||
|
def_into!(into_bytes, $t::Bytes, Bytes,);
|
||||||
|
|
||||||
pub fn into_keyword(self) -> Option<symbols::Keyword> {
|
pub fn into_keyword(self) -> Option<symbols::Keyword> {
|
||||||
match self {
|
match self {
|
||||||
|
@ -451,6 +475,7 @@ macro_rules! def_common_value_methods {
|
||||||
$t::List(_) => 13,
|
$t::List(_) => 13,
|
||||||
$t::Set(_) => 14,
|
$t::Set(_) => 14,
|
||||||
$t::Map(_) => 15,
|
$t::Map(_) => 15,
|
||||||
|
$t::Bytes(_) => 16,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -471,6 +496,7 @@ macro_rules! def_common_value_methods {
|
||||||
$t::List(_) => true,
|
$t::List(_) => true,
|
||||||
$t::Set(_) => true,
|
$t::Set(_) => true,
|
||||||
$t::Map(_) => true,
|
$t::Map(_) => true,
|
||||||
|
$t::Bytes(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -508,9 +534,10 @@ macro_rules! def_common_value_ord {
|
||||||
(&$t::List(ref a), &$t::List(ref b)) => b.cmp(a),
|
(&$t::List(ref a), &$t::List(ref b)) => b.cmp(a),
|
||||||
(&$t::Set(ref a), &$t::Set(ref b)) => b.cmp(a),
|
(&$t::Set(ref a), &$t::Set(ref b)) => b.cmp(a),
|
||||||
(&$t::Map(ref a), &$t::Map(ref b)) => b.cmp(a),
|
(&$t::Map(ref a), &$t::Map(ref b)) => b.cmp(a),
|
||||||
_ => $value.precedence().cmp(&$other.precedence())
|
(&$t::Bytes(ref a), &$t::Bytes(ref b)) => b.cmp(a),
|
||||||
}
|
_ => $value.precedence().cmp(&$other.precedence()),
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a Value or SpannedValue to string, given a formatter.
|
/// Converts a Value or SpannedValue to string, given a formatter.
|
||||||
|
@ -522,7 +549,11 @@ macro_rules! def_common_value_display {
|
||||||
$t::Nil => write!($f, "nil"),
|
$t::Nil => write!($f, "nil"),
|
||||||
$t::Boolean(v) => write!($f, "{}", v),
|
$t::Boolean(v) => write!($f, "{}", v),
|
||||||
$t::Integer(v) => write!($f, "{}", v),
|
$t::Integer(v) => write!($f, "{}", v),
|
||||||
$t::Instant(v) => write!($f, "#inst \"{}\"", v.to_rfc3339_opts(SecondsFormat::AutoSi, true)),
|
$t::Instant(v) => write!(
|
||||||
|
$f,
|
||||||
|
"#inst \"{}\"",
|
||||||
|
v.to_rfc3339_opts(SecondsFormat::AutoSi, true)
|
||||||
|
),
|
||||||
$t::BigInteger(ref v) => write!($f, "{}N", v),
|
$t::BigInteger(ref v) => write!($f, "{}N", v),
|
||||||
// TODO: make sure float syntax is correct.
|
// TODO: make sure float syntax is correct.
|
||||||
$t::Float(ref v) => {
|
$t::Float(ref v) => {
|
||||||
|
@ -570,8 +601,12 @@ macro_rules! def_common_value_display {
|
||||||
}
|
}
|
||||||
write!($f, " }}")
|
write!($f, " }}")
|
||||||
}
|
}
|
||||||
|
$t::Bytes(ref v) => {
|
||||||
|
let s = encode(v);
|
||||||
|
write!($f, "#bytes {}", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! def_common_value_impl {
|
macro_rules! def_common_value_impl {
|
||||||
|
@ -597,7 +632,7 @@ macro_rules! def_common_value_impl {
|
||||||
def_common_value_display!($t, self, f)
|
def_common_value_display!($t, self, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
def_common_value_impl!(Value<Value>);
|
def_common_value_impl!(Value<Value>);
|
||||||
|
@ -633,7 +668,7 @@ pub trait FromMicros {
|
||||||
|
|
||||||
impl FromMicros for DateTime<Utc> {
|
impl FromMicros for DateTime<Utc> {
|
||||||
fn from_micros(ts: i64) -> Self {
|
fn from_micros(ts: i64) -> Self {
|
||||||
Utc.timestamp(ts / 1_000_000, ((ts % 1_000_000).abs() as u32) * 1_000)
|
Utc.timestamp_opt(ts / 1_000_000, ((ts % 1_000_000).unsigned_abs() as u32) * 1_000).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -655,7 +690,7 @@ pub trait FromMillis {
|
||||||
|
|
||||||
impl FromMillis for DateTime<Utc> {
|
impl FromMillis for DateTime<Utc> {
|
||||||
fn from_millis(ts: i64) -> Self {
|
fn from_millis(ts: i64) -> Self {
|
||||||
Utc.timestamp(ts / 1_000, ((ts % 1_000).abs() as u32) * 1_000)
|
Utc.timestamp_opt(ts / 1_000, ((ts % 1_000).unsigned_abs() as u32) * 1_000).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -674,22 +709,19 @@ impl ToMillis for DateTime<Utc> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
extern crate chrono;
|
extern crate chrono;
|
||||||
extern crate ordered_float;
|
|
||||||
extern crate num;
|
extern crate num;
|
||||||
|
extern crate ordered_float;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use std::collections::{BTreeSet, BTreeMap, LinkedList};
|
use std::cmp::Ordering;
|
||||||
use std::cmp::{Ordering};
|
use std::collections::{BTreeMap, BTreeSet, LinkedList};
|
||||||
use std::iter::FromIterator;
|
|
||||||
use std::f64;
|
use std::f64;
|
||||||
|
use std::iter::FromIterator;
|
||||||
|
|
||||||
use parse;
|
use crate::parse;
|
||||||
|
|
||||||
use chrono::{
|
use chrono::{DateTime, Utc};
|
||||||
DateTime,
|
|
||||||
Utc,
|
|
||||||
};
|
|
||||||
use num::BigInt;
|
use num::BigInt;
|
||||||
use ordered_float::OrderedFloat;
|
use ordered_float::OrderedFloat;
|
||||||
|
|
||||||
|
@ -702,29 +734,34 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_value_from() {
|
fn test_value_from() {
|
||||||
assert_eq!(Value::from_float(42f64), Value::Float(OrderedFloat::from(42f64)));
|
assert_eq!(
|
||||||
assert_eq!(Value::from_ordered_float(OrderedFloat::from(42f64)), Value::Float(OrderedFloat::from(42f64)));
|
Value::from_float(42f64),
|
||||||
assert_eq!(Value::from_bigint("42").unwrap(), Value::BigInteger(BigInt::from(42)));
|
Value::Float(OrderedFloat::from(42f64))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Value::from_ordered_float(OrderedFloat::from(42f64)),
|
||||||
|
Value::Float(OrderedFloat::from(42f64))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Value::from_bigint("42").unwrap(),
|
||||||
|
Value::BigInteger(BigInt::from(42))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_print_edn() {
|
fn test_print_edn() {
|
||||||
assert_eq!("1234N", Value::from_bigint("1234").unwrap().to_string());
|
assert_eq!("1234N", Value::from_bigint("1234").unwrap().to_string());
|
||||||
|
|
||||||
let string = "[ 1 2 ( 3.14 ) #{ 4N } { foo/bar 42 :baz/boz 43 } [ ] :five :six/seven eight nine/ten true false nil #f NaN #f -Infinity #f +Infinity ]";
|
let string = "[ 1 2 ( 7.14 ) #{ 4N } { foo/bar 42 :baz/boz 43 } [ ] :five :six/seven eight nine/ten true false nil #f NaN #f -Infinity #f +Infinity ]";
|
||||||
|
|
||||||
let data = Value::Vector(vec![
|
let data = Value::Vector(vec![
|
||||||
Value::Integer(1),
|
Value::Integer(1),
|
||||||
Value::Integer(2),
|
Value::Integer(2),
|
||||||
Value::List(LinkedList::from_iter(vec![
|
Value::List(LinkedList::from_iter(vec![Value::from_float(7.14)])),
|
||||||
Value::from_float(3.14)
|
Value::Set(BTreeSet::from_iter(vec![Value::from_bigint("4").unwrap()])),
|
||||||
])),
|
|
||||||
Value::Set(BTreeSet::from_iter(vec![
|
|
||||||
Value::from_bigint("4").unwrap()
|
|
||||||
])),
|
|
||||||
Value::Map(BTreeMap::from_iter(vec![
|
Value::Map(BTreeMap::from_iter(vec![
|
||||||
(Value::from_symbol("foo", "bar"), Value::Integer(42)),
|
(Value::from_symbol("foo", "bar"), Value::Integer(42)),
|
||||||
(Value::from_keyword("baz", "boz"), Value::Integer(43))
|
(Value::from_keyword("baz", "boz"), Value::Integer(43)),
|
||||||
])),
|
])),
|
||||||
Value::Vector(vec![]),
|
Value::Vector(vec![]),
|
||||||
Value::from_keyword(None, "five"),
|
Value::from_keyword(None, "five"),
|
||||||
|
@ -741,26 +778,68 @@ mod test {
|
||||||
|
|
||||||
assert_eq!(string, data.to_string());
|
assert_eq!(string, data.to_string());
|
||||||
assert_eq!(string, parse::value(&data.to_string()).unwrap().to_string());
|
assert_eq!(string, parse::value(&data.to_string()).unwrap().to_string());
|
||||||
assert_eq!(string, parse::value(&data.to_string()).unwrap().without_spans().to_string());
|
assert_eq!(
|
||||||
|
string,
|
||||||
|
parse::value(&data.to_string())
|
||||||
|
.unwrap()
|
||||||
|
.without_spans()
|
||||||
|
.to_string()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ord() {
|
fn test_ord() {
|
||||||
// TODO: Check we follow the equality rules at the bottom of https://github.com/edn-format/edn
|
// TODO: Check we follow the equality rules at the bottom of https://github.com/edn-format/edn
|
||||||
assert_eq!(Value::Nil.cmp(&Value::Nil), Ordering::Equal);
|
assert_eq!(Value::Nil.cmp(&Value::Nil), Ordering::Equal);
|
||||||
assert_eq!(Value::Boolean(false).cmp(&Value::Boolean(true)), Ordering::Greater);
|
assert_eq!(
|
||||||
|
Value::Boolean(false).cmp(&Value::Boolean(true)),
|
||||||
|
Ordering::Greater
|
||||||
|
);
|
||||||
assert_eq!(Value::Integer(1).cmp(&Value::Integer(2)), Ordering::Greater);
|
assert_eq!(Value::Integer(1).cmp(&Value::Integer(2)), Ordering::Greater);
|
||||||
assert_eq!(Value::from_bigint("1").cmp(&Value::from_bigint("2")), Ordering::Greater);
|
assert_eq!(
|
||||||
assert_eq!(Value::from_float(1f64).cmp(&Value::from_float(2f64)), Ordering::Greater);
|
Value::from_bigint("1").cmp(&Value::from_bigint("2")),
|
||||||
assert_eq!(Value::Text("1".to_string()).cmp(&Value::Text("2".to_string())), Ordering::Greater);
|
Ordering::Greater
|
||||||
assert_eq!(Value::from_symbol("a", "b").cmp(&Value::from_symbol("c", "d")), Ordering::Greater);
|
);
|
||||||
assert_eq!(Value::from_symbol(None, "a").cmp(&Value::from_symbol(None, "b")), Ordering::Greater);
|
assert_eq!(
|
||||||
assert_eq!(Value::from_keyword(":a", ":b").cmp(&Value::from_keyword(":c", ":d")), Ordering::Greater);
|
Value::from_float(1f64).cmp(&Value::from_float(2f64)),
|
||||||
assert_eq!(Value::from_keyword(None, ":a").cmp(&Value::from_keyword(None, ":b")), Ordering::Greater);
|
Ordering::Greater
|
||||||
assert_eq!(Value::Vector(vec![]).cmp(&Value::Vector(vec![])), Ordering::Equal);
|
);
|
||||||
assert_eq!(Value::List(LinkedList::new()).cmp(&Value::List(LinkedList::new())), Ordering::Equal);
|
assert_eq!(
|
||||||
assert_eq!(Value::Set(BTreeSet::new()).cmp(&Value::Set(BTreeSet::new())), Ordering::Equal);
|
Value::Text("1".to_string()).cmp(&Value::Text("2".to_string())),
|
||||||
assert_eq!(Value::Map(BTreeMap::new()).cmp(&Value::Map(BTreeMap::new())), Ordering::Equal);
|
Ordering::Greater
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Value::from_symbol("a", "b").cmp(&Value::from_symbol("c", "d")),
|
||||||
|
Ordering::Greater
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Value::from_symbol(None, "a").cmp(&Value::from_symbol(None, "b")),
|
||||||
|
Ordering::Greater
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Value::from_keyword(":a", ":b").cmp(&Value::from_keyword(":c", ":d")),
|
||||||
|
Ordering::Greater
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Value::from_keyword(None, ":a").cmp(&Value::from_keyword(None, ":b")),
|
||||||
|
Ordering::Greater
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Value::Vector(vec![]).cmp(&Value::Vector(vec![])),
|
||||||
|
Ordering::Equal
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Value::List(LinkedList::new()).cmp(&Value::List(LinkedList::new())),
|
||||||
|
Ordering::Equal
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Value::Set(BTreeSet::new()).cmp(&Value::Set(BTreeSet::new())),
|
||||||
|
Ordering::Equal
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Value::Map(BTreeMap::new()).cmp(&Value::Map(BTreeMap::new())),
|
||||||
|
Ordering::Equal
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -780,10 +859,10 @@ mod test {
|
||||||
|
|
||||||
assert!(n_v.clone().into_keyword().is_some());
|
assert!(n_v.clone().into_keyword().is_some());
|
||||||
assert!(n_v.clone().into_plain_keyword().is_none());
|
assert!(n_v.clone().into_plain_keyword().is_none());
|
||||||
assert!(n_v.clone().into_namespaced_keyword().is_some());
|
assert!(n_v.into_namespaced_keyword().is_some());
|
||||||
|
|
||||||
assert!(p_v.clone().into_keyword().is_some());
|
assert!(p_v.clone().into_keyword().is_some());
|
||||||
assert!(p_v.clone().into_plain_keyword().is_some());
|
assert!(p_v.clone().into_plain_keyword().is_some());
|
||||||
assert!(p_v.clone().into_namespaced_keyword().is_none());
|
assert!(p_v.into_namespaced_keyword().is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use types::Value;
|
use crate::types::Value;
|
||||||
|
|
||||||
/// Merge the EDN `Value::Map` instance `right` into `left`. Returns `None` if either `left` or
|
/// Merge the EDN `Value::Map` instance `right` into `left`. Returns `None` if either `left` or
|
||||||
/// `right` is not a `Value::Map`.
|
/// `right` is not a `Value::Map`.
|
||||||
|
@ -21,11 +21,11 @@ use types::Value;
|
||||||
/// TODO: implement `merge` for [Value], following the `concat`/`SliceConcatExt` pattern.
|
/// TODO: implement `merge` for [Value], following the `concat`/`SliceConcatExt` pattern.
|
||||||
pub fn merge(left: &Value, right: &Value) -> Option<Value> {
|
pub fn merge(left: &Value, right: &Value) -> Option<Value> {
|
||||||
match (left, right) {
|
match (left, right) {
|
||||||
(&Value::Map(ref l), &Value::Map(ref r)) => {
|
(Value::Map(l), Value::Map(r)) => {
|
||||||
let mut result = l.clone();
|
let mut result = l.clone();
|
||||||
result.extend(r.clone().into_iter());
|
result.extend(r.clone());
|
||||||
Some(Value::Map(result))
|
Some(Value::Map(result))
|
||||||
}
|
}
|
||||||
_ => None
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,22 +8,21 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
use ::std::rc::{
|
use std::rc::Rc;
|
||||||
Rc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use ::std::sync::{
|
use std::sync::Arc;
|
||||||
Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub trait FromRc<T> {
|
pub trait FromRc<T> {
|
||||||
fn from_rc(val: Rc<T>) -> Self;
|
fn from_rc(val: Rc<T>) -> Self;
|
||||||
fn from_arc(val: Arc<T>) -> Self;
|
fn from_arc(val: Arc<T>) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> FromRc<T> for Rc<T> where T: Sized + Clone {
|
impl<T> FromRc<T> for Rc<T>
|
||||||
|
where
|
||||||
|
T: Sized + Clone,
|
||||||
|
{
|
||||||
fn from_rc(val: Rc<T>) -> Self {
|
fn from_rc(val: Rc<T>) -> Self {
|
||||||
val.clone()
|
val
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_arc(val: Arc<T>) -> Self {
|
fn from_arc(val: Arc<T>) -> Self {
|
||||||
|
@ -34,7 +33,10 @@ impl<T> FromRc<T> for Rc<T> where T: Sized + Clone {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> FromRc<T> for Arc<T> where T: Sized + Clone {
|
impl<T> FromRc<T> for Arc<T>
|
||||||
|
where
|
||||||
|
T: Sized + Clone,
|
||||||
|
{
|
||||||
fn from_rc(val: Rc<T>) -> Self {
|
fn from_rc(val: Rc<T>) -> Self {
|
||||||
match ::std::rc::Rc::<T>::try_unwrap(val) {
|
match ::std::rc::Rc::<T>::try_unwrap(val) {
|
||||||
Ok(v) => Self::new(v),
|
Ok(v) => Self::new(v),
|
||||||
|
@ -43,11 +45,14 @@ impl<T> FromRc<T> for Arc<T> where T: Sized + Clone {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_arc(val: Arc<T>) -> Self {
|
fn from_arc(val: Arc<T>) -> Self {
|
||||||
val.clone()
|
val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> FromRc<T> for Box<T> where T: Sized + Clone {
|
impl<T> FromRc<T> for Box<T>
|
||||||
|
where
|
||||||
|
T: Sized + Clone,
|
||||||
|
{
|
||||||
fn from_rc(val: Rc<T>) -> Self {
|
fn from_rc(val: Rc<T>) -> Self {
|
||||||
match ::std::rc::Rc::<T>::try_unwrap(val) {
|
match ::std::rc::Rc::<T>::try_unwrap(val) {
|
||||||
Ok(v) => Self::new(v),
|
Ok(v) => Self::new(v),
|
||||||
|
@ -69,7 +74,10 @@ pub trait Cloned<T> {
|
||||||
fn to_value_rc(&self) -> ValueRc<T>;
|
fn to_value_rc(&self) -> ValueRc<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Clone> Cloned<T> for Rc<T> where T: Sized + Clone {
|
impl<T: Clone> Cloned<T> for Rc<T>
|
||||||
|
where
|
||||||
|
T: Sized + Clone,
|
||||||
|
{
|
||||||
fn cloned(&self) -> T {
|
fn cloned(&self) -> T {
|
||||||
(*self.as_ref()).clone()
|
(*self.as_ref()).clone()
|
||||||
}
|
}
|
||||||
|
@ -79,7 +87,10 @@ impl<T: Clone> Cloned<T> for Rc<T> where T: Sized + Clone {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Clone> Cloned<T> for Arc<T> where T: Sized + Clone {
|
impl<T: Clone> Cloned<T> for Arc<T>
|
||||||
|
where
|
||||||
|
T: Sized + Clone,
|
||||||
|
{
|
||||||
fn cloned(&self) -> T {
|
fn cloned(&self) -> T {
|
||||||
(*self.as_ref()).clone()
|
(*self.as_ref()).clone()
|
||||||
}
|
}
|
||||||
|
@ -89,7 +100,10 @@ impl<T: Clone> Cloned<T> for Arc<T> where T: Sized + Clone {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Clone> Cloned<T> for Box<T> where T: Sized + Clone {
|
impl<T: Clone> Cloned<T> for Box<T>
|
||||||
|
where
|
||||||
|
T: Sized + Clone,
|
||||||
|
{
|
||||||
fn cloned(&self) -> T {
|
fn cloned(&self) -> T {
|
||||||
self.as_ref().clone()
|
self.as_ref().clone()
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,33 +10,14 @@
|
||||||
|
|
||||||
extern crate edn;
|
extern crate edn;
|
||||||
|
|
||||||
use edn::{
|
use edn::{Keyword, PlainSymbol};
|
||||||
Keyword,
|
|
||||||
PlainSymbol,
|
|
||||||
};
|
|
||||||
|
|
||||||
use edn::query::{
|
use edn::query::{
|
||||||
Direction,
|
Direction, Element, FindSpec, FnArg, Limit, NonIntegerConstant, OrJoin, OrWhereClause, Order,
|
||||||
Element,
|
Pattern, PatternNonValuePlace, PatternValuePlace, Predicate, UnifyVars, Variable, WhereClause,
|
||||||
FindSpec,
|
|
||||||
FnArg,
|
|
||||||
Limit,
|
|
||||||
NonIntegerConstant,
|
|
||||||
Order,
|
|
||||||
OrJoin,
|
|
||||||
OrWhereClause,
|
|
||||||
Pattern,
|
|
||||||
PatternNonValuePlace,
|
|
||||||
PatternValuePlace,
|
|
||||||
Predicate,
|
|
||||||
UnifyVars,
|
|
||||||
Variable,
|
|
||||||
WhereClause,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use edn::parse::{
|
use edn::parse::parse_query;
|
||||||
parse_query,
|
|
||||||
};
|
|
||||||
|
|
||||||
///! N.B., parsing a query can be done without reference to a DB.
|
///! N.B., parsing a query can be done without reference to a DB.
|
||||||
///! Processing the parsed query into something we can work with
|
///! Processing the parsed query into something we can work with
|
||||||
|
@ -48,9 +29,12 @@ fn can_parse_predicates() {
|
||||||
let s = "[:find [?x ...] :where [?x _ ?y] [(< ?y 10)]]";
|
let s = "[:find [?x ...] :where [?x _ ?y] [(< ?y 10)]]";
|
||||||
let p = parse_query(s).unwrap();
|
let p = parse_query(s).unwrap();
|
||||||
|
|
||||||
assert_eq!(p.find_spec,
|
assert_eq!(
|
||||||
FindSpec::FindColl(Element::Variable(Variable::from_valid_name("?x"))));
|
p.find_spec,
|
||||||
assert_eq!(p.where_clauses,
|
FindSpec::FindColl(Element::Variable(Variable::from_valid_name("?x")))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
p.where_clauses,
|
||||||
vec![
|
vec![
|
||||||
WhereClause::Pattern(Pattern {
|
WhereClause::Pattern(Pattern {
|
||||||
source: None,
|
source: None,
|
||||||
|
@ -59,10 +43,15 @@ fn can_parse_predicates() {
|
||||||
value: PatternValuePlace::Variable(Variable::from_valid_name("?y")),
|
value: PatternValuePlace::Variable(Variable::from_valid_name("?y")),
|
||||||
tx: PatternNonValuePlace::Placeholder,
|
tx: PatternNonValuePlace::Placeholder,
|
||||||
}),
|
}),
|
||||||
WhereClause::Pred(Predicate { operator: PlainSymbol::plain("<"), args: vec![
|
WhereClause::Pred(Predicate {
|
||||||
FnArg::Variable(Variable::from_valid_name("?y")), FnArg::EntidOrInteger(10),
|
operator: PlainSymbol::plain("<"),
|
||||||
]}),
|
args: vec![
|
||||||
]);
|
FnArg::Variable(Variable::from_valid_name("?y")),
|
||||||
|
FnArg::EntidOrInteger(10),
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -70,23 +59,23 @@ fn can_parse_simple_or() {
|
||||||
let s = "[:find ?x . :where (or [?x _ 10] [?x _ 15])]";
|
let s = "[:find ?x . :where (or [?x _ 10] [?x _ 15])]";
|
||||||
let p = parse_query(s).unwrap();
|
let p = parse_query(s).unwrap();
|
||||||
|
|
||||||
assert_eq!(p.find_spec,
|
assert_eq!(
|
||||||
FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x"))));
|
p.find_spec,
|
||||||
assert_eq!(p.where_clauses,
|
FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x")))
|
||||||
vec![
|
);
|
||||||
WhereClause::OrJoin(OrJoin::new(
|
assert_eq!(
|
||||||
|
p.where_clauses,
|
||||||
|
vec![WhereClause::OrJoin(OrJoin::new(
|
||||||
UnifyVars::Implicit,
|
UnifyVars::Implicit,
|
||||||
vec![
|
vec![
|
||||||
OrWhereClause::Clause(
|
OrWhereClause::Clause(WhereClause::Pattern(Pattern {
|
||||||
WhereClause::Pattern(Pattern {
|
|
||||||
source: None,
|
source: None,
|
||||||
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
|
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
|
||||||
attribute: PatternNonValuePlace::Placeholder,
|
attribute: PatternNonValuePlace::Placeholder,
|
||||||
value: PatternValuePlace::EntidOrInteger(10),
|
value: PatternValuePlace::EntidOrInteger(10),
|
||||||
tx: PatternNonValuePlace::Placeholder,
|
tx: PatternNonValuePlace::Placeholder,
|
||||||
})),
|
})),
|
||||||
OrWhereClause::Clause(
|
OrWhereClause::Clause(WhereClause::Pattern(Pattern {
|
||||||
WhereClause::Pattern(Pattern {
|
|
||||||
source: None,
|
source: None,
|
||||||
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
|
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
|
||||||
attribute: PatternNonValuePlace::Placeholder,
|
attribute: PatternNonValuePlace::Placeholder,
|
||||||
|
@ -94,8 +83,8 @@ fn can_parse_simple_or() {
|
||||||
tx: PatternNonValuePlace::Placeholder,
|
tx: PatternNonValuePlace::Placeholder,
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
)),
|
)),]
|
||||||
]);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -103,24 +92,23 @@ fn can_parse_unit_or_join() {
|
||||||
let s = "[:find ?x . :where (or-join [?x] [?x _ 15])]";
|
let s = "[:find ?x . :where (or-join [?x] [?x _ 15])]";
|
||||||
let p = parse_query(s).expect("to be able to parse find");
|
let p = parse_query(s).expect("to be able to parse find");
|
||||||
|
|
||||||
assert_eq!(p.find_spec,
|
assert_eq!(
|
||||||
FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x"))));
|
p.find_spec,
|
||||||
assert_eq!(p.where_clauses,
|
FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x")))
|
||||||
vec![
|
);
|
||||||
WhereClause::OrJoin(OrJoin::new(
|
assert_eq!(
|
||||||
|
p.where_clauses,
|
||||||
|
vec![WhereClause::OrJoin(OrJoin::new(
|
||||||
UnifyVars::Explicit(std::iter::once(Variable::from_valid_name("?x")).collect()),
|
UnifyVars::Explicit(std::iter::once(Variable::from_valid_name("?x")).collect()),
|
||||||
vec![
|
vec![OrWhereClause::Clause(WhereClause::Pattern(Pattern {
|
||||||
OrWhereClause::Clause(
|
|
||||||
WhereClause::Pattern(Pattern {
|
|
||||||
source: None,
|
source: None,
|
||||||
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
|
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
|
||||||
attribute: PatternNonValuePlace::Placeholder,
|
attribute: PatternNonValuePlace::Placeholder,
|
||||||
value: PatternValuePlace::EntidOrInteger(15),
|
value: PatternValuePlace::EntidOrInteger(15),
|
||||||
tx: PatternNonValuePlace::Placeholder,
|
tx: PatternNonValuePlace::Placeholder,
|
||||||
})),
|
})),],
|
||||||
],
|
)),]
|
||||||
)),
|
);
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -128,23 +116,23 @@ fn can_parse_simple_or_join() {
|
||||||
let s = "[:find ?x . :where (or-join [?x] [?x _ 10] [?x _ -15])]";
|
let s = "[:find ?x . :where (or-join [?x] [?x _ 10] [?x _ -15])]";
|
||||||
let p = parse_query(s).unwrap();
|
let p = parse_query(s).unwrap();
|
||||||
|
|
||||||
assert_eq!(p.find_spec,
|
assert_eq!(
|
||||||
FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x"))));
|
p.find_spec,
|
||||||
assert_eq!(p.where_clauses,
|
FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x")))
|
||||||
vec![
|
);
|
||||||
WhereClause::OrJoin(OrJoin::new(
|
assert_eq!(
|
||||||
|
p.where_clauses,
|
||||||
|
vec![WhereClause::OrJoin(OrJoin::new(
|
||||||
UnifyVars::Explicit(std::iter::once(Variable::from_valid_name("?x")).collect()),
|
UnifyVars::Explicit(std::iter::once(Variable::from_valid_name("?x")).collect()),
|
||||||
vec![
|
vec![
|
||||||
OrWhereClause::Clause(
|
OrWhereClause::Clause(WhereClause::Pattern(Pattern {
|
||||||
WhereClause::Pattern(Pattern {
|
|
||||||
source: None,
|
source: None,
|
||||||
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
|
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
|
||||||
attribute: PatternNonValuePlace::Placeholder,
|
attribute: PatternNonValuePlace::Placeholder,
|
||||||
value: PatternValuePlace::EntidOrInteger(10),
|
value: PatternValuePlace::EntidOrInteger(10),
|
||||||
tx: PatternNonValuePlace::Placeholder,
|
tx: PatternNonValuePlace::Placeholder,
|
||||||
})),
|
})),
|
||||||
OrWhereClause::Clause(
|
OrWhereClause::Clause(WhereClause::Pattern(Pattern {
|
||||||
WhereClause::Pattern(Pattern {
|
|
||||||
source: None,
|
source: None,
|
||||||
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
|
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
|
||||||
attribute: PatternNonValuePlace::Placeholder,
|
attribute: PatternNonValuePlace::Placeholder,
|
||||||
|
@ -152,8 +140,8 @@ fn can_parse_simple_or_join() {
|
||||||
tx: PatternNonValuePlace::Placeholder,
|
tx: PatternNonValuePlace::Placeholder,
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
)),
|
)),]
|
||||||
]);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -166,51 +154,57 @@ fn can_parse_simple_or_and_join() {
|
||||||
let s = "[:find ?x . :where (or [?x _ 10] (and (or [?x :foo/bar ?y] [?x :foo/baz ?y]) [(< ?y 1)]))]";
|
let s = "[:find ?x . :where (or [?x _ 10] (and (or [?x :foo/bar ?y] [?x :foo/baz ?y]) [(< ?y 1)]))]";
|
||||||
let p = parse_query(s).unwrap();
|
let p = parse_query(s).unwrap();
|
||||||
|
|
||||||
assert_eq!(p.find_spec,
|
assert_eq!(
|
||||||
FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x"))));
|
p.find_spec,
|
||||||
assert_eq!(p.where_clauses,
|
FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x")))
|
||||||
vec![
|
);
|
||||||
WhereClause::OrJoin(OrJoin::new(
|
assert_eq!(
|
||||||
|
p.where_clauses,
|
||||||
|
vec![WhereClause::OrJoin(OrJoin::new(
|
||||||
UnifyVars::Implicit,
|
UnifyVars::Implicit,
|
||||||
vec![
|
vec![
|
||||||
OrWhereClause::Clause(
|
OrWhereClause::Clause(WhereClause::Pattern(Pattern {
|
||||||
WhereClause::Pattern(Pattern {
|
|
||||||
source: None,
|
source: None,
|
||||||
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
|
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
|
||||||
attribute: PatternNonValuePlace::Placeholder,
|
attribute: PatternNonValuePlace::Placeholder,
|
||||||
value: PatternValuePlace::EntidOrInteger(10),
|
value: PatternValuePlace::EntidOrInteger(10),
|
||||||
tx: PatternNonValuePlace::Placeholder,
|
tx: PatternNonValuePlace::Placeholder,
|
||||||
})),
|
})),
|
||||||
OrWhereClause::And(
|
OrWhereClause::And(vec![
|
||||||
vec![
|
|
||||||
WhereClause::OrJoin(OrJoin::new(
|
WhereClause::OrJoin(OrJoin::new(
|
||||||
UnifyVars::Implicit,
|
UnifyVars::Implicit,
|
||||||
vec![
|
vec![
|
||||||
OrWhereClause::Clause(WhereClause::Pattern(Pattern {
|
OrWhereClause::Clause(WhereClause::Pattern(Pattern {
|
||||||
source: None,
|
source: None,
|
||||||
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
|
entity: PatternNonValuePlace::Variable(Variable::from_valid_name(
|
||||||
|
"?x"
|
||||||
|
)),
|
||||||
attribute: ident("foo", "bar"),
|
attribute: ident("foo", "bar"),
|
||||||
value: PatternValuePlace::Variable(Variable::from_valid_name("?y")),
|
value: PatternValuePlace::Variable(Variable::from_valid_name("?y")),
|
||||||
tx: PatternNonValuePlace::Placeholder,
|
tx: PatternNonValuePlace::Placeholder,
|
||||||
})),
|
})),
|
||||||
OrWhereClause::Clause(WhereClause::Pattern(Pattern {
|
OrWhereClause::Clause(WhereClause::Pattern(Pattern {
|
||||||
source: None,
|
source: None,
|
||||||
entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
|
entity: PatternNonValuePlace::Variable(Variable::from_valid_name(
|
||||||
|
"?x"
|
||||||
|
)),
|
||||||
attribute: ident("foo", "baz"),
|
attribute: ident("foo", "baz"),
|
||||||
value: PatternValuePlace::Variable(Variable::from_valid_name("?y")),
|
value: PatternValuePlace::Variable(Variable::from_valid_name("?y")),
|
||||||
tx: PatternNonValuePlace::Placeholder,
|
tx: PatternNonValuePlace::Placeholder,
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
|
WhereClause::Pred(Predicate {
|
||||||
WhereClause::Pred(Predicate { operator: PlainSymbol::plain("<"), args: vec![
|
operator: PlainSymbol::plain("<"),
|
||||||
FnArg::Variable(Variable::from_valid_name("?y")), FnArg::EntidOrInteger(1),
|
args: vec![
|
||||||
]}),
|
FnArg::Variable(Variable::from_valid_name("?y")),
|
||||||
|
FnArg::EntidOrInteger(1),
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
],)
|
||||||
],
|
],
|
||||||
)
|
)),]
|
||||||
],
|
);
|
||||||
)),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -220,21 +214,40 @@ fn can_parse_order_by() {
|
||||||
|
|
||||||
// Defaults to ascending.
|
// Defaults to ascending.
|
||||||
let default = "[:find ?x :where [?x :foo/baz ?y] :order ?y]";
|
let default = "[:find ?x :where [?x :foo/baz ?y] :order ?y]";
|
||||||
assert_eq!(parse_query(default).unwrap().order,
|
assert_eq!(
|
||||||
Some(vec![Order(Direction::Ascending, Variable::from_valid_name("?y"))]));
|
parse_query(default).unwrap().order,
|
||||||
|
Some(vec![Order(
|
||||||
|
Direction::Ascending,
|
||||||
|
Variable::from_valid_name("?y")
|
||||||
|
)])
|
||||||
|
);
|
||||||
|
|
||||||
let ascending = "[:find ?x :where [?x :foo/baz ?y] :order (asc ?y)]";
|
let ascending = "[:find ?x :where [?x :foo/baz ?y] :order (asc ?y)]";
|
||||||
assert_eq!(parse_query(ascending).unwrap().order,
|
assert_eq!(
|
||||||
Some(vec![Order(Direction::Ascending, Variable::from_valid_name("?y"))]));
|
parse_query(ascending).unwrap().order,
|
||||||
|
Some(vec![Order(
|
||||||
|
Direction::Ascending,
|
||||||
|
Variable::from_valid_name("?y")
|
||||||
|
)])
|
||||||
|
);
|
||||||
|
|
||||||
let descending = "[:find ?x :where [?x :foo/baz ?y] :order (desc ?y)]";
|
let descending = "[:find ?x :where [?x :foo/baz ?y] :order (desc ?y)]";
|
||||||
assert_eq!(parse_query(descending).unwrap().order,
|
assert_eq!(
|
||||||
Some(vec![Order(Direction::Descending, Variable::from_valid_name("?y"))]));
|
parse_query(descending).unwrap().order,
|
||||||
|
Some(vec![Order(
|
||||||
|
Direction::Descending,
|
||||||
|
Variable::from_valid_name("?y")
|
||||||
|
)])
|
||||||
|
);
|
||||||
|
|
||||||
let mixed = "[:find ?x :where [?x :foo/baz ?y] :order (desc ?y) (asc ?x)]";
|
let mixed = "[:find ?x :where [?x :foo/baz ?y] :order (desc ?y) (asc ?x)]";
|
||||||
assert_eq!(parse_query(mixed).unwrap().order,
|
assert_eq!(
|
||||||
Some(vec![Order(Direction::Descending, Variable::from_valid_name("?y")),
|
parse_query(mixed).unwrap().order,
|
||||||
Order(Direction::Ascending, Variable::from_valid_name("?x"))]));
|
Some(vec![
|
||||||
|
Order(Direction::Descending, Variable::from_valid_name("?y")),
|
||||||
|
Order(Direction::Ascending, Variable::from_valid_name("?x"))
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -246,55 +259,76 @@ fn can_parse_limit() {
|
||||||
assert!(parse_query(zero_invalid).is_err());
|
assert!(parse_query(zero_invalid).is_err());
|
||||||
|
|
||||||
let none = "[:find ?x :where [?x :foo/baz ?y]]";
|
let none = "[:find ?x :where [?x :foo/baz ?y]]";
|
||||||
assert_eq!(parse_query(none).unwrap().limit,
|
assert_eq!(parse_query(none).unwrap().limit, Limit::None);
|
||||||
Limit::None);
|
|
||||||
|
|
||||||
let one = "[:find ?x :where [?x :foo/baz ?y] :limit 1]";
|
let one = "[:find ?x :where [?x :foo/baz ?y] :limit 1]";
|
||||||
assert_eq!(parse_query(one).unwrap().limit,
|
assert_eq!(parse_query(one).unwrap().limit, Limit::Fixed(1));
|
||||||
Limit::Fixed(1));
|
|
||||||
|
|
||||||
let onethousand = "[:find ?x :where [?x :foo/baz ?y] :limit 1000]";
|
let onethousand = "[:find ?x :where [?x :foo/baz ?y] :limit 1000]";
|
||||||
assert_eq!(parse_query(onethousand).unwrap().limit,
|
assert_eq!(parse_query(onethousand).unwrap().limit, Limit::Fixed(1000));
|
||||||
Limit::Fixed(1000));
|
|
||||||
|
|
||||||
let variable_with_in = "[:find ?x :in ?limit :where [?x :foo/baz ?y] :limit ?limit]";
|
let variable_with_in = "[:find ?x :in ?limit :where [?x :foo/baz ?y] :limit ?limit]";
|
||||||
assert_eq!(parse_query(variable_with_in).unwrap().limit,
|
assert_eq!(
|
||||||
Limit::Variable(Variable::from_valid_name("?limit")));
|
parse_query(variable_with_in).unwrap().limit,
|
||||||
|
Limit::Variable(Variable::from_valid_name("?limit"))
|
||||||
|
);
|
||||||
|
|
||||||
let variable_with_in_used = "[:find ?x :in ?limit :where [?x :foo/baz ?limit] :limit ?limit]";
|
let variable_with_in_used = "[:find ?x :in ?limit :where [?x :foo/baz ?limit] :limit ?limit]";
|
||||||
assert_eq!(parse_query(variable_with_in_used).unwrap().limit,
|
assert_eq!(
|
||||||
Limit::Variable(Variable::from_valid_name("?limit")));
|
parse_query(variable_with_in_used).unwrap().limit,
|
||||||
|
Limit::Variable(Variable::from_valid_name("?limit"))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_parse_uuid() {
|
fn can_parse_uuid() {
|
||||||
let expected = edn::Uuid::parse_str("4cb3f828-752d-497a-90c9-b1fd516d5644").expect("valid uuid");
|
let expected =
|
||||||
|
edn::Uuid::parse_str("4cb3f828-752d-497a-90c9-b1fd516d5644").expect("valid uuid");
|
||||||
let s = "[:find ?x :where [?x :foo/baz #uuid \"4cb3f828-752d-497a-90c9-b1fd516d5644\"]]";
|
let s = "[:find ?x :where [?x :foo/baz #uuid \"4cb3f828-752d-497a-90c9-b1fd516d5644\"]]";
|
||||||
assert_eq!(parse_query(s).expect("parsed").where_clauses.pop().expect("a where clause"),
|
assert_eq!(
|
||||||
|
parse_query(s)
|
||||||
|
.expect("parsed")
|
||||||
|
.where_clauses
|
||||||
|
.pop()
|
||||||
|
.expect("a where clause"),
|
||||||
WhereClause::Pattern(
|
WhereClause::Pattern(
|
||||||
Pattern::new(None,
|
Pattern::new(
|
||||||
|
None,
|
||||||
PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
|
PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
|
||||||
Keyword::namespaced("foo", "baz").into(),
|
Keyword::namespaced("foo", "baz").into(),
|
||||||
PatternValuePlace::Constant(NonIntegerConstant::Uuid(expected)),
|
PatternValuePlace::Constant(NonIntegerConstant::Uuid(expected)),
|
||||||
PatternNonValuePlace::Placeholder)
|
PatternNonValuePlace::Placeholder
|
||||||
.expect("valid pattern")));
|
)
|
||||||
|
.expect("valid pattern")
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_parse_exotic_whitespace() {
|
fn can_parse_exotic_whitespace() {
|
||||||
let expected = edn::Uuid::parse_str("4cb3f828-752d-497a-90c9-b1fd516d5644").expect("valid uuid");
|
let expected =
|
||||||
|
edn::Uuid::parse_str("4cb3f828-752d-497a-90c9-b1fd516d5644").expect("valid uuid");
|
||||||
// The query string from `can_parse_uuid`, with newlines, commas, and line comments interspersed.
|
// The query string from `can_parse_uuid`, with newlines, commas, and line comments interspersed.
|
||||||
let s = r#"[:find
|
let s = r#"[:find
|
||||||
?x ,, :where, ;atest
|
?x ,, :where, ;atest
|
||||||
[?x :foo/baz #uuid
|
[?x :foo/baz #uuid
|
||||||
"4cb3f828-752d-497a-90c9-b1fd516d5644", ;testa
|
"4cb3f828-752d-497a-90c9-b1fd516d5644", ;testa
|
||||||
,],, ,],;"#;
|
,],, ,],;"#;
|
||||||
assert_eq!(parse_query(s).expect("parsed").where_clauses.pop().expect("a where clause"),
|
assert_eq!(
|
||||||
|
parse_query(s)
|
||||||
|
.expect("parsed")
|
||||||
|
.where_clauses
|
||||||
|
.pop()
|
||||||
|
.expect("a where clause"),
|
||||||
WhereClause::Pattern(
|
WhereClause::Pattern(
|
||||||
Pattern::new(None,
|
Pattern::new(
|
||||||
|
None,
|
||||||
PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
|
PatternNonValuePlace::Variable(Variable::from_valid_name("?x")),
|
||||||
Keyword::namespaced("foo", "baz").into(),
|
Keyword::namespaced("foo", "baz").into(),
|
||||||
PatternValuePlace::Constant(NonIntegerConstant::Uuid(expected)),
|
PatternValuePlace::Constant(NonIntegerConstant::Uuid(expected)),
|
||||||
PatternNonValuePlace::Placeholder)
|
PatternNonValuePlace::Placeholder
|
||||||
.expect("valid pattern")));
|
)
|
||||||
|
.expect("valid pattern")
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,10 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
#![cfg(feature = "serde_support")]
|
#![cfg(feature = "serde_support")]
|
||||||
|
|
||||||
extern crate serde_test;
|
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
extern crate serde_test;
|
||||||
|
|
||||||
extern crate edn;
|
extern crate edn;
|
||||||
use edn::symbols::Keyword;
|
use edn::symbols::Keyword;
|
||||||
|
@ -22,19 +21,24 @@ use serde_test::{assert_tokens, Token};
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serialize_keyword() {
|
fn test_serialize_keyword() {
|
||||||
let kw = Keyword::namespaced("foo", "bar");
|
let kw = Keyword::namespaced("foo", "bar");
|
||||||
assert_tokens(&kw, &[
|
assert_tokens(
|
||||||
|
&kw,
|
||||||
|
&[
|
||||||
Token::NewtypeStruct { name: "Keyword" },
|
Token::NewtypeStruct { name: "Keyword" },
|
||||||
Token::Struct { name: "NamespaceableName", len: 2 },
|
Token::Struct {
|
||||||
|
name: "NamespaceableName",
|
||||||
|
len: 2,
|
||||||
|
},
|
||||||
Token::Str("namespace"),
|
Token::Str("namespace"),
|
||||||
Token::Some,
|
Token::Some,
|
||||||
Token::BorrowedStr("foo"),
|
Token::BorrowedStr("foo"),
|
||||||
Token::Str("name"),
|
Token::Str("name"),
|
||||||
Token::BorrowedStr("bar"),
|
Token::BorrowedStr("bar"),
|
||||||
Token::StructEnd,
|
Token::StructEnd,
|
||||||
]);
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(feature = "serde_support")]
|
#[cfg(feature = "serde_support")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_deserialize_keyword() {
|
fn test_deserialize_keyword() {
|
||||||
|
@ -51,6 +55,3 @@ fn test_deserialize_keyword() {
|
||||||
let not_kw = serde_json::from_str::<Keyword>(bad_ns_json);
|
let not_kw = serde_json::from_str::<Keyword>(bad_ns_json);
|
||||||
assert!(not_kw.is_err());
|
assert!(not_kw.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
31
examples/movies.edn
Normal file
31
examples/movies.edn
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
;; movie schema
|
||||||
|
[{:db/ident :movie/title
|
||||||
|
:db/valueType :db.type/string
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db/doc "The title of the movie"}
|
||||||
|
|
||||||
|
{:db/ident :movie/genre
|
||||||
|
:db/valueType :db.type/string
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db/doc "The genre of the movie"}
|
||||||
|
|
||||||
|
{:db/ident :movie/release-year
|
||||||
|
:db/valueType :db.type/long
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db/doc "The year the movie was released in theaters"}]
|
||||||
|
|
||||||
|
;; a few movies
|
||||||
|
[{:movie/title "The Goonies"
|
||||||
|
:movie/genre "action/adventure"
|
||||||
|
:movie/release-year 1985}
|
||||||
|
{:movie/title "Commando"
|
||||||
|
:movie/genre "thriller/action"
|
||||||
|
:movie/release-year 1985}
|
||||||
|
{:movie/title "Repo Man"
|
||||||
|
:movie/genre "punk dystopia"
|
||||||
|
:movie/release-year 1984}]
|
||||||
|
|
||||||
|
;; query
|
||||||
|
[:find ?movie-title
|
||||||
|
:where [_ :movie/title ?movie-title]]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "mentat_ffi"
|
name = "mentat_ffi"
|
||||||
version = "0.0.1"
|
version = "0.0.2"
|
||||||
authors = ["Emily Toop <etoop@mozilla.com>"]
|
authors = ["Emily Toop <etoop@mozilla.com>"]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
@ -13,7 +13,7 @@ sqlcipher = ["mentat/sqlcipher"]
|
||||||
bundled_sqlite3 = ["mentat/bundled_sqlite3"]
|
bundled_sqlite3 = ["mentat/bundled_sqlite3"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libc = "0.2"
|
libc = "~0.2"
|
||||||
|
|
||||||
[dependencies.mentat]
|
[dependencies.mentat]
|
||||||
path = "../"
|
path = "../"
|
||||||
|
|
|
@ -21,4 +21,6 @@ pub enum LogLevel {
|
||||||
Error = 6,
|
Error = 6,
|
||||||
}
|
}
|
||||||
|
|
||||||
extern { pub fn __android_log_write(prio: c_int, tag: *const c_char, text: *const c_char) -> c_int; }
|
extern "C" {
|
||||||
|
pub fn __android_log_write(prio: c_int, tag: *const c_char, text: *const c_char) -> c_int;
|
||||||
|
}
|
||||||
|
|
791
ffi/src/lib.rs
791
ffi/src/lib.rs
File diff suppressed because it is too large
Load diff
|
@ -9,30 +9,31 @@
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
pub mod strings {
|
pub mod strings {
|
||||||
use std::ffi::{
|
use std::ffi::{CStr, CString};
|
||||||
CString,
|
|
||||||
CStr
|
|
||||||
};
|
|
||||||
use std::os::raw::c_char;
|
use std::os::raw::c_char;
|
||||||
|
|
||||||
use mentat::{
|
use mentat::Keyword;
|
||||||
Keyword,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn c_char_to_string(cchar: *const c_char) -> &'static str {
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function TODO
|
||||||
|
pub unsafe fn c_char_to_string(cchar: *const c_char) -> &'static str {
|
||||||
assert!(!cchar.is_null());
|
assert!(!cchar.is_null());
|
||||||
let c_str = unsafe { CStr::from_ptr(cchar) };
|
let c_str = CStr::from_ptr(cchar);
|
||||||
c_str.to_str().unwrap_or("")
|
c_str.to_str().unwrap_or("")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn string_to_c_char<T>(r_string: T) -> *mut c_char where T: Into<String> {
|
pub fn string_to_c_char<T>(r_string: T) -> *mut c_char
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
CString::new(r_string.into()).unwrap().into_raw()
|
CString::new(r_string.into()).unwrap().into_raw()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn kw_from_string(keyword_string: &'static str) -> Keyword {
|
pub fn kw_from_string(keyword_string: &'static str) -> Keyword {
|
||||||
// TODO: validate. The input might not be a keyword!
|
// TODO: validate. The input might not be a keyword!
|
||||||
let attr_name = keyword_string.trim_left_matches(":");
|
let attr_name = keyword_string.trim_start_matches(':');
|
||||||
let parts: Vec<&str> = attr_name.split("/").collect();
|
let parts: Vec<&str> = attr_name.split('/').collect();
|
||||||
Keyword::namespaced(parts[0], parts[1])
|
Keyword::namespaced(parts[0], parts[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,9 +66,9 @@ pub mod log {
|
||||||
|
|
||||||
pub mod error {
|
pub mod error {
|
||||||
use super::strings::string_to_c_char;
|
use super::strings::string_to_c_char;
|
||||||
use std::os::raw::c_char;
|
|
||||||
use std::boxed::Box;
|
use std::boxed::Box;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
use std::os::raw::c_char;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
/// Represents an error that occurred on the mentat side. Many mentat FFI functions take a
|
/// Represents an error that occurred on the mentat side. Many mentat FFI functions take a
|
||||||
|
@ -96,7 +97,9 @@ pub mod error {
|
||||||
|
|
||||||
impl Default for ExternError {
|
impl Default for ExternError {
|
||||||
fn default() -> ExternError {
|
fn default() -> ExternError {
|
||||||
ExternError { message: ptr::null_mut() }
|
ExternError {
|
||||||
|
message: ptr::null_mut(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,8 +110,12 @@ pub mod error {
|
||||||
/// - If `result` is `Err(e)`, returns a null pointer and stores a string representing the error
|
/// - If `result` is `Err(e)`, returns a null pointer and stores a string representing the error
|
||||||
/// message (which was allocated on the heap and should eventually be freed) into
|
/// message (which was allocated on the heap and should eventually be freed) into
|
||||||
/// `error.message`
|
/// `error.message`
|
||||||
|
/// # Safety
|
||||||
|
/// Be afraid... TODO
|
||||||
pub unsafe fn translate_result<T, E>(result: Result<T, E>, error: *mut ExternError) -> *mut T
|
pub unsafe fn translate_result<T, E>(result: Result<T, E>, error: *mut ExternError) -> *mut T
|
||||||
where E: Display {
|
where
|
||||||
|
E: Display,
|
||||||
|
{
|
||||||
// TODO: can't unwind across FFI...
|
// TODO: can't unwind across FFI...
|
||||||
assert!(!error.is_null(), "Error output parameter is not optional");
|
assert!(!error.is_null(), "Error output parameter is not optional");
|
||||||
let error = &mut *error;
|
let error = &mut *error;
|
||||||
|
@ -131,8 +138,15 @@ pub mod error {
|
||||||
/// - If `result` is `Err(e)`, returns a null pointer and stores a string representing the error
|
/// - If `result` is `Err(e)`, returns a null pointer and stores a string representing the error
|
||||||
/// message (which was allocated on the heap and should eventually be freed) into
|
/// message (which was allocated on the heap and should eventually be freed) into
|
||||||
/// `error.message`
|
/// `error.message`
|
||||||
pub unsafe fn translate_opt_result<T, E>(result: Result<Option<T>, E>, error: *mut ExternError) -> *mut T
|
/// # Safety
|
||||||
where E: Display {
|
/// Be afraid... TODO
|
||||||
|
pub unsafe fn translate_opt_result<T, E>(
|
||||||
|
result: Result<Option<T>, E>,
|
||||||
|
error: *mut ExternError,
|
||||||
|
) -> *mut T
|
||||||
|
where
|
||||||
|
E: Display,
|
||||||
|
{
|
||||||
assert!(!error.is_null(), "Error output parameter is not optional");
|
assert!(!error.is_null(), "Error output parameter is not optional");
|
||||||
let error = &mut *error;
|
let error = &mut *error;
|
||||||
error.message = ptr::null_mut();
|
error.message = ptr::null_mut();
|
||||||
|
@ -148,10 +162,14 @@ pub mod error {
|
||||||
|
|
||||||
/// Identical to `translate_result`, but with additional type checking for the case that we have
|
/// Identical to `translate_result`, but with additional type checking for the case that we have
|
||||||
/// a `Result<(), E>` (which we're about to drop on the floor).
|
/// a `Result<(), E>` (which we're about to drop on the floor).
|
||||||
pub unsafe fn translate_void_result<E>(result: Result<(), E>, error: *mut ExternError) where E: Display {
|
/// # Safety
|
||||||
|
/// Be afraid... TODO
|
||||||
|
pub unsafe fn translate_void_result<E>(result: Result<(), E>, error: *mut ExternError)
|
||||||
|
where
|
||||||
|
E: Display,
|
||||||
|
{
|
||||||
// Note that Box<T> guarantees that if T is zero sized, it's not heap allocated. So not
|
// Note that Box<T> guarantees that if T is zero sized, it's not heap allocated. So not
|
||||||
// only do we never need to free the return value of this, it would be a problem if someone did.
|
// only do we never need to free the return value of this, it would be a problem if someone did.
|
||||||
translate_result(result, error);
|
translate_result(result, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
57
public-traits/Cargo.toml
Normal file
57
public-traits/Cargo.toml
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
[package]
|
||||||
|
name = "public_traits"
|
||||||
|
version = "0.0.2"
|
||||||
|
workspace = ".."
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "public_traits"
|
||||||
|
path = "lib.rs"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["syncable"]
|
||||||
|
sqlcipher = ["rusqlite/sqlcipher"]
|
||||||
|
syncable = ["tolstoy_traits", "hyper", "serde_json"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
failure = "~0.1"
|
||||||
|
failure_derive = "~0.1"
|
||||||
|
http = "~0.2"
|
||||||
|
tokio = { version = "1.8.0", features = ["full"] }
|
||||||
|
uuid = "~1.0"
|
||||||
|
|
||||||
|
[dependencies.rusqlite]
|
||||||
|
version = "~0.29"
|
||||||
|
features = ["limits", "bundled"]
|
||||||
|
|
||||||
|
[dependencies.hyper]
|
||||||
|
version = "~0.14"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.serde_json]
|
||||||
|
version = "~1.0"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.edn]
|
||||||
|
path = "../edn"
|
||||||
|
|
||||||
|
[dependencies.core_traits]
|
||||||
|
path = "../core-traits"
|
||||||
|
|
||||||
|
[dependencies.db_traits]
|
||||||
|
path = "../db-traits"
|
||||||
|
|
||||||
|
[dependencies.query_algebrizer_traits]
|
||||||
|
path = "../query-algebrizer-traits"
|
||||||
|
|
||||||
|
[dependencies.query_projector_traits]
|
||||||
|
path = "../query-projector-traits"
|
||||||
|
|
||||||
|
[dependencies.query_pull_traits]
|
||||||
|
path = "../query-pull-traits"
|
||||||
|
|
||||||
|
[dependencies.sql_traits]
|
||||||
|
path = "../sql-traits"
|
||||||
|
|
||||||
|
[dependencies.tolstoy_traits]
|
||||||
|
path = "../tolstoy-traits"
|
||||||
|
optional = true
|
227
public-traits/errors.rs
Normal file
227
public-traits/errors.rs
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
// Copyright 2016 Mozilla
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||||
|
// this file except in compliance with the License. You may obtain a copy of the
|
||||||
|
// License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
// Unless required by applicable law or agreed to in writing, software distributed
|
||||||
|
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||||
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use std; // To refer to std::result::Result.
|
||||||
|
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
use rusqlite;
|
||||||
|
use uuid;
|
||||||
|
|
||||||
|
use edn;
|
||||||
|
|
||||||
|
use core_traits::{Attribute, ValueType};
|
||||||
|
|
||||||
|
use db_traits::errors::DbError;
|
||||||
|
use query_algebrizer_traits::errors::AlgebrizerError;
|
||||||
|
use query_projector_traits::errors::ProjectorError;
|
||||||
|
use query_pull_traits::errors::PullError;
|
||||||
|
use sql_traits::errors::SQLError;
|
||||||
|
|
||||||
|
#[cfg(feature = "syncable")]
|
||||||
|
use tolstoy_traits::errors::TolstoyError;
|
||||||
|
|
||||||
|
#[cfg(feature = "syncable")]
|
||||||
|
use hyper;
|
||||||
|
|
||||||
|
#[cfg(feature = "syncable")]
|
||||||
|
use serde_json;
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, MentatError>;
|
||||||
|
|
||||||
|
#[derive(Debug, Fail)]
|
||||||
|
pub enum MentatError {
|
||||||
|
#[fail(display = "bad uuid {}", _0)]
|
||||||
|
BadUuid(String),
|
||||||
|
|
||||||
|
#[fail(display = "path {} already exists", _0)]
|
||||||
|
PathAlreadyExists(String),
|
||||||
|
|
||||||
|
#[fail(display = "variables {:?} unbound at query execution time", _0)]
|
||||||
|
UnboundVariables(BTreeSet<String>),
|
||||||
|
|
||||||
|
#[fail(display = "invalid argument name: '{}'", _0)]
|
||||||
|
InvalidArgumentName(String),
|
||||||
|
|
||||||
|
#[fail(display = "unknown attribute: '{}'", _0)]
|
||||||
|
UnknownAttribute(String),
|
||||||
|
|
||||||
|
#[fail(display = "invalid vocabulary version")]
|
||||||
|
InvalidVocabularyVersion,
|
||||||
|
|
||||||
|
#[fail(
|
||||||
|
display = "vocabulary {}/version {} already has attribute {}, and the requested definition differs",
|
||||||
|
_0, _1, _2
|
||||||
|
)]
|
||||||
|
ConflictingAttributeDefinitions(String, u32, String, Attribute, Attribute),
|
||||||
|
|
||||||
|
#[fail(
|
||||||
|
display = "existing vocabulary {} too new: wanted version {}, got version {}",
|
||||||
|
_0, _1, _2
|
||||||
|
)]
|
||||||
|
ExistingVocabularyTooNew(String, u32, u32),
|
||||||
|
|
||||||
|
#[fail(display = "core schema: wanted version {}, got version {:?}", _0, _1)]
|
||||||
|
UnexpectedCoreSchema(u32, Option<u32>),
|
||||||
|
|
||||||
|
#[fail(display = "Lost the transact() race!")]
|
||||||
|
UnexpectedLostTransactRace,
|
||||||
|
|
||||||
|
#[fail(display = "missing core attribute {}", _0)]
|
||||||
|
MissingCoreVocabulary(edn::query::Keyword),
|
||||||
|
|
||||||
|
#[fail(display = "schema changed since query was prepared")]
|
||||||
|
PreparedQuerySchemaMismatch,
|
||||||
|
|
||||||
|
#[fail(
|
||||||
|
display = "provided value of type {} doesn't match attribute value type {}",
|
||||||
|
_0, _1
|
||||||
|
)]
|
||||||
|
ValueTypeMismatch(ValueType, ValueType),
|
||||||
|
|
||||||
|
#[fail(display = "{}", _0)]
|
||||||
|
IoError(#[cause] std::io::Error),
|
||||||
|
|
||||||
|
/// We're just not done yet. Message that the feature is recognized but not yet
|
||||||
|
/// implemented.
|
||||||
|
#[fail(display = "not yet implemented: {}", _0)]
|
||||||
|
NotYetImplemented(String),
|
||||||
|
|
||||||
|
// It would be better to capture the underlying `rusqlite::Error`, but that type doesn't
|
||||||
|
// implement many useful traits, including `Clone`, `Eq`, and `PartialEq`.
|
||||||
|
#[fail(display = "SQL error: {}, cause: {}", _0, _1)]
|
||||||
|
RusqliteError(String, String),
|
||||||
|
|
||||||
|
#[fail(display = "{}", _0)]
|
||||||
|
EdnParseError(#[cause] edn::ParseError),
|
||||||
|
|
||||||
|
#[fail(display = "{}", _0)]
|
||||||
|
DbError(#[cause] DbError),
|
||||||
|
|
||||||
|
#[fail(display = "{}", _0)]
|
||||||
|
AlgebrizerError(#[cause] AlgebrizerError),
|
||||||
|
|
||||||
|
#[fail(display = "{}", _0)]
|
||||||
|
ProjectorError(#[cause] ProjectorError),
|
||||||
|
|
||||||
|
#[fail(display = "{}", _0)]
|
||||||
|
PullError(#[cause] PullError),
|
||||||
|
|
||||||
|
#[fail(display = "{}", _0)]
|
||||||
|
SQLError(#[cause] SQLError),
|
||||||
|
|
||||||
|
#[fail(display = "{}", _0)]
|
||||||
|
UuidError(#[cause] uuid::Error),
|
||||||
|
|
||||||
|
#[cfg(feature = "syncable")]
|
||||||
|
#[fail(display = "{}", _0)]
|
||||||
|
TolstoyError(#[cause] TolstoyError),
|
||||||
|
|
||||||
|
#[cfg(feature = "syncable")]
|
||||||
|
#[fail(display = "{}", _0)]
|
||||||
|
NetworkError(#[cause] hyper::Error),
|
||||||
|
|
||||||
|
#[cfg(feature = "syncable")]
|
||||||
|
#[fail(display = "{}", _0)]
|
||||||
|
UriError(#[cause] http::uri::InvalidUri),
|
||||||
|
|
||||||
|
#[cfg(feature = "syncable")]
|
||||||
|
#[fail(display = "{}", _0)]
|
||||||
|
SerializationError(#[cause] serde_json::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for MentatError {
|
||||||
|
fn from(error: std::io::Error) -> Self {
|
||||||
|
MentatError::IoError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<rusqlite::Error> for MentatError {
|
||||||
|
fn from(error: rusqlite::Error) -> Self {
|
||||||
|
let cause = match error.source() {
|
||||||
|
Some(e) => e.to_string(),
|
||||||
|
None => "".to_string(),
|
||||||
|
};
|
||||||
|
MentatError::RusqliteError(error.to_string(), cause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<uuid::Error> for MentatError {
|
||||||
|
fn from(error: uuid::Error) -> Self {
|
||||||
|
MentatError::UuidError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<edn::ParseError> for MentatError {
|
||||||
|
fn from(error: edn::ParseError) -> Self {
|
||||||
|
MentatError::EdnParseError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DbError> for MentatError {
|
||||||
|
fn from(error: DbError) -> Self {
|
||||||
|
MentatError::DbError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AlgebrizerError> for MentatError {
|
||||||
|
fn from(error: AlgebrizerError) -> Self {
|
||||||
|
MentatError::AlgebrizerError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ProjectorError> for MentatError {
|
||||||
|
fn from(error: ProjectorError) -> Self {
|
||||||
|
MentatError::ProjectorError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PullError> for MentatError {
|
||||||
|
fn from(error: PullError) -> Self {
|
||||||
|
MentatError::PullError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SQLError> for MentatError {
|
||||||
|
fn from(error: SQLError) -> Self {
|
||||||
|
MentatError::SQLError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "syncable")]
|
||||||
|
impl From<TolstoyError> for MentatError {
|
||||||
|
fn from(error: TolstoyError) -> Self {
|
||||||
|
MentatError::TolstoyError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "syncable")]
|
||||||
|
impl From<serde_json::Error> for MentatError {
|
||||||
|
fn from(error: serde_json::Error) -> Self {
|
||||||
|
MentatError::SerializationError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "syncable")]
|
||||||
|
impl From<hyper::Error> for MentatError {
|
||||||
|
fn from(error: hyper::Error) -> Self {
|
||||||
|
MentatError::NetworkError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "syncable")]
|
||||||
|
impl From<http::uri::InvalidUri> for MentatError {
|
||||||
|
fn from(error: http::uri::InvalidUri) -> Self {
|
||||||
|
MentatError::UriError(error)
|
||||||
|
}
|
||||||
|
}
|
35
public-traits/lib.rs
Normal file
35
public-traits/lib.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// Copyright 2018 Mozilla
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||||
|
// this file except in compliance with the License. You may obtain a copy of the
|
||||||
|
// License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
// Unless required by applicable law or agreed to in writing, software distributed
|
||||||
|
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||||
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
|
extern crate failure;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate failure_derive;
|
||||||
|
|
||||||
|
extern crate rusqlite;
|
||||||
|
|
||||||
|
extern crate core_traits;
|
||||||
|
extern crate db_traits;
|
||||||
|
extern crate edn;
|
||||||
|
extern crate query_algebrizer_traits;
|
||||||
|
extern crate query_projector_traits;
|
||||||
|
extern crate query_pull_traits;
|
||||||
|
extern crate sql_traits;
|
||||||
|
extern crate uuid;
|
||||||
|
|
||||||
|
#[cfg(feature = "syncable")]
|
||||||
|
extern crate tolstoy_traits;
|
||||||
|
|
||||||
|
#[cfg(feature = "syncable")]
|
||||||
|
extern crate hyper;
|
||||||
|
|
||||||
|
#[cfg(feature = "syncable")]
|
||||||
|
extern crate serde_json;
|
||||||
|
|
||||||
|
pub mod errors;
|
18
query-algebrizer-traits/Cargo.toml
Normal file
18
query-algebrizer-traits/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "query_algebrizer_traits"
|
||||||
|
version = "0.0.2"
|
||||||
|
workspace = ".."
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "query_algebrizer_traits"
|
||||||
|
path = "lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
failure = "~0.1"
|
||||||
|
failure_derive = "~0.1"
|
||||||
|
|
||||||
|
[dependencies.edn]
|
||||||
|
path = "../edn"
|
||||||
|
|
||||||
|
[dependencies.core_traits]
|
||||||
|
path = "../core-traits"
|
|
@ -8,29 +8,14 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
extern crate mentat_query;
|
|
||||||
|
|
||||||
use std; // To refer to std::result::Result.
|
use std; // To refer to std::result::Result.
|
||||||
|
|
||||||
use mentat_core::{
|
use core_traits::{ValueType, ValueTypeSet};
|
||||||
EdnParseError,
|
|
||||||
ValueType,
|
|
||||||
ValueTypeSet,
|
|
||||||
};
|
|
||||||
|
|
||||||
use self::mentat_query::{
|
use edn::{query::PlainSymbol, ParseError};
|
||||||
PlainSymbol,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, AlgebrizerError>;
|
pub type Result<T> = std::result::Result<T, AlgebrizerError>;
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! bail {
|
|
||||||
($e:expr) => (
|
|
||||||
return Err($e.into());
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum BindingError {
|
pub enum BindingError {
|
||||||
NoBoundVariable,
|
NoBoundVariable,
|
||||||
|
@ -48,7 +33,10 @@ pub enum BindingError {
|
||||||
|
|
||||||
/// Expected `[?x1 … ?xN]` or `[[?x1 … ?xN]]` but got some other number of bindings. Mentat is
|
/// Expected `[?x1 … ?xN]` or `[[?x1 … ?xN]]` but got some other number of bindings. Mentat is
|
||||||
/// deliberately more strict than Datomic: we prefer placeholders to omission.
|
/// deliberately more strict than Datomic: we prefer placeholders to omission.
|
||||||
InvalidNumberOfBindings { number: usize, expected: usize },
|
InvalidNumberOfBindings {
|
||||||
|
number: usize,
|
||||||
|
expected: usize,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, Fail, PartialEq)]
|
#[derive(Clone, Debug, Eq, Fail, PartialEq)]
|
||||||
|
@ -59,23 +47,38 @@ pub enum AlgebrizerError {
|
||||||
#[fail(display = "unexpected FnArg")]
|
#[fail(display = "unexpected FnArg")]
|
||||||
UnsupportedArgument,
|
UnsupportedArgument,
|
||||||
|
|
||||||
#[fail(display = "value of type {} provided for var {}, expected {}", _0, _1, _2)]
|
#[fail(
|
||||||
|
display = "value of type {} provided for var {}, expected {}",
|
||||||
|
_0, _1, _2
|
||||||
|
)]
|
||||||
InputTypeDisagreement(PlainSymbol, ValueType, ValueType),
|
InputTypeDisagreement(PlainSymbol, ValueType, ValueType),
|
||||||
|
|
||||||
#[fail(display = "invalid number of arguments to {}: expected {}, got {}.", _0, _1, _2)]
|
#[fail(
|
||||||
|
display = "invalid number of arguments to {}: expected {}, got {}.",
|
||||||
|
_0, _1, _2
|
||||||
|
)]
|
||||||
InvalidNumberOfArguments(PlainSymbol, usize, usize),
|
InvalidNumberOfArguments(PlainSymbol, usize, usize),
|
||||||
|
|
||||||
#[fail(display = "invalid argument to {}: expected {} in position {}.", _0, _1, _2)]
|
#[fail(
|
||||||
|
display = "invalid argument to {}: expected {} in position {}.",
|
||||||
|
_0, _1, _2
|
||||||
|
)]
|
||||||
InvalidArgument(PlainSymbol, &'static str, usize),
|
InvalidArgument(PlainSymbol, &'static str, usize),
|
||||||
|
|
||||||
#[fail(display = "invalid argument to {}: expected one of {:?} in position {}.", _0, _1, _2)]
|
#[fail(
|
||||||
|
display = "invalid argument to {}: expected one of {:?} in position {}.",
|
||||||
|
_0, _1, _2
|
||||||
|
)]
|
||||||
InvalidArgumentType(PlainSymbol, ValueTypeSet, usize),
|
InvalidArgumentType(PlainSymbol, ValueTypeSet, usize),
|
||||||
|
|
||||||
// TODO: flesh this out.
|
// TODO: flesh this out.
|
||||||
#[fail(display = "invalid expression in ground constant")]
|
#[fail(display = "invalid expression in ground constant")]
|
||||||
InvalidGroundConstant,
|
InvalidGroundConstant,
|
||||||
|
|
||||||
#[fail(display = "invalid limit {} of type {}: expected natural number.", _0, _1)]
|
#[fail(
|
||||||
|
display = "invalid limit {} of type {}: expected natural number.",
|
||||||
|
_0, _1
|
||||||
|
)]
|
||||||
InvalidLimit(String, ValueType),
|
InvalidLimit(String, ValueType),
|
||||||
|
|
||||||
#[fail(display = "mismatched bindings in ground")]
|
#[fail(display = "mismatched bindings in ground")]
|
||||||
|
@ -104,11 +107,11 @@ pub enum AlgebrizerError {
|
||||||
InvalidBinding(PlainSymbol, BindingError),
|
InvalidBinding(PlainSymbol, BindingError),
|
||||||
|
|
||||||
#[fail(display = "{}", _0)]
|
#[fail(display = "{}", _0)]
|
||||||
EdnParseError(#[cause] EdnParseError),
|
EdnParseError(#[cause] ParseError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<EdnParseError> for AlgebrizerError {
|
impl From<ParseError> for AlgebrizerError {
|
||||||
fn from(error: EdnParseError) -> AlgebrizerError {
|
fn from(error: ParseError) -> AlgebrizerError {
|
||||||
AlgebrizerError::EdnParseError(error)
|
AlgebrizerError::EdnParseError(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2016 Mozilla
|
// Copyright 2018 Mozilla
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||||
// this file except in compliance with the License. You may obtain a copy of the
|
// this file except in compliance with the License. You may obtain a copy of the
|
||||||
|
@ -8,5 +8,11 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
|
extern crate failure;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate failure_derive;
|
||||||
|
|
||||||
|
extern crate core_traits;
|
||||||
extern crate edn;
|
extern crate edn;
|
||||||
pub use edn::query::*;
|
|
||||||
|
pub mod errors;
|
|
@ -1,17 +1,22 @@
|
||||||
[package]
|
[package]
|
||||||
name = "mentat_query_algebrizer"
|
name = "mentat_query_algebrizer"
|
||||||
version = "0.0.1"
|
version = "0.0.2"
|
||||||
workspace = ".."
|
workspace = ".."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
failure = "0.1.1"
|
failure = "~0.1"
|
||||||
failure_derive = "0.1.1"
|
|
||||||
|
[dependencies.edn]
|
||||||
|
path = "../edn"
|
||||||
|
|
||||||
[dependencies.mentat_core]
|
[dependencies.mentat_core]
|
||||||
path = "../core"
|
path = "../core"
|
||||||
|
|
||||||
[dependencies.mentat_query]
|
[dependencies.core_traits]
|
||||||
path = "../query"
|
path = "../core-traits"
|
||||||
|
|
||||||
|
[dependencies.query_algebrizer_traits]
|
||||||
|
path = "../query-algebrizer-traits"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
itertools = "0.7"
|
itertools = "~0.10"
|
||||||
|
|
|
@ -8,33 +8,17 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
use mentat_core::{
|
use core_traits::{TypedValue, ValueType, ValueTypeSet};
|
||||||
HasSchema,
|
|
||||||
Schema,
|
|
||||||
SQLValueType,
|
|
||||||
TypedValue,
|
|
||||||
ValueType,
|
|
||||||
ValueTypeSet,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mentat_query::{
|
use mentat_core::{HasSchema, SQLValueType, Schema};
|
||||||
FnArg,
|
|
||||||
NonIntegerConstant,
|
|
||||||
Variable,
|
|
||||||
};
|
|
||||||
|
|
||||||
use clauses::{
|
use edn::query::{FnArg, NonIntegerConstant, Variable};
|
||||||
ConjoiningClauses,
|
|
||||||
};
|
|
||||||
|
|
||||||
use errors::{
|
use crate::clauses::ConjoiningClauses;
|
||||||
AlgebrizerError,
|
|
||||||
Result,
|
|
||||||
};
|
|
||||||
|
|
||||||
use types::{
|
use query_algebrizer_traits::errors::{AlgebrizerError, Result};
|
||||||
EmptyBecause,
|
|
||||||
};
|
use crate::types::EmptyBecause;
|
||||||
|
|
||||||
macro_rules! coerce_to_typed_value {
|
macro_rules! coerce_to_typed_value {
|
||||||
($var: ident, $val: ident, $types: expr, $type: path, $constructor: path) => {{
|
($var: ident, $val: ident, $types: expr, $type: path, $constructor: path) => {{
|
||||||
|
@ -47,7 +31,7 @@ macro_rules! coerce_to_typed_value {
|
||||||
} else {
|
} else {
|
||||||
Val($constructor($val).into())
|
Val($constructor($val).into())
|
||||||
})
|
})
|
||||||
} }
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait ValueTypes {
|
pub(crate) trait ValueTypes {
|
||||||
|
@ -64,7 +48,7 @@ impl ValueTypes for FnArg {
|
||||||
} else {
|
} else {
|
||||||
ValueTypeSet::of_one(ValueType::Long)
|
ValueTypeSet::of_one(ValueType::Long)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
&FnArg::IdentOrKeyword(ref x) => {
|
&FnArg::IdentOrKeyword(ref x) => {
|
||||||
if schema.get_entid(x).is_some() {
|
if schema.get_entid(x).is_some() {
|
||||||
|
@ -72,27 +56,32 @@ impl ValueTypes for FnArg {
|
||||||
} else {
|
} else {
|
||||||
ValueTypeSet::of_one(ValueType::Keyword)
|
ValueTypeSet::of_one(ValueType::Keyword)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
&FnArg::Variable(_) => {
|
&FnArg::Variable(_) => ValueTypeSet::any(),
|
||||||
ValueTypeSet::any()
|
|
||||||
},
|
|
||||||
|
|
||||||
&FnArg::Constant(NonIntegerConstant::BigInteger(_)) => {
|
&FnArg::Constant(NonIntegerConstant::BigInteger(_)) => {
|
||||||
// Not yet implemented.
|
// Not yet implemented.
|
||||||
bail!(AlgebrizerError::UnsupportedArgument)
|
bail!(AlgebrizerError::UnsupportedArgument)
|
||||||
},
|
}
|
||||||
|
|
||||||
// These don't make sense here. TODO: split FnArg into scalar and non-scalar…
|
// These don't make sense here. TODO: split FnArg into scalar and non-scalar…
|
||||||
&FnArg::Vector(_) |
|
&FnArg::Vector(_) | &FnArg::SrcVar(_) => bail!(AlgebrizerError::UnsupportedArgument),
|
||||||
&FnArg::SrcVar(_) => bail!(AlgebrizerError::UnsupportedArgument),
|
|
||||||
|
|
||||||
// These are all straightforward.
|
// These are all straightforward.
|
||||||
&FnArg::Constant(NonIntegerConstant::Boolean(_)) => ValueTypeSet::of_one(ValueType::Boolean),
|
&FnArg::Constant(NonIntegerConstant::Boolean(_)) => {
|
||||||
&FnArg::Constant(NonIntegerConstant::Instant(_)) => ValueTypeSet::of_one(ValueType::Instant),
|
ValueTypeSet::of_one(ValueType::Boolean)
|
||||||
|
}
|
||||||
|
&FnArg::Constant(NonIntegerConstant::Instant(_)) => {
|
||||||
|
ValueTypeSet::of_one(ValueType::Instant)
|
||||||
|
}
|
||||||
&FnArg::Constant(NonIntegerConstant::Uuid(_)) => ValueTypeSet::of_one(ValueType::Uuid),
|
&FnArg::Constant(NonIntegerConstant::Uuid(_)) => ValueTypeSet::of_one(ValueType::Uuid),
|
||||||
&FnArg::Constant(NonIntegerConstant::Float(_)) => ValueTypeSet::of_one(ValueType::Double),
|
&FnArg::Constant(NonIntegerConstant::Float(_)) => {
|
||||||
&FnArg::Constant(NonIntegerConstant::Text(_)) => ValueTypeSet::of_one(ValueType::String),
|
ValueTypeSet::of_one(ValueType::Double)
|
||||||
|
}
|
||||||
|
&FnArg::Constant(NonIntegerConstant::Text(_)) => {
|
||||||
|
ValueTypeSet::of_one(ValueType::String)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,7 +97,13 @@ impl ConjoiningClauses {
|
||||||
/// The conversion depends on, and can fail because of:
|
/// The conversion depends on, and can fail because of:
|
||||||
/// - Existing known types of a variable to which this arg will be bound.
|
/// - Existing known types of a variable to which this arg will be bound.
|
||||||
/// - Existing bindings of a variable `FnArg`.
|
/// - Existing bindings of a variable `FnArg`.
|
||||||
pub(crate) fn typed_value_from_arg<'s>(&self, schema: &'s Schema, var: &Variable, arg: FnArg, known_types: ValueTypeSet) -> Result<ValueConversion> {
|
pub(crate) fn typed_value_from_arg<'s>(
|
||||||
|
&self,
|
||||||
|
schema: &'s Schema,
|
||||||
|
var: &Variable,
|
||||||
|
arg: FnArg,
|
||||||
|
known_types: ValueTypeSet,
|
||||||
|
) -> Result<ValueConversion> {
|
||||||
use self::ValueConversion::*;
|
use self::ValueConversion::*;
|
||||||
if known_types.is_empty() {
|
if known_types.is_empty() {
|
||||||
// If this happens, it likely means the pattern has already failed!
|
// If this happens, it likely means the pattern has already failed!
|
||||||
|
@ -121,7 +116,7 @@ impl ConjoiningClauses {
|
||||||
|
|
||||||
let constrained_types;
|
let constrained_types;
|
||||||
if let Some(required) = self.required_types.get(var) {
|
if let Some(required) = self.required_types.get(var) {
|
||||||
constrained_types = known_types.intersection(required);
|
constrained_types = known_types.intersection(*required);
|
||||||
} else {
|
} else {
|
||||||
constrained_types = known_types;
|
constrained_types = known_types;
|
||||||
}
|
}
|
||||||
|
@ -129,22 +124,24 @@ impl ConjoiningClauses {
|
||||||
match arg {
|
match arg {
|
||||||
// Longs are potentially ambiguous: they might be longs or entids.
|
// Longs are potentially ambiguous: they might be longs or entids.
|
||||||
FnArg::EntidOrInteger(x) => {
|
FnArg::EntidOrInteger(x) => {
|
||||||
match (ValueType::Ref.accommodates_integer(x),
|
match (
|
||||||
|
ValueType::Ref.accommodates_integer(x),
|
||||||
constrained_types.contains(ValueType::Ref),
|
constrained_types.contains(ValueType::Ref),
|
||||||
constrained_types.contains(ValueType::Long)) {
|
constrained_types.contains(ValueType::Long),
|
||||||
|
) {
|
||||||
(true, true, true) => {
|
(true, true, true) => {
|
||||||
// Ambiguous: this arg could be an entid or a long.
|
// Ambiguous: this arg could be an entid or a long.
|
||||||
// We default to long.
|
// We default to long.
|
||||||
Ok(Val(TypedValue::Long(x)))
|
Ok(Val(TypedValue::Long(x)))
|
||||||
},
|
}
|
||||||
(true, true, false) => {
|
(true, true, false) => {
|
||||||
// This can only be a ref.
|
// This can only be a ref.
|
||||||
Ok(Val(TypedValue::Ref(x)))
|
Ok(Val(TypedValue::Ref(x)))
|
||||||
},
|
}
|
||||||
(_, false, true) => {
|
(_, false, true) => {
|
||||||
// This can only be a long.
|
// This can only be a long.
|
||||||
Ok(Val(TypedValue::Long(x)))
|
Ok(Val(TypedValue::Long(x)))
|
||||||
},
|
}
|
||||||
(false, true, _) => {
|
(false, true, _) => {
|
||||||
// This isn't a valid ref, but that's the type to which this must conform!
|
// This isn't a valid ref, but that's the type to which this must conform!
|
||||||
Ok(Impossible(EmptyBecause::TypeMismatch {
|
Ok(Impossible(EmptyBecause::TypeMismatch {
|
||||||
|
@ -152,7 +149,7 @@ impl ConjoiningClauses {
|
||||||
existing: known_types,
|
existing: known_types,
|
||||||
desired: ValueTypeSet::of_longs(),
|
desired: ValueTypeSet::of_longs(),
|
||||||
}))
|
}))
|
||||||
},
|
}
|
||||||
(_, false, false) => {
|
(_, false, false) => {
|
||||||
// Non-overlapping type sets.
|
// Non-overlapping type sets.
|
||||||
Ok(Impossible(EmptyBecause::TypeMismatch {
|
Ok(Impossible(EmptyBecause::TypeMismatch {
|
||||||
|
@ -160,38 +157,36 @@ impl ConjoiningClauses {
|
||||||
existing: known_types,
|
existing: known_types,
|
||||||
desired: ValueTypeSet::of_longs(),
|
desired: ValueTypeSet::of_longs(),
|
||||||
}))
|
}))
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If you definitely want to look up an ident, do it before running the query.
|
// If you definitely want to look up an ident, do it before running the query.
|
||||||
FnArg::IdentOrKeyword(x) => {
|
FnArg::IdentOrKeyword(x) => {
|
||||||
match (constrained_types.contains(ValueType::Ref),
|
match (
|
||||||
constrained_types.contains(ValueType::Keyword)) {
|
constrained_types.contains(ValueType::Ref),
|
||||||
|
constrained_types.contains(ValueType::Keyword),
|
||||||
|
) {
|
||||||
(true, true) => {
|
(true, true) => {
|
||||||
// Ambiguous: this could be a keyword or an ident.
|
// Ambiguous: this could be a keyword or an ident.
|
||||||
// Default to keyword.
|
// Default to keyword.
|
||||||
Ok(Val(x.into()))
|
Ok(Val(x.into()))
|
||||||
},
|
}
|
||||||
(true, false) => {
|
(true, false) => {
|
||||||
// This can only be an ident. Look it up!
|
// This can only be an ident. Look it up!
|
||||||
match schema.get_entid(&x).map(|k| k.into()) {
|
match schema.get_entid(&x).map(|k| k.into()) {
|
||||||
Some(e) => Ok(Val(e)),
|
Some(e) => Ok(Val(e)),
|
||||||
None => Ok(Impossible(EmptyBecause::UnresolvedIdent(x.clone()))),
|
None => Ok(Impossible(EmptyBecause::UnresolvedIdent(x.clone()))),
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
(false, true) => {
|
(false, true) => Ok(Val(TypedValue::Keyword(x.into()))),
|
||||||
Ok(Val(TypedValue::Keyword(x.into())))
|
(false, false) => Ok(Impossible(EmptyBecause::TypeMismatch {
|
||||||
},
|
|
||||||
(false, false) => {
|
|
||||||
Ok(Impossible(EmptyBecause::TypeMismatch {
|
|
||||||
var: var.clone(),
|
var: var.clone(),
|
||||||
existing: known_types,
|
existing: known_types,
|
||||||
desired: ValueTypeSet::of_keywords(),
|
desired: ValueTypeSet::of_keywords(),
|
||||||
}))
|
})),
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
FnArg::Variable(in_var) => {
|
FnArg::Variable(in_var) => {
|
||||||
// TODO: technically you could ground an existing variable inside the query….
|
// TODO: technically you could ground an existing variable inside the query….
|
||||||
|
@ -206,33 +201,32 @@ impl ConjoiningClauses {
|
||||||
// This is a restriction we will eventually relax: we don't yet have a way
|
// This is a restriction we will eventually relax: we don't yet have a way
|
||||||
// to collect variables as part of a computed table or substitution.
|
// to collect variables as part of a computed table or substitution.
|
||||||
bail!(AlgebrizerError::UnboundVariable((*in_var.0).clone()))
|
bail!(AlgebrizerError::UnboundVariable((*in_var.0).clone()))
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This isn't implemented yet.
|
// This isn't implemented yet.
|
||||||
FnArg::Constant(NonIntegerConstant::BigInteger(_)) => unimplemented!(),
|
FnArg::Constant(NonIntegerConstant::BigInteger(_)) => unimplemented!(),
|
||||||
|
|
||||||
// These don't make sense here.
|
// These don't make sense here.
|
||||||
FnArg::Vector(_) |
|
FnArg::Vector(_) | FnArg::SrcVar(_) => bail!(AlgebrizerError::InvalidGroundConstant),
|
||||||
FnArg::SrcVar(_) => bail!(AlgebrizerError::InvalidGroundConstant),
|
|
||||||
|
|
||||||
// These are all straightforward.
|
// These are all straightforward.
|
||||||
FnArg::Constant(NonIntegerConstant::Boolean(x)) => {
|
FnArg::Constant(NonIntegerConstant::Boolean(x)) => {
|
||||||
coerce_to_typed_value!(var, x, known_types, ValueType::Boolean, TypedValue::Boolean)
|
coerce_to_typed_value!(var, x, known_types, ValueType::Boolean, TypedValue::Boolean)
|
||||||
},
|
}
|
||||||
FnArg::Constant(NonIntegerConstant::Instant(x)) => {
|
FnArg::Constant(NonIntegerConstant::Instant(x)) => {
|
||||||
coerce_to_typed_value!(var, x, known_types, ValueType::Instant, TypedValue::Instant)
|
coerce_to_typed_value!(var, x, known_types, ValueType::Instant, TypedValue::Instant)
|
||||||
},
|
}
|
||||||
FnArg::Constant(NonIntegerConstant::Uuid(x)) => {
|
FnArg::Constant(NonIntegerConstant::Uuid(x)) => {
|
||||||
coerce_to_typed_value!(var, x, known_types, ValueType::Uuid, TypedValue::Uuid)
|
coerce_to_typed_value!(var, x, known_types, ValueType::Uuid, TypedValue::Uuid)
|
||||||
},
|
}
|
||||||
FnArg::Constant(NonIntegerConstant::Float(x)) => {
|
FnArg::Constant(NonIntegerConstant::Float(x)) => {
|
||||||
coerce_to_typed_value!(var, x, known_types, ValueType::Double, TypedValue::Double)
|
coerce_to_typed_value!(var, x, known_types, ValueType::Double, TypedValue::Double)
|
||||||
},
|
}
|
||||||
FnArg::Constant(NonIntegerConstant::Text(x)) => {
|
FnArg::Constant(NonIntegerConstant::Text(x)) => {
|
||||||
coerce_to_typed_value!(var, x, known_types, ValueType::String, TypedValue::String)
|
coerce_to_typed_value!(var, x, known_types, ValueType::String, TypedValue::String)
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,62 +8,50 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
use mentat_core::{
|
use core_traits::{TypedValue, ValueType};
|
||||||
HasSchema,
|
|
||||||
TypedValue,
|
use mentat_core::HasSchema;
|
||||||
ValueType,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mentat_core::util::Either;
|
use mentat_core::util::Either;
|
||||||
|
|
||||||
use mentat_query::{
|
use edn::query::{Binding, FnArg, NonIntegerConstant, SrcVar, VariableOrPlaceholder, WhereFn};
|
||||||
Binding,
|
|
||||||
FnArg,
|
use crate::clauses::ConjoiningClauses;
|
||||||
NonIntegerConstant,
|
|
||||||
SrcVar,
|
use query_algebrizer_traits::errors::{AlgebrizerError, BindingError, Result};
|
||||||
VariableOrPlaceholder,
|
|
||||||
WhereFn,
|
use crate::types::{
|
||||||
|
Column, ColumnConstraint, DatomsColumn, DatomsTable, EmptyBecause, FulltextColumn,
|
||||||
|
QualifiedAlias, QueryValue, SourceAlias,
|
||||||
};
|
};
|
||||||
|
|
||||||
use clauses::{
|
use crate::Known;
|
||||||
ConjoiningClauses,
|
|
||||||
};
|
|
||||||
|
|
||||||
use errors::{
|
|
||||||
AlgebrizerError,
|
|
||||||
BindingError,
|
|
||||||
Result,
|
|
||||||
};
|
|
||||||
|
|
||||||
use types::{
|
|
||||||
Column,
|
|
||||||
ColumnConstraint,
|
|
||||||
DatomsColumn,
|
|
||||||
DatomsTable,
|
|
||||||
EmptyBecause,
|
|
||||||
FulltextColumn,
|
|
||||||
QualifiedAlias,
|
|
||||||
QueryValue,
|
|
||||||
SourceAlias,
|
|
||||||
};
|
|
||||||
|
|
||||||
use Known;
|
|
||||||
|
|
||||||
impl ConjoiningClauses {
|
impl ConjoiningClauses {
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
pub(crate) fn apply_fulltext(&mut self, known: Known, where_fn: WhereFn) -> Result<()> {
|
pub(crate) fn apply_fulltext(&mut self, known: Known, where_fn: WhereFn) -> Result<()> {
|
||||||
if where_fn.args.len() != 3 {
|
if where_fn.args.len() != 3 {
|
||||||
bail!(AlgebrizerError::InvalidNumberOfArguments(where_fn.operator.clone(), where_fn.args.len(), 3));
|
bail!(AlgebrizerError::InvalidNumberOfArguments(
|
||||||
|
where_fn.operator.clone(),
|
||||||
|
where_fn.args.len(),
|
||||||
|
3
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if where_fn.binding.is_empty() {
|
if where_fn.binding.is_empty() {
|
||||||
// The binding must introduce at least one bound variable.
|
// The binding must introduce at least one bound variable.
|
||||||
bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::NoBoundVariable));
|
bail!(AlgebrizerError::InvalidBinding(
|
||||||
|
where_fn.operator.clone(),
|
||||||
|
BindingError::NoBoundVariable
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !where_fn.binding.is_valid() {
|
if !where_fn.binding.is_valid() {
|
||||||
// The binding must not duplicate bound variables.
|
// The binding must not duplicate bound variables.
|
||||||
bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::RepeatedBoundVariable));
|
bail!(AlgebrizerError::InvalidBinding(
|
||||||
|
where_fn.operator.clone(),
|
||||||
|
BindingError::RepeatedBoundVariable
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// We should have exactly four bindings. Destructure them now.
|
// We should have exactly four bindings. Destructure them now.
|
||||||
|
@ -71,31 +59,45 @@ impl ConjoiningClauses {
|
||||||
Binding::BindRel(bindings) => {
|
Binding::BindRel(bindings) => {
|
||||||
let bindings_count = bindings.len();
|
let bindings_count = bindings.len();
|
||||||
if bindings_count < 1 || bindings_count > 4 {
|
if bindings_count < 1 || bindings_count > 4 {
|
||||||
bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(),
|
bail!(AlgebrizerError::InvalidBinding(
|
||||||
|
where_fn.operator.clone(),
|
||||||
BindingError::InvalidNumberOfBindings {
|
BindingError::InvalidNumberOfBindings {
|
||||||
number: bindings.len(),
|
number: bindings.len(),
|
||||||
expected: 4,
|
expected: 4,
|
||||||
})
|
}
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
bindings
|
bindings
|
||||||
},
|
}
|
||||||
Binding::BindScalar(_) |
|
Binding::BindScalar(_) | Binding::BindTuple(_) | Binding::BindColl(_) => {
|
||||||
Binding::BindTuple(_) |
|
bail!(AlgebrizerError::InvalidBinding(
|
||||||
Binding::BindColl(_) => bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::ExpectedBindRel)),
|
where_fn.operator.clone(),
|
||||||
|
BindingError::ExpectedBindRel
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let mut bindings = bindings.into_iter();
|
let mut bindings = bindings.into_iter();
|
||||||
let b_entity = bindings.next().unwrap();
|
let b_entity = bindings.next().unwrap();
|
||||||
let b_value = bindings.next().unwrap_or(VariableOrPlaceholder::Placeholder);
|
let b_value = bindings
|
||||||
let b_tx = bindings.next().unwrap_or(VariableOrPlaceholder::Placeholder);
|
.next()
|
||||||
let b_score = bindings.next().unwrap_or(VariableOrPlaceholder::Placeholder);
|
.unwrap_or(VariableOrPlaceholder::Placeholder);
|
||||||
|
let b_tx = bindings
|
||||||
|
.next()
|
||||||
|
.unwrap_or(VariableOrPlaceholder::Placeholder);
|
||||||
|
let b_score = bindings
|
||||||
|
.next()
|
||||||
|
.unwrap_or(VariableOrPlaceholder::Placeholder);
|
||||||
|
|
||||||
let mut args = where_fn.args.into_iter();
|
let mut args = where_fn.args.into_iter();
|
||||||
|
|
||||||
// TODO: process source variables.
|
// TODO(gburd): process source variables.
|
||||||
match args.next().unwrap() {
|
match args.next().unwrap() {
|
||||||
FnArg::SrcVar(SrcVar::DefaultSrc) => {},
|
FnArg::SrcVar(SrcVar::DefaultSrc) => {}
|
||||||
_ => bail!(AlgebrizerError::InvalidArgument(where_fn.operator.clone(), "source variable", 0)),
|
_ => bail!(AlgebrizerError::InvalidArgument(
|
||||||
|
where_fn.operator.clone(),
|
||||||
|
"source variable",
|
||||||
|
0
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
|
|
||||||
let schema = known.schema;
|
let schema = known.schema;
|
||||||
|
@ -114,25 +116,27 @@ impl ConjoiningClauses {
|
||||||
// TODO: allow non-constant attributes.
|
// TODO: allow non-constant attributes.
|
||||||
match self.bound_value(&v) {
|
match self.bound_value(&v) {
|
||||||
Some(TypedValue::Ref(entid)) => Some(entid),
|
Some(TypedValue::Ref(entid)) => Some(entid),
|
||||||
Some(tv) => {
|
Some(tv) => bail!(AlgebrizerError::InputTypeDisagreement(
|
||||||
bail!(AlgebrizerError::InputTypeDisagreement(v.name().clone(), ValueType::Ref, tv.value_type()))
|
v.name(),
|
||||||
},
|
ValueType::Ref,
|
||||||
None => {
|
tv.value_type()
|
||||||
bail!(AlgebrizerError::UnboundVariable((*v.0).clone()))
|
)),
|
||||||
|
None => bail!(AlgebrizerError::UnboundVariable((*v.0).clone())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
// An unknown ident, or an entity that isn't present in the store, or isn't a fulltext
|
// An unknown ident, or an entity that isn't present in the store, or isn't a fulltext
|
||||||
// attribute, is likely enough to be a coding error that we choose to bail instead of
|
// attribute, is likely enough to be a coding error that we choose to bail instead of
|
||||||
// marking the pattern as known-empty.
|
// marking the pattern as known-empty.
|
||||||
let a = a.ok_or(AlgebrizerError::InvalidArgument(where_fn.operator.clone(), "attribute", 1))?;
|
let op = where_fn.operator.clone(); //TODO(gburd): remove me...
|
||||||
let attribute = schema.attribute_for_entid(a)
|
let a = a.ok_or_else(move || AlgebrizerError::InvalidArgument(op, "attribute", 1))?;
|
||||||
|
let op = where_fn.operator.clone(); //TODO(gburd): remove me...
|
||||||
|
let attribute = schema
|
||||||
|
.attribute_for_entid(a)
|
||||||
.cloned()
|
.cloned()
|
||||||
.ok_or(AlgebrizerError::InvalidArgument(where_fn.operator.clone(),
|
.ok_or_else(move || AlgebrizerError::InvalidArgument(op, "attribute", 1))?;
|
||||||
"attribute", 1))?;
|
|
||||||
|
|
||||||
if !attribute.fulltext {
|
if !attribute.fulltext {
|
||||||
// We can never get results from a non-fulltext attribute!
|
// We can never get results from a non-fulltext attribute!
|
||||||
|
@ -146,16 +150,27 @@ impl ConjoiningClauses {
|
||||||
|
|
||||||
// We do a fulltext lookup by joining the fulltext values table against datoms -- just
|
// We do a fulltext lookup by joining the fulltext values table against datoms -- just
|
||||||
// like applying a pattern, but two tables contribute instead of one.
|
// like applying a pattern, but two tables contribute instead of one.
|
||||||
self.from.push(SourceAlias(DatomsTable::FulltextValues, fulltext_values_alias.clone()));
|
self.from.push(SourceAlias(
|
||||||
self.from.push(SourceAlias(DatomsTable::Datoms, datoms_table_alias.clone()));
|
DatomsTable::FulltextValues,
|
||||||
|
fulltext_values_alias.clone(),
|
||||||
|
));
|
||||||
|
self.from
|
||||||
|
.push(SourceAlias(DatomsTable::Datoms, datoms_table_alias.clone()));
|
||||||
|
|
||||||
// TODO: constrain the type in the more general cases (e.g., `a` is a var).
|
// TODO: constrain the type in the more general cases (e.g., `a` is a var).
|
||||||
self.constrain_attribute(datoms_table_alias.clone(), a);
|
self.constrain_attribute(datoms_table_alias.clone(), a);
|
||||||
|
|
||||||
// Join the datoms table to the fulltext values table.
|
// Join the datoms table to the fulltext values table.
|
||||||
self.wheres.add_intersection(ColumnConstraint::Equals(
|
self.wheres.add_intersection(ColumnConstraint::Equals(
|
||||||
QualifiedAlias(datoms_table_alias.clone(), Column::Fixed(DatomsColumn::Value)),
|
QualifiedAlias(
|
||||||
QueryValue::Column(QualifiedAlias(fulltext_values_alias.clone(), Column::Fulltext(FulltextColumn::Rowid)))));
|
datoms_table_alias.clone(),
|
||||||
|
Column::Fixed(DatomsColumn::Value),
|
||||||
|
),
|
||||||
|
QueryValue::Column(QualifiedAlias(
|
||||||
|
fulltext_values_alias.clone(),
|
||||||
|
Column::Fulltext(FulltextColumn::Rowid),
|
||||||
|
)),
|
||||||
|
));
|
||||||
|
|
||||||
// `search` is either text or a variable.
|
// `search` is either text or a variable.
|
||||||
// If it's simple text, great.
|
// If it's simple text, great.
|
||||||
|
@ -164,18 +179,24 @@ impl ConjoiningClauses {
|
||||||
// - It's not already bound, but it's a defined input of type Text. Not yet implemented: TODO.
|
// - It's not already bound, but it's a defined input of type Text. Not yet implemented: TODO.
|
||||||
// - It's not bound. The query cannot be algebrized.
|
// - It's not bound. The query cannot be algebrized.
|
||||||
let search: Either<TypedValue, QualifiedAlias> = match args.next().unwrap() {
|
let search: Either<TypedValue, QualifiedAlias> = match args.next().unwrap() {
|
||||||
FnArg::Constant(NonIntegerConstant::Text(s)) => {
|
FnArg::Constant(NonIntegerConstant::Text(s)) => Either::Left(TypedValue::String(s)),
|
||||||
Either::Left(TypedValue::String(s))
|
|
||||||
},
|
|
||||||
FnArg::Variable(in_var) => {
|
FnArg::Variable(in_var) => {
|
||||||
match self.bound_value(&in_var) {
|
match self.bound_value(&in_var) {
|
||||||
Some(t @ TypedValue::String(_)) => Either::Left(t),
|
Some(t @ TypedValue::String(_)) => Either::Left(t),
|
||||||
Some(_) => bail!(AlgebrizerError::InvalidArgument(where_fn.operator.clone(), "string", 2)),
|
Some(_) => bail!(AlgebrizerError::InvalidArgument(
|
||||||
|
where_fn.operator.clone(),
|
||||||
|
"string",
|
||||||
|
2
|
||||||
|
)),
|
||||||
None => {
|
None => {
|
||||||
// Regardless of whether we'll be providing a string later, or the value
|
// Regardless of whether we'll be providing a string later, or the value
|
||||||
// comes from a column, it must be a string.
|
// comes from a column, it must be a string.
|
||||||
if self.known_type(&in_var) != Some(ValueType::String) {
|
if self.known_type(&in_var) != Some(ValueType::String) {
|
||||||
bail!(AlgebrizerError::InvalidArgument(where_fn.operator.clone(), "string", 2))
|
bail!(AlgebrizerError::InvalidArgument(
|
||||||
|
where_fn.operator.clone(),
|
||||||
|
"string",
|
||||||
|
2
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.input_variables.contains(&in_var) {
|
if self.input_variables.contains(&in_var) {
|
||||||
|
@ -185,18 +206,24 @@ impl ConjoiningClauses {
|
||||||
} else {
|
} else {
|
||||||
// It must be bound earlier in the query. We already established that
|
// It must be bound earlier in the query. We already established that
|
||||||
// it must be a string column.
|
// it must be a string column.
|
||||||
if let Some(binding) = self.column_bindings
|
if let Some(binding) = self
|
||||||
|
.column_bindings
|
||||||
.get(&in_var)
|
.get(&in_var)
|
||||||
.and_then(|bindings| bindings.get(0).cloned()) {
|
.and_then(|bindings| bindings.get(0).cloned())
|
||||||
|
{
|
||||||
Either::Right(binding)
|
Either::Right(binding)
|
||||||
} else {
|
} else {
|
||||||
bail!(AlgebrizerError::UnboundVariable((*in_var.0).clone()))
|
bail!(AlgebrizerError::UnboundVariable((*in_var.0).clone()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
_ => bail!(AlgebrizerError::InvalidArgument(where_fn.operator.clone(), "string", 2)),
|
}
|
||||||
|
_ => bail!(AlgebrizerError::InvalidArgument(
|
||||||
|
where_fn.operator.clone(),
|
||||||
|
"string",
|
||||||
|
2
|
||||||
|
)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let qv = match search {
|
let qv = match search {
|
||||||
|
@ -204,9 +231,13 @@ impl ConjoiningClauses {
|
||||||
Either::Right(qa) => QueryValue::Column(qa),
|
Either::Right(qa) => QueryValue::Column(qa),
|
||||||
};
|
};
|
||||||
|
|
||||||
let constraint = ColumnConstraint::Matches(QualifiedAlias(fulltext_values_alias.clone(),
|
let constraint = ColumnConstraint::Matches(
|
||||||
Column::Fulltext(FulltextColumn::Text)),
|
QualifiedAlias(
|
||||||
qv);
|
fulltext_values_alias.clone(),
|
||||||
|
Column::Fulltext(FulltextColumn::Text),
|
||||||
|
),
|
||||||
|
qv,
|
||||||
|
);
|
||||||
self.wheres.add_intersection(constraint);
|
self.wheres.add_intersection(constraint);
|
||||||
|
|
||||||
if let VariableOrPlaceholder::Variable(ref var) = b_entity {
|
if let VariableOrPlaceholder::Variable(ref var) = b_entity {
|
||||||
|
@ -216,7 +247,12 @@ impl ConjoiningClauses {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.bind_column_to_var(schema, datoms_table_alias.clone(), DatomsColumn::Entity, var.clone());
|
self.bind_column_to_var(
|
||||||
|
schema,
|
||||||
|
datoms_table_alias.clone(),
|
||||||
|
DatomsColumn::Entity,
|
||||||
|
var.clone(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let VariableOrPlaceholder::Variable(ref var) = b_value {
|
if let VariableOrPlaceholder::Variable(ref var) = b_value {
|
||||||
|
@ -226,7 +262,12 @@ impl ConjoiningClauses {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.bind_column_to_var(schema, fulltext_values_alias.clone(), Column::Fulltext(FulltextColumn::Text), var.clone());
|
self.bind_column_to_var(
|
||||||
|
schema,
|
||||||
|
fulltext_values_alias,
|
||||||
|
Column::Fulltext(FulltextColumn::Text),
|
||||||
|
var.clone(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let VariableOrPlaceholder::Variable(ref var) = b_tx {
|
if let VariableOrPlaceholder::Variable(ref var) = b_tx {
|
||||||
|
@ -236,7 +277,7 @@ impl ConjoiningClauses {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.bind_column_to_var(schema, datoms_table_alias.clone(), DatomsColumn::Tx, var.clone());
|
self.bind_column_to_var(schema, datoms_table_alias, DatomsColumn::Tx, var.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let VariableOrPlaceholder::Variable(ref var) = b_score {
|
if let VariableOrPlaceholder::Variable(ref var) = b_score {
|
||||||
|
@ -245,7 +286,10 @@ impl ConjoiningClauses {
|
||||||
|
|
||||||
// We do not allow the score to be bound.
|
// We do not allow the score to be bound.
|
||||||
if self.value_bindings.contains_key(var) || self.input_variables.contains(var) {
|
if self.value_bindings.contains_key(var) || self.input_variables.contains(var) {
|
||||||
bail!(AlgebrizerError::InvalidBinding(var.name(), BindingError::UnexpectedBinding));
|
bail!(AlgebrizerError::InvalidBinding(
|
||||||
|
var.name(),
|
||||||
|
BindingError::UnexpectedBinding
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// We bind the value ourselves. This handily takes care of substituting into existing uses.
|
// We bind the value ourselves. This handily takes care of substituting into existing uses.
|
||||||
|
@ -261,24 +305,13 @@ impl ConjoiningClauses {
|
||||||
mod testing {
|
mod testing {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use mentat_core::{
|
use core_traits::{Attribute, ValueType};
|
||||||
Attribute,
|
|
||||||
Schema,
|
|
||||||
ValueType,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mentat_query::{
|
use mentat_core::Schema;
|
||||||
Binding,
|
|
||||||
FnArg,
|
|
||||||
Keyword,
|
|
||||||
PlainSymbol,
|
|
||||||
Variable,
|
|
||||||
};
|
|
||||||
|
|
||||||
use clauses::{
|
use edn::query::{Binding, FnArg, Keyword, PlainSymbol, Variable};
|
||||||
add_attribute,
|
|
||||||
associate_ident,
|
use crate::clauses::{add_attribute, associate_ident};
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_apply_fulltext() {
|
fn test_apply_fulltext() {
|
||||||
|
@ -286,35 +319,49 @@ mod testing {
|
||||||
let mut schema = Schema::default();
|
let mut schema = Schema::default();
|
||||||
|
|
||||||
associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 101);
|
associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 101);
|
||||||
add_attribute(&mut schema, 101, Attribute {
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
|
101,
|
||||||
|
Attribute {
|
||||||
value_type: ValueType::String,
|
value_type: ValueType::String,
|
||||||
fulltext: false,
|
fulltext: false,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
associate_ident(&mut schema, Keyword::namespaced("foo", "fts"), 100);
|
associate_ident(&mut schema, Keyword::namespaced("foo", "fts"), 100);
|
||||||
add_attribute(&mut schema, 100, Attribute {
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
|
100,
|
||||||
|
Attribute {
|
||||||
value_type: ValueType::String,
|
value_type: ValueType::String,
|
||||||
index: true,
|
index: true,
|
||||||
fulltext: true,
|
fulltext: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let known = Known::for_schema(&schema);
|
let known = Known::for_schema(&schema);
|
||||||
|
|
||||||
let op = PlainSymbol::plain("fulltext");
|
let op = PlainSymbol::plain("fulltext");
|
||||||
cc.apply_fulltext(known, WhereFn {
|
cc.apply_fulltext(
|
||||||
|
known,
|
||||||
|
WhereFn {
|
||||||
operator: op,
|
operator: op,
|
||||||
args: vec![
|
args: vec![
|
||||||
FnArg::SrcVar(SrcVar::DefaultSrc),
|
FnArg::SrcVar(SrcVar::DefaultSrc),
|
||||||
FnArg::IdentOrKeyword(Keyword::namespaced("foo", "fts")),
|
FnArg::IdentOrKeyword(Keyword::namespaced("foo", "fts")),
|
||||||
FnArg::Constant("needle".into()),
|
FnArg::Constant("needle".into()),
|
||||||
],
|
],
|
||||||
binding: Binding::BindRel(vec![VariableOrPlaceholder::Variable(Variable::from_valid_name("?entity")),
|
binding: Binding::BindRel(vec![
|
||||||
|
VariableOrPlaceholder::Variable(Variable::from_valid_name("?entity")),
|
||||||
VariableOrPlaceholder::Variable(Variable::from_valid_name("?value")),
|
VariableOrPlaceholder::Variable(Variable::from_valid_name("?value")),
|
||||||
VariableOrPlaceholder::Variable(Variable::from_valid_name("?tx")),
|
VariableOrPlaceholder::Variable(Variable::from_valid_name("?tx")),
|
||||||
VariableOrPlaceholder::Variable(Variable::from_valid_name("?score"))]),
|
VariableOrPlaceholder::Variable(Variable::from_valid_name("?score")),
|
||||||
}).expect("to be able to apply_fulltext");
|
]),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("to be able to apply_fulltext");
|
||||||
|
|
||||||
assert!(!cc.is_known_empty());
|
assert!(!cc.is_known_empty());
|
||||||
|
|
||||||
|
@ -325,54 +372,136 @@ mod testing {
|
||||||
let clauses = cc.wheres;
|
let clauses = cc.wheres;
|
||||||
assert_eq!(clauses.len(), 3);
|
assert_eq!(clauses.len(), 3);
|
||||||
|
|
||||||
assert_eq!(clauses.0[0], ColumnConstraint::Equals(QualifiedAlias("datoms01".to_string(), Column::Fixed(DatomsColumn::Attribute)),
|
assert_eq!(
|
||||||
QueryValue::Entid(100)).into());
|
clauses.0[0],
|
||||||
assert_eq!(clauses.0[1], ColumnConstraint::Equals(QualifiedAlias("datoms01".to_string(), Column::Fixed(DatomsColumn::Value)),
|
ColumnConstraint::Equals(
|
||||||
QueryValue::Column(QualifiedAlias("fulltext_values00".to_string(), Column::Fulltext(FulltextColumn::Rowid)))).into());
|
QualifiedAlias(
|
||||||
assert_eq!(clauses.0[2], ColumnConstraint::Matches(QualifiedAlias("fulltext_values00".to_string(), Column::Fulltext(FulltextColumn::Text)),
|
"datoms01".to_string(),
|
||||||
QueryValue::TypedValue("needle".into())).into());
|
Column::Fixed(DatomsColumn::Attribute)
|
||||||
|
),
|
||||||
|
QueryValue::Entid(100)
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
clauses.0[1],
|
||||||
|
ColumnConstraint::Equals(
|
||||||
|
QualifiedAlias("datoms01".to_string(), Column::Fixed(DatomsColumn::Value)),
|
||||||
|
QueryValue::Column(QualifiedAlias(
|
||||||
|
"fulltext_values00".to_string(),
|
||||||
|
Column::Fulltext(FulltextColumn::Rowid)
|
||||||
|
))
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
clauses.0[2],
|
||||||
|
ColumnConstraint::Matches(
|
||||||
|
QualifiedAlias(
|
||||||
|
"fulltext_values00".to_string(),
|
||||||
|
Column::Fulltext(FulltextColumn::Text)
|
||||||
|
),
|
||||||
|
QueryValue::TypedValue("needle".into())
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
|
||||||
let bindings = cc.column_bindings;
|
let bindings = cc.column_bindings;
|
||||||
assert_eq!(bindings.len(), 3);
|
assert_eq!(bindings.len(), 3);
|
||||||
|
|
||||||
assert_eq!(bindings.get(&Variable::from_valid_name("?entity")).expect("column binding for ?entity").clone(),
|
assert_eq!(
|
||||||
vec![QualifiedAlias("datoms01".to_string(), Column::Fixed(DatomsColumn::Entity))]);
|
bindings
|
||||||
assert_eq!(bindings.get(&Variable::from_valid_name("?value")).expect("column binding for ?value").clone(),
|
.get(&Variable::from_valid_name("?entity"))
|
||||||
vec![QualifiedAlias("fulltext_values00".to_string(), Column::Fulltext(FulltextColumn::Text))]);
|
.expect("column binding for ?entity")
|
||||||
assert_eq!(bindings.get(&Variable::from_valid_name("?tx")).expect("column binding for ?tx").clone(),
|
.clone(),
|
||||||
vec![QualifiedAlias("datoms01".to_string(), Column::Fixed(DatomsColumn::Tx))]);
|
vec![QualifiedAlias(
|
||||||
|
"datoms01".to_string(),
|
||||||
|
Column::Fixed(DatomsColumn::Entity)
|
||||||
|
)]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
bindings
|
||||||
|
.get(&Variable::from_valid_name("?value"))
|
||||||
|
.expect("column binding for ?value")
|
||||||
|
.clone(),
|
||||||
|
vec![QualifiedAlias(
|
||||||
|
"fulltext_values00".to_string(),
|
||||||
|
Column::Fulltext(FulltextColumn::Text)
|
||||||
|
)]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
bindings
|
||||||
|
.get(&Variable::from_valid_name("?tx"))
|
||||||
|
.expect("column binding for ?tx")
|
||||||
|
.clone(),
|
||||||
|
vec![QualifiedAlias(
|
||||||
|
"datoms01".to_string(),
|
||||||
|
Column::Fixed(DatomsColumn::Tx)
|
||||||
|
)]
|
||||||
|
);
|
||||||
|
|
||||||
// Score is a value binding.
|
// Score is a value binding.
|
||||||
let values = cc.value_bindings;
|
let values = cc.value_bindings;
|
||||||
assert_eq!(values.get(&Variable::from_valid_name("?score")).expect("column binding for ?score").clone(),
|
assert_eq!(
|
||||||
TypedValue::Double(0.0.into()));
|
values
|
||||||
|
.get(&Variable::from_valid_name("?score"))
|
||||||
|
.expect("column binding for ?score")
|
||||||
|
.clone(),
|
||||||
|
TypedValue::Double(0.0.into())
|
||||||
|
);
|
||||||
|
|
||||||
let known_types = cc.known_types;
|
let known_types = cc.known_types;
|
||||||
assert_eq!(known_types.len(), 4);
|
assert_eq!(known_types.len(), 4);
|
||||||
|
|
||||||
assert_eq!(known_types.get(&Variable::from_valid_name("?entity")).expect("known types for ?entity").clone(),
|
assert_eq!(
|
||||||
vec![ValueType::Ref].into_iter().collect());
|
known_types
|
||||||
assert_eq!(known_types.get(&Variable::from_valid_name("?value")).expect("known types for ?value").clone(),
|
.get(&Variable::from_valid_name("?entity"))
|
||||||
vec![ValueType::String].into_iter().collect());
|
.expect("known types for ?entity")
|
||||||
assert_eq!(known_types.get(&Variable::from_valid_name("?tx")).expect("known types for ?tx").clone(),
|
.clone(),
|
||||||
vec![ValueType::Ref].into_iter().collect());
|
vec![ValueType::Ref].into_iter().collect()
|
||||||
assert_eq!(known_types.get(&Variable::from_valid_name("?score")).expect("known types for ?score").clone(),
|
);
|
||||||
vec![ValueType::Double].into_iter().collect());
|
assert_eq!(
|
||||||
|
known_types
|
||||||
|
.get(&Variable::from_valid_name("?value"))
|
||||||
|
.expect("known types for ?value")
|
||||||
|
.clone(),
|
||||||
|
vec![ValueType::String].into_iter().collect()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
known_types
|
||||||
|
.get(&Variable::from_valid_name("?tx"))
|
||||||
|
.expect("known types for ?tx")
|
||||||
|
.clone(),
|
||||||
|
vec![ValueType::Ref].into_iter().collect()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
known_types
|
||||||
|
.get(&Variable::from_valid_name("?score"))
|
||||||
|
.expect("known types for ?score")
|
||||||
|
.clone(),
|
||||||
|
vec![ValueType::Double].into_iter().collect()
|
||||||
|
);
|
||||||
|
|
||||||
let mut cc = ConjoiningClauses::default();
|
let mut cc = ConjoiningClauses::default();
|
||||||
let op = PlainSymbol::plain("fulltext");
|
let op = PlainSymbol::plain("fulltext");
|
||||||
cc.apply_fulltext(known, WhereFn {
|
cc.apply_fulltext(
|
||||||
|
known,
|
||||||
|
WhereFn {
|
||||||
operator: op,
|
operator: op,
|
||||||
args: vec![
|
args: vec![
|
||||||
FnArg::SrcVar(SrcVar::DefaultSrc),
|
FnArg::SrcVar(SrcVar::DefaultSrc),
|
||||||
FnArg::IdentOrKeyword(Keyword::namespaced("foo", "bar")),
|
FnArg::IdentOrKeyword(Keyword::namespaced("foo", "bar")),
|
||||||
FnArg::Constant("needle".into()),
|
FnArg::Constant("needle".into()),
|
||||||
],
|
],
|
||||||
binding: Binding::BindRel(vec![VariableOrPlaceholder::Variable(Variable::from_valid_name("?entity")),
|
binding: Binding::BindRel(vec![
|
||||||
|
VariableOrPlaceholder::Variable(Variable::from_valid_name("?entity")),
|
||||||
VariableOrPlaceholder::Variable(Variable::from_valid_name("?value")),
|
VariableOrPlaceholder::Variable(Variable::from_valid_name("?value")),
|
||||||
VariableOrPlaceholder::Variable(Variable::from_valid_name("?tx")),
|
VariableOrPlaceholder::Variable(Variable::from_valid_name("?tx")),
|
||||||
VariableOrPlaceholder::Variable(Variable::from_valid_name("?score"))]),
|
VariableOrPlaceholder::Variable(Variable::from_valid_name("?score")),
|
||||||
}).expect("to be able to apply_fulltext");
|
]),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("to be able to apply_fulltext");
|
||||||
|
|
||||||
// It's not a fulltext attribute, so the CC cannot yield results.
|
// It's not a fulltext attribute, so the CC cannot yield results.
|
||||||
assert!(cc.is_known_empty());
|
assert!(cc.is_known_empty());
|
||||||
|
|
|
@ -8,49 +8,34 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
use mentat_core::{
|
use core_traits::{TypedValue, ValueType, ValueTypeSet};
|
||||||
Schema,
|
|
||||||
TypedValue,
|
|
||||||
ValueType,
|
|
||||||
ValueTypeSet,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mentat_query::{
|
use mentat_core::Schema;
|
||||||
Binding,
|
|
||||||
FnArg,
|
|
||||||
Variable,
|
|
||||||
VariableOrPlaceholder,
|
|
||||||
WhereFn,
|
|
||||||
};
|
|
||||||
|
|
||||||
use clauses::{
|
use edn::query::{Binding, FnArg, Variable, VariableOrPlaceholder, WhereFn};
|
||||||
ConjoiningClauses,
|
|
||||||
PushComputed,
|
|
||||||
};
|
|
||||||
|
|
||||||
use clauses::convert::ValueConversion;
|
use crate::clauses::{ConjoiningClauses, PushComputed};
|
||||||
|
|
||||||
use errors::{
|
use crate::clauses::convert::ValueConversion;
|
||||||
AlgebrizerError,
|
|
||||||
BindingError,
|
|
||||||
Result,
|
|
||||||
};
|
|
||||||
|
|
||||||
use types::{
|
use query_algebrizer_traits::errors::{AlgebrizerError, BindingError, Result};
|
||||||
ComputedTable,
|
|
||||||
EmptyBecause,
|
|
||||||
SourceAlias,
|
|
||||||
VariableColumn,
|
|
||||||
};
|
|
||||||
|
|
||||||
use Known;
|
use crate::types::{ComputedTable, EmptyBecause, SourceAlias, VariableColumn};
|
||||||
|
|
||||||
|
use crate::Known;
|
||||||
|
|
||||||
impl ConjoiningClauses {
|
impl ConjoiningClauses {
|
||||||
/// Take a relation: a matrix of values which will successively bind to named variables of
|
/// Take a relation: a matrix of values which will successively bind to named variables of
|
||||||
/// the provided types.
|
/// the provided types.
|
||||||
/// Construct a computed table to yield this relation.
|
/// Construct a computed table to yield this relation.
|
||||||
/// This function will panic if some invariants are not met.
|
/// This function will panic if some invariants are not met.
|
||||||
fn collect_named_bindings<'s>(&mut self, schema: &'s Schema, names: Vec<Variable>, types: Vec<ValueType>, values: Vec<TypedValue>) {
|
fn collect_named_bindings<'s>(
|
||||||
|
&mut self,
|
||||||
|
schema: &'s Schema,
|
||||||
|
names: Vec<Variable>,
|
||||||
|
types: Vec<ValueType>,
|
||||||
|
values: Vec<TypedValue>,
|
||||||
|
) {
|
||||||
if values.is_empty() {
|
if values.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -62,7 +47,7 @@ impl ConjoiningClauses {
|
||||||
|
|
||||||
let named_values = ComputedTable::NamedValues {
|
let named_values = ComputedTable::NamedValues {
|
||||||
names: names.clone(),
|
names: names.clone(),
|
||||||
values: values,
|
values,
|
||||||
};
|
};
|
||||||
|
|
||||||
let table = self.computed_tables.push_computed(named_values);
|
let table = self.computed_tables.push_computed(named_values);
|
||||||
|
@ -71,13 +56,23 @@ impl ConjoiningClauses {
|
||||||
// Stitch the computed table into column_bindings, so we get cross-linking.
|
// Stitch the computed table into column_bindings, so we get cross-linking.
|
||||||
for (name, ty) in names.iter().zip(types.into_iter()) {
|
for (name, ty) in names.iter().zip(types.into_iter()) {
|
||||||
self.constrain_var_to_type(name.clone(), ty);
|
self.constrain_var_to_type(name.clone(), ty);
|
||||||
self.bind_column_to_var(schema, alias.clone(), VariableColumn::Variable(name.clone()), name.clone());
|
self.bind_column_to_var(
|
||||||
|
schema,
|
||||||
|
alias.clone(),
|
||||||
|
VariableColumn::Variable(name.clone()),
|
||||||
|
name.clone(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.from.push(SourceAlias(table, alias));
|
self.from.push(SourceAlias(table, alias));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_ground_place<'s>(&mut self, schema: &'s Schema, var: VariableOrPlaceholder, arg: FnArg) -> Result<()> {
|
fn apply_ground_place<'s>(
|
||||||
|
&mut self,
|
||||||
|
schema: &'s Schema,
|
||||||
|
var: VariableOrPlaceholder,
|
||||||
|
arg: FnArg,
|
||||||
|
) -> Result<()> {
|
||||||
match var {
|
match var {
|
||||||
VariableOrPlaceholder::Placeholder => Ok(()),
|
VariableOrPlaceholder::Placeholder => Ok(()),
|
||||||
VariableOrPlaceholder::Variable(var) => self.apply_ground_var(schema, var, arg),
|
VariableOrPlaceholder::Variable(var) => self.apply_ground_var(schema, var, arg),
|
||||||
|
@ -86,14 +81,19 @@ impl ConjoiningClauses {
|
||||||
|
|
||||||
/// Constrain the CC to associate the given var with the given ground argument.
|
/// Constrain the CC to associate the given var with the given ground argument.
|
||||||
/// Marks known-empty on failure.
|
/// Marks known-empty on failure.
|
||||||
fn apply_ground_var<'s>(&mut self, schema: &'s Schema, var: Variable, arg: FnArg) -> Result<()> {
|
fn apply_ground_var<'s>(
|
||||||
|
&mut self,
|
||||||
|
schema: &'s Schema,
|
||||||
|
var: Variable,
|
||||||
|
arg: FnArg,
|
||||||
|
) -> Result<()> {
|
||||||
let known_types = self.known_type_set(&var);
|
let known_types = self.known_type_set(&var);
|
||||||
match self.typed_value_from_arg(schema, &var, arg, known_types)? {
|
match self.typed_value_from_arg(schema, &var, arg, known_types)? {
|
||||||
ValueConversion::Val(value) => self.apply_ground_value(var, value),
|
ValueConversion::Val(value) => self.apply_ground_value(var, value),
|
||||||
ValueConversion::Impossible(because) => {
|
ValueConversion::Impossible(because) => {
|
||||||
self.mark_known_empty(because);
|
self.mark_known_empty(because);
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,13 +103,13 @@ impl ConjoiningClauses {
|
||||||
if existing != value {
|
if existing != value {
|
||||||
self.mark_known_empty(EmptyBecause::ConflictingBindings {
|
self.mark_known_empty(EmptyBecause::ConflictingBindings {
|
||||||
var: var.clone(),
|
var: var.clone(),
|
||||||
existing: existing.clone(),
|
existing,
|
||||||
desired: value,
|
desired: value,
|
||||||
});
|
});
|
||||||
return Ok(())
|
return Ok(());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.bind_value(&var, value.clone());
|
self.bind_value(&var, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -117,19 +117,29 @@ impl ConjoiningClauses {
|
||||||
|
|
||||||
pub(crate) fn apply_ground(&mut self, known: Known, where_fn: WhereFn) -> Result<()> {
|
pub(crate) fn apply_ground(&mut self, known: Known, where_fn: WhereFn) -> Result<()> {
|
||||||
if where_fn.args.len() != 1 {
|
if where_fn.args.len() != 1 {
|
||||||
bail!(AlgebrizerError::InvalidNumberOfArguments(where_fn.operator.clone(), where_fn.args.len(), 1));
|
bail!(AlgebrizerError::InvalidNumberOfArguments(
|
||||||
|
where_fn.operator.clone(),
|
||||||
|
where_fn.args.len(),
|
||||||
|
1
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut args = where_fn.args.into_iter();
|
let mut args = where_fn.args.into_iter();
|
||||||
|
|
||||||
if where_fn.binding.is_empty() {
|
if where_fn.binding.is_empty() {
|
||||||
// The binding must introduce at least one bound variable.
|
// The binding must introduce at least one bound variable.
|
||||||
bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::NoBoundVariable));
|
bail!(AlgebrizerError::InvalidBinding(
|
||||||
|
where_fn.operator,
|
||||||
|
BindingError::NoBoundVariable
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !where_fn.binding.is_valid() {
|
if !where_fn.binding.is_valid() {
|
||||||
// The binding must not duplicate bound variables.
|
// The binding must not duplicate bound variables.
|
||||||
bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::RepeatedBoundVariable));
|
bail!(AlgebrizerError::InvalidBinding(
|
||||||
|
where_fn.operator,
|
||||||
|
BindingError::RepeatedBoundVariable
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let schema = known.schema;
|
let schema = known.schema;
|
||||||
|
@ -138,8 +148,7 @@ impl ConjoiningClauses {
|
||||||
// we can immediately substitute the value as a known value in the CC, additionally
|
// we can immediately substitute the value as a known value in the CC, additionally
|
||||||
// generating a WHERE clause if columns have already been bound.
|
// generating a WHERE clause if columns have already been bound.
|
||||||
match (where_fn.binding, args.next().unwrap()) {
|
match (where_fn.binding, args.next().unwrap()) {
|
||||||
(Binding::BindScalar(var), constant) =>
|
(Binding::BindScalar(var), constant) => self.apply_ground_var(schema, var, constant),
|
||||||
self.apply_ground_var(schema, var, constant),
|
|
||||||
|
|
||||||
(Binding::BindTuple(places), FnArg::Vector(children)) => {
|
(Binding::BindTuple(places), FnArg::Vector(children)) => {
|
||||||
// Just the same, but we bind more than one column at a time.
|
// Just the same, but we bind more than one column at a time.
|
||||||
|
@ -151,7 +160,7 @@ impl ConjoiningClauses {
|
||||||
self.apply_ground_place(schema, place, arg)? // TODO: short-circuit on impossible.
|
self.apply_ground_place(schema, place, arg)? // TODO: short-circuit on impossible.
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
|
|
||||||
// Collection bindings and rel bindings are similar in that they are both
|
// Collection bindings and rel bindings are similar in that they are both
|
||||||
// implemented as a subquery with a projection list and a set of values.
|
// implemented as a subquery with a projection list and a set of values.
|
||||||
|
@ -167,27 +176,29 @@ impl ConjoiningClauses {
|
||||||
// Check that every value has the same type.
|
// Check that every value has the same type.
|
||||||
let mut accumulated_types = ValueTypeSet::none();
|
let mut accumulated_types = ValueTypeSet::none();
|
||||||
let mut skip: Option<EmptyBecause> = None;
|
let mut skip: Option<EmptyBecause> = None;
|
||||||
let values = children.into_iter()
|
let values = children
|
||||||
|
.into_iter()
|
||||||
.filter_map(|arg| -> Option<Result<TypedValue>> {
|
.filter_map(|arg| -> Option<Result<TypedValue>> {
|
||||||
// We need to get conversion errors out.
|
// We need to get conversion errors out.
|
||||||
// We also want to mark known-empty on impossibilty, but
|
// We also want to mark known-empty on impossibility, but
|
||||||
// still detect serious errors.
|
// still detect serious errors.
|
||||||
match self.typed_value_from_arg(schema, &var, arg, known_types) {
|
match self.typed_value_from_arg(schema, &var, arg, known_types) {
|
||||||
Ok(ValueConversion::Val(tv)) => {
|
Ok(ValueConversion::Val(tv)) => {
|
||||||
if accumulated_types.insert(tv.value_type()) &&
|
if accumulated_types.insert(tv.value_type())
|
||||||
!accumulated_types.is_unit() {
|
&& !accumulated_types.is_unit()
|
||||||
|
{
|
||||||
// Values not all of the same type.
|
// Values not all of the same type.
|
||||||
Some(Err(AlgebrizerError::InvalidGroundConstant.into()))
|
Some(Err(AlgebrizerError::InvalidGroundConstant))
|
||||||
} else {
|
} else {
|
||||||
Some(Ok(tv))
|
Some(Ok(tv))
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Ok(ValueConversion::Impossible(because)) => {
|
Ok(ValueConversion::Impossible(because)) => {
|
||||||
// Skip this value.
|
// Skip this value.
|
||||||
skip = Some(because);
|
skip = Some(because);
|
||||||
None
|
None
|
||||||
},
|
}
|
||||||
Err(e) => Some(Err(e.into())),
|
Err(e) => Some(Err(e)),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<TypedValue>>>()?;
|
.collect::<Result<Vec<TypedValue>>>()?;
|
||||||
|
@ -200,11 +211,11 @@ impl ConjoiningClauses {
|
||||||
|
|
||||||
// Otherwise, we now have the values and the type.
|
// Otherwise, we now have the values and the type.
|
||||||
let types = vec![accumulated_types.exemplar().unwrap()];
|
let types = vec![accumulated_types.exemplar().unwrap()];
|
||||||
let names = vec![var.clone()];
|
let names = vec![var];
|
||||||
|
|
||||||
self.collect_named_bindings(schema, names, types, values);
|
self.collect_named_bindings(schema, names, types, values);
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
|
|
||||||
(Binding::BindRel(places), FnArg::Vector(rows)) => {
|
(Binding::BindRel(places), FnArg::Vector(rows)) => {
|
||||||
if rows.is_empty() {
|
if rows.is_empty() {
|
||||||
|
@ -213,17 +224,20 @@ impl ConjoiningClauses {
|
||||||
|
|
||||||
// Grab the known types to which these args must conform, and track
|
// Grab the known types to which these args must conform, and track
|
||||||
// the places that won't be bound in the output.
|
// the places that won't be bound in the output.
|
||||||
let template: Vec<Option<(Variable, ValueTypeSet)>> =
|
let template: Vec<Option<(Variable, ValueTypeSet)>> = places
|
||||||
places.iter()
|
.iter()
|
||||||
.map(|x| match x {
|
.map(|x| match x {
|
||||||
&VariableOrPlaceholder::Placeholder => None,
|
VariableOrPlaceholder::Placeholder => None,
|
||||||
&VariableOrPlaceholder::Variable(ref v) => Some((v.clone(), self.known_type_set(v))),
|
VariableOrPlaceholder::Variable(ref v) => {
|
||||||
|
Some((v.clone(), self.known_type_set(v)))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// The expected 'width' of the matrix is the number of named variables.
|
// The expected 'width' of the matrix is the number of named variables.
|
||||||
let full_width = places.len();
|
let full_width = places.len();
|
||||||
let names: Vec<Variable> = places.into_iter().filter_map(|x| x.into_var()).collect();
|
let names: Vec<Variable> =
|
||||||
|
places.into_iter().filter_map(|x| x.into_var()).collect();
|
||||||
let expected_width = names.len();
|
let expected_width = names.len();
|
||||||
let expected_rows = rows.len();
|
let expected_rows = rows.len();
|
||||||
|
|
||||||
|
@ -257,14 +271,14 @@ impl ConjoiningClauses {
|
||||||
// Convert each item in the row.
|
// Convert each item in the row.
|
||||||
// If any value in the row is impossible, then skip the row.
|
// If any value in the row is impossible, then skip the row.
|
||||||
// If all rows are impossible, fail the entire CC.
|
// If all rows are impossible, fail the entire CC.
|
||||||
if let &Some(ref pair) = pair {
|
if let Some(ref pair) = pair {
|
||||||
match self.typed_value_from_arg(schema, &pair.0, col, pair.1)? {
|
match self.typed_value_from_arg(schema, &pair.0, col, pair.1)? {
|
||||||
ValueConversion::Val(tv) => vals.push(tv),
|
ValueConversion::Val(tv) => vals.push(tv),
|
||||||
ValueConversion::Impossible(because) => {
|
ValueConversion::Impossible(because) => {
|
||||||
// Skip this row. It cannot produce bindings.
|
// Skip this row. It cannot produce bindings.
|
||||||
skip = Some(because);
|
skip = Some(because);
|
||||||
break;
|
break;
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -276,7 +290,10 @@ impl ConjoiningClauses {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accumulate the values into the matrix and the types into the type set.
|
// Accumulate the values into the matrix and the types into the type set.
|
||||||
for (val, acc) in vals.into_iter().zip(accumulated_types_for_columns.iter_mut()) {
|
for (val, acc) in vals
|
||||||
|
.into_iter()
|
||||||
|
.zip(accumulated_types_for_columns.iter_mut())
|
||||||
|
{
|
||||||
let inserted = acc.insert(val.value_type());
|
let inserted = acc.insert(val.value_type());
|
||||||
if inserted && !acc.is_unit() {
|
if inserted && !acc.is_unit() {
|
||||||
// Heterogeneous types.
|
// Heterogeneous types.
|
||||||
|
@ -284,8 +301,7 @@ impl ConjoiningClauses {
|
||||||
}
|
}
|
||||||
matrix.push(val);
|
matrix.push(val);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
|
||||||
_ => bail!(AlgebrizerError::InvalidGroundConstant),
|
_ => bail!(AlgebrizerError::InvalidGroundConstant),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -306,12 +322,13 @@ impl ConjoiningClauses {
|
||||||
// type tags. If and when we want to algebrize in two phases and allow for
|
// type tags. If and when we want to algebrize in two phases and allow for
|
||||||
// late-binding input variables, we'll probably be able to loosen this restriction
|
// late-binding input variables, we'll probably be able to loosen this restriction
|
||||||
// with little penalty.
|
// with little penalty.
|
||||||
let types = accumulated_types_for_columns.into_iter()
|
let types = accumulated_types_for_columns
|
||||||
|
.into_iter()
|
||||||
.map(|x| x.exemplar().unwrap())
|
.map(|x| x.exemplar().unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
self.collect_named_bindings(schema, names, types, matrix);
|
self.collect_named_bindings(schema, names, types, matrix);
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
(_, _) => bail!(AlgebrizerError::InvalidGroundConstant),
|
(_, _) => bail!(AlgebrizerError::InvalidGroundConstant),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -321,23 +338,11 @@ impl ConjoiningClauses {
|
||||||
mod testing {
|
mod testing {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use mentat_core::{
|
use core_traits::{Attribute, ValueType};
|
||||||
Attribute,
|
|
||||||
ValueType,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mentat_query::{
|
use edn::query::{Binding, FnArg, Keyword, PlainSymbol, Variable};
|
||||||
Binding,
|
|
||||||
FnArg,
|
|
||||||
Keyword,
|
|
||||||
PlainSymbol,
|
|
||||||
Variable,
|
|
||||||
};
|
|
||||||
|
|
||||||
use clauses::{
|
use crate::clauses::{add_attribute, associate_ident};
|
||||||
add_attribute,
|
|
||||||
associate_ident,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_apply_ground() {
|
fn test_apply_ground() {
|
||||||
|
@ -347,25 +352,31 @@ mod testing {
|
||||||
let mut schema = Schema::default();
|
let mut schema = Schema::default();
|
||||||
|
|
||||||
associate_ident(&mut schema, Keyword::namespaced("foo", "fts"), 100);
|
associate_ident(&mut schema, Keyword::namespaced("foo", "fts"), 100);
|
||||||
add_attribute(&mut schema, 100, Attribute {
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
|
100,
|
||||||
|
Attribute {
|
||||||
value_type: ValueType::String,
|
value_type: ValueType::String,
|
||||||
index: true,
|
index: true,
|
||||||
fulltext: true,
|
fulltext: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let known = Known::for_schema(&schema);
|
let known = Known::for_schema(&schema);
|
||||||
|
|
||||||
// It's awkward enough to write these expansions that we give the details for the simplest
|
// It's awkward enough to write these expansions that we give the details for the simplest
|
||||||
// case only. See the tests of the translator for more extensive (albeit looser) coverage.
|
// case only. See the tests of the translator for more extensive (albeit looser) coverage.
|
||||||
let op = PlainSymbol::plain("ground");
|
let op = PlainSymbol::plain("ground");
|
||||||
cc.apply_ground(known, WhereFn {
|
cc.apply_ground(
|
||||||
|
known,
|
||||||
|
WhereFn {
|
||||||
operator: op,
|
operator: op,
|
||||||
args: vec![
|
args: vec![FnArg::EntidOrInteger(10)],
|
||||||
FnArg::EntidOrInteger(10),
|
|
||||||
],
|
|
||||||
binding: Binding::BindScalar(vz.clone()),
|
binding: Binding::BindScalar(vz.clone()),
|
||||||
}).expect("to be able to apply_ground");
|
},
|
||||||
|
)
|
||||||
|
.expect("to be able to apply_ground");
|
||||||
|
|
||||||
assert!(!cc.is_known_empty());
|
assert!(!cc.is_known_empty());
|
||||||
|
|
||||||
|
@ -381,12 +392,16 @@ mod testing {
|
||||||
|
|
||||||
let known_types = cc.known_types;
|
let known_types = cc.known_types;
|
||||||
assert_eq!(known_types.len(), 1);
|
assert_eq!(known_types.len(), 1);
|
||||||
assert_eq!(known_types.get(&vz).expect("to know the type of ?z"),
|
assert_eq!(
|
||||||
&ValueTypeSet::of_one(ValueType::Long));
|
known_types.get(&vz).expect("to know the type of ?z"),
|
||||||
|
&ValueTypeSet::of_one(ValueType::Long)
|
||||||
|
);
|
||||||
|
|
||||||
let value_bindings = cc.value_bindings;
|
let value_bindings = cc.value_bindings;
|
||||||
assert_eq!(value_bindings.len(), 1);
|
assert_eq!(value_bindings.len(), 1);
|
||||||
assert_eq!(value_bindings.get(&vz).expect("to have a value for ?z"),
|
assert_eq!(
|
||||||
&TypedValue::Long(10)); // We default to Long instead of entid.
|
value_bindings.get(&vz).expect("to have a value for ?z"),
|
||||||
|
&TypedValue::Long(10)
|
||||||
|
); // We default to Long instead of entid.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,19 +10,11 @@
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use mentat_core::{
|
use core_traits::{TypedValue, ValueType};
|
||||||
TypedValue,
|
|
||||||
ValueType,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mentat_query::{
|
use edn::query::Variable;
|
||||||
Variable,
|
|
||||||
};
|
|
||||||
|
|
||||||
use errors::{
|
use query_algebrizer_traits::errors::{AlgebrizerError, Result};
|
||||||
AlgebrizerError,
|
|
||||||
Result,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Define the inputs to a query. This is in two parts: a set of values known now, and a set of
|
/// Define the inputs to a query. This is in two parts: a set of values known now, and a set of
|
||||||
/// types known now.
|
/// types known now.
|
||||||
|
@ -59,13 +51,18 @@ impl QueryInputs {
|
||||||
|
|
||||||
pub fn with_values(values: BTreeMap<Variable, TypedValue>) -> QueryInputs {
|
pub fn with_values(values: BTreeMap<Variable, TypedValue>) -> QueryInputs {
|
||||||
QueryInputs {
|
QueryInputs {
|
||||||
types: values.iter().map(|(var, val)| (var.clone(), val.value_type())).collect(),
|
types: values
|
||||||
values: values,
|
.iter()
|
||||||
|
.map(|(var, val)| (var.clone(), val.value_type()))
|
||||||
|
.collect(),
|
||||||
|
values,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(mut types: BTreeMap<Variable, ValueType>,
|
pub fn new(
|
||||||
values: BTreeMap<Variable, TypedValue>) -> Result<QueryInputs> {
|
mut types: BTreeMap<Variable, ValueType>,
|
||||||
|
values: BTreeMap<Variable, TypedValue>,
|
||||||
|
) -> Result<QueryInputs> {
|
||||||
// Make sure that the types of the values agree with those in types, and collect.
|
// Make sure that the types of the values agree with those in types, and collect.
|
||||||
for (var, v) in values.iter() {
|
for (var, v) in values.iter() {
|
||||||
let t = v.value_type();
|
let t = v.value_type();
|
||||||
|
@ -76,6 +73,6 @@ impl QueryInputs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(QueryInputs { types: types, values: values })
|
Ok(QueryInputs { types, values })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,93 +10,46 @@
|
||||||
|
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
|
|
||||||
use std::collections::{
|
use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
||||||
BTreeMap,
|
|
||||||
BTreeSet,
|
|
||||||
VecDeque,
|
|
||||||
};
|
|
||||||
|
|
||||||
use std::collections::btree_map::{
|
use std::collections::btree_map::Entry;
|
||||||
Entry,
|
|
||||||
};
|
|
||||||
|
|
||||||
use std::fmt::{
|
use std::fmt::{Debug, Formatter};
|
||||||
Debug,
|
|
||||||
Formatter,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mentat_core::{
|
use core_traits::{Attribute, Entid, KnownEntid, TypedValue, ValueType, ValueTypeSet};
|
||||||
Attribute,
|
|
||||||
Cloned,
|
use mentat_core::{Cloned, HasSchema, Schema};
|
||||||
Entid,
|
|
||||||
HasSchema,
|
|
||||||
KnownEntid,
|
|
||||||
Schema,
|
|
||||||
TypedValue,
|
|
||||||
ValueType,
|
|
||||||
ValueTypeSet,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mentat_core::counter::RcCounter;
|
use mentat_core::counter::RcCounter;
|
||||||
|
|
||||||
use mentat_query::{
|
use edn::query::{Element, FindSpec, Keyword, PatternNonValuePlace, Pull, Variable, WhereClause};
|
||||||
Element,
|
|
||||||
FindSpec,
|
|
||||||
Keyword,
|
|
||||||
Pull,
|
|
||||||
Variable,
|
|
||||||
WhereClause,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mentat_query::{
|
use query_algebrizer_traits::errors::{AlgebrizerError, Result};
|
||||||
PatternNonValuePlace,
|
|
||||||
};
|
|
||||||
|
|
||||||
use errors::{
|
use crate::types::{
|
||||||
AlgebrizerError,
|
Column, ColumnConstraint, ColumnIntersection, ComputedTable, DatomsColumn, DatomsTable,
|
||||||
Result,
|
EmptyBecause, EvolvedNonValuePlace, EvolvedPattern, EvolvedValuePlace, FulltextColumn,
|
||||||
};
|
PlaceOrEmpty, QualifiedAlias, QueryValue, SourceAlias, TableAlias,
|
||||||
|
|
||||||
use types::{
|
|
||||||
ColumnConstraint,
|
|
||||||
ColumnIntersection,
|
|
||||||
ComputedTable,
|
|
||||||
Column,
|
|
||||||
DatomsColumn,
|
|
||||||
DatomsTable,
|
|
||||||
EmptyBecause,
|
|
||||||
EvolvedNonValuePlace,
|
|
||||||
EvolvedPattern,
|
|
||||||
EvolvedValuePlace,
|
|
||||||
FulltextColumn,
|
|
||||||
PlaceOrEmpty,
|
|
||||||
QualifiedAlias,
|
|
||||||
QueryValue,
|
|
||||||
SourceAlias,
|
|
||||||
TableAlias,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mod convert; // Converting args to values.
|
mod convert; // Converting args to values.
|
||||||
mod inputs;
|
mod inputs;
|
||||||
mod or;
|
|
||||||
mod not;
|
mod not;
|
||||||
|
mod or;
|
||||||
mod pattern;
|
mod pattern;
|
||||||
mod predicate;
|
mod predicate;
|
||||||
mod resolve;
|
mod resolve;
|
||||||
|
|
||||||
mod ground;
|
|
||||||
mod fulltext;
|
mod fulltext;
|
||||||
|
mod ground;
|
||||||
mod tx_log_api;
|
mod tx_log_api;
|
||||||
mod where_fn;
|
mod where_fn;
|
||||||
|
|
||||||
use validate::{
|
use crate::validate::{validate_not_join, validate_or_join};
|
||||||
validate_not_join,
|
|
||||||
validate_or_join,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use self::inputs::QueryInputs;
|
pub use self::inputs::QueryInputs;
|
||||||
|
|
||||||
use Known;
|
use crate::Known;
|
||||||
|
|
||||||
trait Contains<K, T> {
|
trait Contains<K, T> {
|
||||||
fn when_contains<F: FnOnce() -> T>(&self, k: &K, f: F) -> Option<T>;
|
fn when_contains<F: FnOnce() -> T>(&self, k: &K, f: F) -> Option<T>;
|
||||||
|
@ -194,8 +147,8 @@ pub struct ConjoiningClauses {
|
||||||
/// A map from var to qualified columns. Used to project.
|
/// A map from var to qualified columns. Used to project.
|
||||||
pub column_bindings: BTreeMap<Variable, Vec<QualifiedAlias>>,
|
pub column_bindings: BTreeMap<Variable, Vec<QualifiedAlias>>,
|
||||||
|
|
||||||
/// A list of variables mentioned in the enclosing query's :in clause. These must all be bound
|
/// A list of variables mentioned in the enclosing query's `:in` clause all of which must be
|
||||||
/// before the query can be executed. TODO: clarify what this means for nested CCs.
|
/// bound before the query can be executed. TODO: clarify what this means for nested CCs.
|
||||||
pub input_variables: BTreeSet<Variable>,
|
pub input_variables: BTreeSet<Variable>,
|
||||||
|
|
||||||
/// In some situations -- e.g., when a query is being run only once -- we know in advance the
|
/// In some situations -- e.g., when a query is being run only once -- we know in advance the
|
||||||
|
@ -227,16 +180,16 @@ pub struct ConjoiningClauses {
|
||||||
|
|
||||||
impl PartialEq for ConjoiningClauses {
|
impl PartialEq for ConjoiningClauses {
|
||||||
fn eq(&self, other: &ConjoiningClauses) -> bool {
|
fn eq(&self, other: &ConjoiningClauses) -> bool {
|
||||||
self.empty_because.eq(&other.empty_because) &&
|
self.empty_because.eq(&other.empty_because)
|
||||||
self.from.eq(&other.from) &&
|
&& self.from.eq(&other.from)
|
||||||
self.computed_tables.eq(&other.computed_tables) &&
|
&& self.computed_tables.eq(&other.computed_tables)
|
||||||
self.wheres.eq(&other.wheres) &&
|
&& self.wheres.eq(&other.wheres)
|
||||||
self.column_bindings.eq(&other.column_bindings) &&
|
&& self.column_bindings.eq(&other.column_bindings)
|
||||||
self.input_variables.eq(&other.input_variables) &&
|
&& self.input_variables.eq(&other.input_variables)
|
||||||
self.value_bindings.eq(&other.value_bindings) &&
|
&& self.value_bindings.eq(&other.value_bindings)
|
||||||
self.known_types.eq(&other.known_types) &&
|
&& self.known_types.eq(&other.known_types)
|
||||||
self.extracted_types.eq(&other.extracted_types) &&
|
&& self.extracted_types.eq(&other.extracted_types)
|
||||||
self.required_types.eq(&other.required_types)
|
&& self.required_types.eq(&other.required_types)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,9 +231,7 @@ impl Default for ConjoiningClauses {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VariableIterator<'a>(
|
pub struct VariableIterator<'a>(::std::collections::btree_map::Keys<'a, Variable, TypedValue>);
|
||||||
::std::collections::btree_map::Keys<'a, Variable, TypedValue>,
|
|
||||||
);
|
|
||||||
|
|
||||||
impl<'a> Iterator for VariableIterator<'a> {
|
impl<'a> Iterator for VariableIterator<'a> {
|
||||||
type Item = &'a Variable;
|
type Item = &'a Variable;
|
||||||
|
@ -303,34 +254,45 @@ impl ConjoiningClauses {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn with_inputs<T>(in_variables: BTreeSet<Variable>, inputs: T) -> ConjoiningClauses
|
pub fn with_inputs<T>(in_variables: BTreeSet<Variable>, inputs: T) -> ConjoiningClauses
|
||||||
where T: Into<Option<QueryInputs>> {
|
where
|
||||||
|
T: Into<Option<QueryInputs>>,
|
||||||
|
{
|
||||||
ConjoiningClauses::with_inputs_and_alias_counter(in_variables, inputs, RcCounter::new())
|
ConjoiningClauses::with_inputs_and_alias_counter(in_variables, inputs, RcCounter::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn with_inputs_and_alias_counter<T>(in_variables: BTreeSet<Variable>,
|
pub(crate) fn with_inputs_and_alias_counter<T>(
|
||||||
|
in_variables: BTreeSet<Variable>,
|
||||||
inputs: T,
|
inputs: T,
|
||||||
alias_counter: RcCounter) -> ConjoiningClauses
|
alias_counter: RcCounter,
|
||||||
where T: Into<Option<QueryInputs>> {
|
) -> ConjoiningClauses
|
||||||
|
where
|
||||||
|
T: Into<Option<QueryInputs>>,
|
||||||
|
{
|
||||||
match inputs.into() {
|
match inputs.into() {
|
||||||
None => ConjoiningClauses::with_alias_counter(alias_counter),
|
None => ConjoiningClauses::with_alias_counter(alias_counter),
|
||||||
Some(QueryInputs { mut types, mut values }) => {
|
Some(QueryInputs {
|
||||||
|
mut types,
|
||||||
|
mut values,
|
||||||
|
}) => {
|
||||||
// Discard any bindings not mentioned in our :in clause.
|
// Discard any bindings not mentioned in our :in clause.
|
||||||
types.keep_intersected_keys(&in_variables);
|
types.keep_intersected_keys(&in_variables);
|
||||||
values.keep_intersected_keys(&in_variables);
|
values.keep_intersected_keys(&in_variables);
|
||||||
|
|
||||||
let mut cc = ConjoiningClauses {
|
let mut cc = ConjoiningClauses {
|
||||||
alias_counter: alias_counter,
|
alias_counter,
|
||||||
input_variables: in_variables,
|
input_variables: in_variables,
|
||||||
value_bindings: values,
|
value_bindings: values,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Pre-fill our type mappings with the types of the input bindings.
|
// Pre-fill our type mappings with the types of the input bindings.
|
||||||
cc.known_types
|
cc.known_types.extend(
|
||||||
.extend(types.iter()
|
types
|
||||||
.map(|(k, v)| (k.clone(), ValueTypeSet::of_one(*v))));
|
.iter()
|
||||||
|
.map(|(k, v)| (k.clone(), ValueTypeSet::of_one(*v))),
|
||||||
|
);
|
||||||
cc
|
cc
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -339,12 +301,8 @@ impl ConjoiningClauses {
|
||||||
impl ConjoiningClauses {
|
impl ConjoiningClauses {
|
||||||
pub(crate) fn derive_types_from_find_spec(&mut self, find_spec: &FindSpec) {
|
pub(crate) fn derive_types_from_find_spec(&mut self, find_spec: &FindSpec) {
|
||||||
for spec in find_spec.columns() {
|
for spec in find_spec.columns() {
|
||||||
match spec {
|
if let Element::Pull(Pull { ref var, .. }) = spec {
|
||||||
&Element::Pull(Pull { ref var, patterns: _ }) => {
|
|
||||||
self.constrain_var_to_type(var.clone(), ValueType::Ref);
|
self.constrain_var_to_type(var.clone(), ValueType::Ref);
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -391,14 +349,18 @@ impl ConjoiningClauses {
|
||||||
// If so, generate a constraint against the primary column.
|
// If so, generate a constraint against the primary column.
|
||||||
if let Some(vec) = self.column_bindings.get(var) {
|
if let Some(vec) = self.column_bindings.get(var) {
|
||||||
if let Some(col) = vec.first() {
|
if let Some(col) = vec.first() {
|
||||||
self.wheres.add_intersection(ColumnConstraint::Equals(col.clone(), QueryValue::TypedValue(value.clone())));
|
self.wheres.add_intersection(ColumnConstraint::Equals(
|
||||||
|
col.clone(),
|
||||||
|
QueryValue::TypedValue(value.clone()),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Are we also trying to figure out the type of the value when the query runs?
|
// Are we also trying to figure out the type of the value when the query runs?
|
||||||
// If so, constrain that!
|
// If so, constrain that!
|
||||||
if let Some(qa) = self.extracted_types.get(&var) {
|
if let Some(qa) = self.extracted_types.get(&var) {
|
||||||
self.wheres.add_intersection(ColumnConstraint::has_unit_type(qa.0.clone(), vt));
|
self.wheres
|
||||||
|
.add_intersection(ColumnConstraint::has_unit_type(qa.0.clone(), vt));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, store the binding for future use.
|
// Finally, store the binding for future use.
|
||||||
|
@ -439,10 +401,19 @@ impl ConjoiningClauses {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn known_type_set(&self, var: &Variable) -> ValueTypeSet {
|
pub fn known_type_set(&self, var: &Variable) -> ValueTypeSet {
|
||||||
self.known_types.get(var).cloned().unwrap_or(ValueTypeSet::any())
|
self.known_types
|
||||||
|
.get(var)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(ValueTypeSet::any)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn bind_column_to_var<C: Into<Column>>(&mut self, schema: &Schema, table: TableAlias, column: C, var: Variable) {
|
pub(crate) fn bind_column_to_var<C: Into<Column>>(
|
||||||
|
&mut self,
|
||||||
|
schema: &Schema,
|
||||||
|
table: TableAlias,
|
||||||
|
column: C,
|
||||||
|
var: Variable,
|
||||||
|
) {
|
||||||
let column = column.into();
|
let column = column.into();
|
||||||
// Do we have an external binding for this?
|
// Do we have an external binding for this?
|
||||||
if let Some(bound_val) = self.bound_value(&var) {
|
if let Some(bound_val) = self.bound_value(&var) {
|
||||||
|
@ -454,18 +425,18 @@ impl ConjoiningClauses {
|
||||||
// We don't need to handle expansion of attributes here. The subquery that
|
// We don't need to handle expansion of attributes here. The subquery that
|
||||||
// produces the variable projection will do so.
|
// produces the variable projection will do so.
|
||||||
self.constrain_column_to_constant(table, column, bound_val);
|
self.constrain_column_to_constant(table, column, bound_val);
|
||||||
},
|
}
|
||||||
|
|
||||||
Column::Transactions(_) => {
|
Column::Transactions(_) => {
|
||||||
self.constrain_column_to_constant(table, column, bound_val);
|
self.constrain_column_to_constant(table, column, bound_val);
|
||||||
},
|
}
|
||||||
|
|
||||||
Column::Fulltext(FulltextColumn::Rowid) |
|
Column::Fulltext(FulltextColumn::Rowid)
|
||||||
Column::Fulltext(FulltextColumn::Text) => {
|
| Column::Fulltext(FulltextColumn::Text) => {
|
||||||
// We never expose `rowid` via queries. We do expose `text`, but only
|
// We never expose `rowid` via queries. We do expose `text`, but only
|
||||||
// indirectly, by joining against `datoms`. Therefore, these are meaningless.
|
// indirectly, by joining against `datoms`. Therefore, these are meaningless.
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
},
|
}
|
||||||
|
|
||||||
Column::Fixed(DatomsColumn::ValueTypeTag) => {
|
Column::Fixed(DatomsColumn::ValueTypeTag) => {
|
||||||
// I'm pretty sure this is meaningless right now, because we will never bind
|
// I'm pretty sure this is meaningless right now, because we will never bind
|
||||||
|
@ -477,18 +448,18 @@ impl ConjoiningClauses {
|
||||||
// [(= (typeof ?y) :db.valueType/double)]]
|
// [(= (typeof ?y) :db.valueType/double)]]
|
||||||
// ```
|
// ```
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
},
|
}
|
||||||
|
|
||||||
// TODO: recognize when the valueType might be a ref and also translate entids there.
|
// TODO: recognize when the valueType might be a ref and also translate entids there.
|
||||||
Column::Fixed(DatomsColumn::Value) => {
|
Column::Fixed(DatomsColumn::Value) => {
|
||||||
self.constrain_column_to_constant(table, column, bound_val);
|
self.constrain_column_to_constant(table, column, bound_val);
|
||||||
},
|
}
|
||||||
|
|
||||||
// These columns can only be entities, so attempt to translate keywords. If we can't
|
// These columns can only be entities, so attempt to translate keywords. If we can't
|
||||||
// get an entity out of the bound value, the pattern cannot produce results.
|
// get an entity out of the bound value, the pattern cannot produce results.
|
||||||
Column::Fixed(DatomsColumn::Attribute) |
|
Column::Fixed(DatomsColumn::Attribute)
|
||||||
Column::Fixed(DatomsColumn::Entity) |
|
| Column::Fixed(DatomsColumn::Entity)
|
||||||
Column::Fixed(DatomsColumn::Tx) => {
|
| Column::Fixed(DatomsColumn::Tx) => {
|
||||||
match bound_val {
|
match bound_val {
|
||||||
TypedValue::Keyword(ref kw) => {
|
TypedValue::Keyword(ref kw) => {
|
||||||
if let Some(entid) = self.entid_for_ident(schema, kw) {
|
if let Some(entid) = self.entid_for_ident(schema, kw) {
|
||||||
|
@ -500,14 +471,14 @@ impl ConjoiningClauses {
|
||||||
// attribute then we should have already marked the pattern as empty.
|
// attribute then we should have already marked the pattern as empty.
|
||||||
self.mark_known_empty(EmptyBecause::UnresolvedIdent(kw.cloned()));
|
self.mark_known_empty(EmptyBecause::UnresolvedIdent(kw.cloned()));
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
TypedValue::Ref(entid) => {
|
TypedValue::Ref(entid) => {
|
||||||
self.constrain_column_to_entity(table, column, entid);
|
self.constrain_column_to_entity(table, column, entid);
|
||||||
},
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// One can't bind an e, a, or tx to something other than an entity.
|
// One can't bind an e, a, or tx to something other than an entity.
|
||||||
self.mark_known_empty(EmptyBecause::InvalidBinding(column, bound_val));
|
self.mark_known_empty(EmptyBecause::InvalidBinding(column, bound_val));
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -521,8 +492,7 @@ impl ConjoiningClauses {
|
||||||
|
|
||||||
// If this is a value, and we don't already know its type or where
|
// If this is a value, and we don't already know its type or where
|
||||||
// to get its type, record that we can get it from this table.
|
// to get its type, record that we can get it from this table.
|
||||||
let needs_type_extraction =
|
let needs_type_extraction = !late_binding && // Never need to extract for bound vars.
|
||||||
!late_binding && // Never need to extract for bound vars.
|
|
||||||
self.known_type(&var).is_none() && // Don't need to extract if we know a single type.
|
self.known_type(&var).is_none() && // Don't need to extract if we know a single type.
|
||||||
!self.extracted_types.contains_key(&var); // We're already extracting the type.
|
!self.extracted_types.contains_key(&var); // We're already extracting the type.
|
||||||
|
|
||||||
|
@ -536,23 +506,42 @@ impl ConjoiningClauses {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.column_bindings.entry(var).or_insert(vec![]).push(alias);
|
self.column_bindings
|
||||||
|
.entry(var)
|
||||||
|
.or_insert_with(Vec::new)
|
||||||
|
.push(alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn constrain_column_to_constant<C: Into<Column>>(&mut self, table: TableAlias, column: C, constant: TypedValue) {
|
pub(crate) fn constrain_column_to_constant<C: Into<Column>>(
|
||||||
|
&mut self,
|
||||||
|
table: TableAlias,
|
||||||
|
column: C,
|
||||||
|
constant: TypedValue,
|
||||||
|
) {
|
||||||
match constant {
|
match constant {
|
||||||
// Be a little more explicit.
|
// Be a little more explicit.
|
||||||
TypedValue::Ref(entid) => self.constrain_column_to_entity(table, column, entid),
|
TypedValue::Ref(entid) => self.constrain_column_to_entity(table, column, entid),
|
||||||
_ => {
|
_ => {
|
||||||
let column = column.into();
|
let column = column.into();
|
||||||
self.wheres.add_intersection(ColumnConstraint::Equals(QualifiedAlias(table, column), QueryValue::TypedValue(constant)))
|
self.wheres.add_intersection(ColumnConstraint::Equals(
|
||||||
},
|
QualifiedAlias(table, column),
|
||||||
|
QueryValue::TypedValue(constant),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn constrain_column_to_entity<C: Into<Column>>(&mut self, table: TableAlias, column: C, entity: Entid) {
|
pub(crate) fn constrain_column_to_entity<C: Into<Column>>(
|
||||||
|
&mut self,
|
||||||
|
table: TableAlias,
|
||||||
|
column: C,
|
||||||
|
entity: Entid,
|
||||||
|
) {
|
||||||
let column = column.into();
|
let column = column.into();
|
||||||
self.wheres.add_intersection(ColumnConstraint::Equals(QualifiedAlias(table, column), QueryValue::Entid(entity)))
|
self.wheres.add_intersection(ColumnConstraint::Equals(
|
||||||
|
QualifiedAlias(table, column),
|
||||||
|
QueryValue::Entid(entity),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn constrain_attribute(&mut self, table: TableAlias, attribute: Entid) {
|
pub(crate) fn constrain_attribute(&mut self, table: TableAlias, attribute: Entid) {
|
||||||
|
@ -562,7 +551,8 @@ impl ConjoiningClauses {
|
||||||
pub(crate) fn constrain_value_to_numeric(&mut self, table: TableAlias, value: i64) {
|
pub(crate) fn constrain_value_to_numeric(&mut self, table: TableAlias, value: i64) {
|
||||||
self.wheres.add_intersection(ColumnConstraint::Equals(
|
self.wheres.add_intersection(ColumnConstraint::Equals(
|
||||||
QualifiedAlias(table, Column::Fixed(DatomsColumn::Value)),
|
QualifiedAlias(table, Column::Fixed(DatomsColumn::Value)),
|
||||||
QueryValue::PrimitiveLong(value)))
|
QueryValue::PrimitiveLong(value),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mark the given value as a long.
|
/// Mark the given value as a long.
|
||||||
|
@ -575,16 +565,24 @@ impl ConjoiningClauses {
|
||||||
self.narrow_types_for_var(variable, ValueTypeSet::of_numeric_types());
|
self.narrow_types_for_var(variable, ValueTypeSet::of_numeric_types());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn can_constrain_var_to_type(&self, var: &Variable, this_type: ValueType) -> Option<EmptyBecause> {
|
pub(crate) fn can_constrain_var_to_type(
|
||||||
|
&self,
|
||||||
|
var: &Variable,
|
||||||
|
this_type: ValueType,
|
||||||
|
) -> Option<EmptyBecause> {
|
||||||
self.can_constrain_var_to_types(var, ValueTypeSet::of_one(this_type))
|
self.can_constrain_var_to_types(var, ValueTypeSet::of_one(this_type))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_constrain_var_to_types(&self, var: &Variable, these_types: ValueTypeSet) -> Option<EmptyBecause> {
|
fn can_constrain_var_to_types(
|
||||||
|
&self,
|
||||||
|
var: &Variable,
|
||||||
|
these_types: ValueTypeSet,
|
||||||
|
) -> Option<EmptyBecause> {
|
||||||
if let Some(existing) = self.known_types.get(var) {
|
if let Some(existing) = self.known_types.get(var) {
|
||||||
if existing.intersection(&these_types).is_empty() {
|
if existing.intersection(these_types).is_empty() {
|
||||||
return Some(EmptyBecause::TypeMismatch {
|
return Some(EmptyBecause::TypeMismatch {
|
||||||
var: var.clone(),
|
var: var.clone(),
|
||||||
existing: existing.clone(),
|
existing: *existing,
|
||||||
desired: these_types,
|
desired: these_types,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -603,7 +601,11 @@ impl ConjoiningClauses {
|
||||||
if let Some(existing) = self.known_types.insert(var.clone(), this_type_set) {
|
if let Some(existing) = self.known_types.insert(var.clone(), this_type_set) {
|
||||||
// There was an existing mapping. Does this type match?
|
// There was an existing mapping. Does this type match?
|
||||||
if !existing.contains(this_type) {
|
if !existing.contains(this_type) {
|
||||||
self.mark_known_empty(EmptyBecause::TypeMismatch { var, existing, desired: this_type_set });
|
self.mark_known_empty(EmptyBecause::TypeMismatch {
|
||||||
|
var,
|
||||||
|
existing,
|
||||||
|
desired: this_type_set,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -627,12 +629,12 @@ impl ConjoiningClauses {
|
||||||
Entry::Vacant(entry) => {
|
Entry::Vacant(entry) => {
|
||||||
entry.insert(types);
|
entry.insert(types);
|
||||||
return;
|
return;
|
||||||
},
|
}
|
||||||
Entry::Occupied(mut entry) => {
|
Entry::Occupied(mut entry) => {
|
||||||
// We have an existing requirement. The new requirement will be
|
// We have an existing requirement. The new requirement will be
|
||||||
// the intersection, but we'll `mark_known_empty` if that's empty.
|
// the intersection, but we'll `mark_known_empty` if that's empty.
|
||||||
let existing = *entry.get();
|
let existing = *entry.get();
|
||||||
let intersection = types.intersection(&existing);
|
let intersection = types.intersection(existing);
|
||||||
entry.insert(intersection);
|
entry.insert(intersection);
|
||||||
|
|
||||||
if !intersection.is_empty() {
|
if !intersection.is_empty() {
|
||||||
|
@ -640,11 +642,11 @@ impl ConjoiningClauses {
|
||||||
}
|
}
|
||||||
|
|
||||||
EmptyBecause::TypeMismatch {
|
EmptyBecause::TypeMismatch {
|
||||||
var: var,
|
var,
|
||||||
existing: existing,
|
existing,
|
||||||
desired: types,
|
desired: types,
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
self.mark_known_empty(empty_because);
|
self.mark_known_empty(empty_because);
|
||||||
}
|
}
|
||||||
|
@ -664,21 +666,22 @@ impl ConjoiningClauses {
|
||||||
self.extracted_types.remove(e.key());
|
self.extracted_types.remove(e.key());
|
||||||
}
|
}
|
||||||
e.insert(new_types);
|
e.insert(new_types);
|
||||||
},
|
}
|
||||||
Entry::Occupied(mut e) => {
|
Entry::Occupied(mut e) => {
|
||||||
let new;
|
let new;
|
||||||
// Scoped borrow of `e`.
|
// Scoped borrow of `e`.
|
||||||
{
|
{
|
||||||
let existing_types = e.get();
|
let existing_types = e.get();
|
||||||
if existing_types.is_empty() && // The set is empty: no types are possible.
|
if existing_types.is_empty() && // The set is empty: no types are possible.
|
||||||
self.empty_because.is_some() {
|
self.empty_because.is_some()
|
||||||
|
{
|
||||||
panic!("Uh oh: we failed this pattern, probably because {:?} couldn't match, but now we're broadening its type.",
|
panic!("Uh oh: we failed this pattern, probably because {:?} couldn't match, but now we're broadening its type.",
|
||||||
e.key());
|
e.key());
|
||||||
}
|
}
|
||||||
new = existing_types.union(&new_types);
|
new = existing_types.union(new_types);
|
||||||
}
|
}
|
||||||
e.insert(new);
|
e.insert(new);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -699,18 +702,20 @@ impl ConjoiningClauses {
|
||||||
match self.known_types.entry(var) {
|
match self.known_types.entry(var) {
|
||||||
Entry::Vacant(e) => {
|
Entry::Vacant(e) => {
|
||||||
e.insert(types);
|
e.insert(types);
|
||||||
},
|
}
|
||||||
Entry::Occupied(mut e) => {
|
Entry::Occupied(mut e) => {
|
||||||
let intersected: ValueTypeSet = types.intersection(e.get());
|
let intersected: ValueTypeSet = types.intersection(*e.get());
|
||||||
if intersected.is_empty() {
|
if intersected.is_empty() {
|
||||||
let reason = EmptyBecause::TypeMismatch { var: e.key().clone(),
|
let reason = EmptyBecause::TypeMismatch {
|
||||||
existing: e.get().clone(),
|
var: e.key().clone(),
|
||||||
desired: types };
|
existing: *e.get(),
|
||||||
|
desired: types,
|
||||||
|
};
|
||||||
empty_because = Some(reason);
|
empty_because = Some(reason);
|
||||||
}
|
}
|
||||||
// Always insert, even if it's empty!
|
// Always insert, even if it's empty!
|
||||||
e.insert(intersected);
|
e.insert(intersected);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(e) = empty_because {
|
if let Some(e) = empty_because {
|
||||||
|
@ -740,7 +745,7 @@ impl ConjoiningClauses {
|
||||||
// If it's a variable, record that it has the right type.
|
// If it's a variable, record that it has the right type.
|
||||||
// Ident or attribute resolution errors (the only other check we need to do) will be done
|
// Ident or attribute resolution errors (the only other check we need to do) will be done
|
||||||
// by the caller.
|
// by the caller.
|
||||||
if let &EvolvedNonValuePlace::Variable(ref v) = value {
|
if let EvolvedNonValuePlace::Variable(ref v) = value {
|
||||||
self.constrain_var_to_type(v.clone(), ValueType::Ref)
|
self.constrain_var_to_type(v.clone(), ValueType::Ref)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -758,35 +763,43 @@ impl ConjoiningClauses {
|
||||||
self.empty_because = Some(why);
|
self.empty_because = Some(why);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn entid_for_ident<'s, 'a>(&self, schema: &'s Schema, ident: &'a Keyword) -> Option<KnownEntid> {
|
fn entid_for_ident<'s, 'a>(
|
||||||
|
&self,
|
||||||
|
schema: &'s Schema,
|
||||||
|
ident: &'a Keyword,
|
||||||
|
) -> Option<KnownEntid> {
|
||||||
schema.get_entid(&ident)
|
schema.get_entid(&ident)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn table_for_attribute_and_value<'s, 'a>(&self, attribute: &'s Attribute, value: &'a EvolvedValuePlace) -> ::std::result::Result<DatomsTable, EmptyBecause> {
|
fn table_for_attribute_and_value<'s, 'a>(
|
||||||
|
&self,
|
||||||
|
attribute: &'s Attribute,
|
||||||
|
value: &'a EvolvedValuePlace,
|
||||||
|
) -> ::std::result::Result<DatomsTable, EmptyBecause> {
|
||||||
if attribute.fulltext {
|
if attribute.fulltext {
|
||||||
match value {
|
match value {
|
||||||
&EvolvedValuePlace::Placeholder =>
|
EvolvedValuePlace::Placeholder => Ok(DatomsTable::Datoms), // We don't need the value.
|
||||||
Ok(DatomsTable::Datoms), // We don't need the value.
|
|
||||||
|
|
||||||
// TODO: an existing non-string binding can cause this pattern to fail.
|
// TODO: an existing non-string binding can cause this pattern to fail.
|
||||||
&EvolvedValuePlace::Variable(_) =>
|
EvolvedValuePlace::Variable(_) => Ok(DatomsTable::FulltextDatoms),
|
||||||
Ok(DatomsTable::FulltextDatoms),
|
|
||||||
|
|
||||||
&EvolvedValuePlace::Value(TypedValue::String(_)) =>
|
EvolvedValuePlace::Value(TypedValue::String(_)) => Ok(DatomsTable::FulltextDatoms),
|
||||||
Ok(DatomsTable::FulltextDatoms),
|
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
// We can't succeed if there's a non-string constant value for a fulltext
|
// We can't succeed if there's a non-string constant value for a fulltext
|
||||||
// field.
|
// field.
|
||||||
Err(EmptyBecause::NonStringFulltextValue)
|
Err(EmptyBecause::NonStringFulltextValue)
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(DatomsTable::Datoms)
|
Ok(DatomsTable::Datoms)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn table_for_unknown_attribute<'s, 'a>(&self, value: &'a EvolvedValuePlace) -> ::std::result::Result<DatomsTable, EmptyBecause> {
|
fn table_for_unknown_attribute(
|
||||||
|
&self,
|
||||||
|
value: &EvolvedValuePlace,
|
||||||
|
) -> ::std::result::Result<DatomsTable, EmptyBecause> {
|
||||||
// If the value is known to be non-textual, we can simply use the regular datoms
|
// If the value is known to be non-textual, we can simply use the regular datoms
|
||||||
// table (TODO: and exclude on `index_fulltext`!).
|
// table (TODO: and exclude on `index_fulltext`!).
|
||||||
//
|
//
|
||||||
|
@ -795,24 +808,28 @@ impl ConjoiningClauses {
|
||||||
//
|
//
|
||||||
// If the value is a variable or string, we must use `all_datoms`, or do the join
|
// If the value is a variable or string, we must use `all_datoms`, or do the join
|
||||||
// ourselves, because we'll need to either extract or compare on the string.
|
// ourselves, because we'll need to either extract or compare on the string.
|
||||||
Ok(
|
Ok(match value {
|
||||||
match value {
|
|
||||||
// TODO: see if the variable is projected, aggregated, or compared elsewhere in
|
// TODO: see if the variable is projected, aggregated, or compared elsewhere in
|
||||||
// the query. If it's not, we don't need to use all_datoms here.
|
// the query. If it's not, we don't need to use all_datoms here.
|
||||||
&EvolvedValuePlace::Variable(ref v) => {
|
EvolvedValuePlace::Variable(ref v) => {
|
||||||
// If `required_types` and `known_types` don't exclude strings,
|
// If `required_types` and `known_types` don't exclude strings,
|
||||||
// we need to query `all_datoms`.
|
// we need to query `all_datoms`.
|
||||||
if self.required_types.get(v).map_or(true, |s| s.contains(ValueType::String)) &&
|
if self
|
||||||
self.known_types.get(v).map_or(true, |s| s.contains(ValueType::String)) {
|
.required_types
|
||||||
|
.get(v)
|
||||||
|
.map_or(true, |s| s.contains(ValueType::String))
|
||||||
|
&& self
|
||||||
|
.known_types
|
||||||
|
.get(v)
|
||||||
|
.map_or(true, |s| s.contains(ValueType::String))
|
||||||
|
{
|
||||||
DatomsTable::AllDatoms
|
DatomsTable::AllDatoms
|
||||||
} else {
|
} else {
|
||||||
DatomsTable::Datoms
|
DatomsTable::Datoms
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&EvolvedValuePlace::Value(TypedValue::String(_)) =>
|
EvolvedValuePlace::Value(TypedValue::String(_)) => DatomsTable::AllDatoms,
|
||||||
DatomsTable::AllDatoms,
|
_ => DatomsTable::Datoms,
|
||||||
_ =>
|
|
||||||
DatomsTable::Datoms,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -820,47 +837,58 @@ impl ConjoiningClauses {
|
||||||
/// If the attribute input or value binding doesn't name an attribute, or doesn't name an
|
/// If the attribute input or value binding doesn't name an attribute, or doesn't name an
|
||||||
/// attribute that is congruent with the supplied value, we return an `EmptyBecause`.
|
/// attribute that is congruent with the supplied value, we return an `EmptyBecause`.
|
||||||
/// The caller is responsible for marking the CC as known-empty if this is a fatal failure.
|
/// The caller is responsible for marking the CC as known-empty if this is a fatal failure.
|
||||||
fn table_for_places<'s, 'a>(&self, schema: &'s Schema, attribute: &'a EvolvedNonValuePlace, value: &'a EvolvedValuePlace) -> ::std::result::Result<DatomsTable, EmptyBecause> {
|
fn table_for_places<'s, 'a>(
|
||||||
|
&self,
|
||||||
|
schema: &'s Schema,
|
||||||
|
attribute: &'a EvolvedNonValuePlace,
|
||||||
|
value: &'a EvolvedValuePlace,
|
||||||
|
) -> ::std::result::Result<DatomsTable, EmptyBecause> {
|
||||||
match attribute {
|
match attribute {
|
||||||
&EvolvedNonValuePlace::Entid(id) =>
|
EvolvedNonValuePlace::Entid(id) => schema
|
||||||
schema.attribute_for_entid(id)
|
.attribute_for_entid(*id)
|
||||||
.ok_or_else(|| EmptyBecause::InvalidAttributeEntid(id))
|
.ok_or_else(|| EmptyBecause::InvalidAttributeEntid(*id))
|
||||||
.and_then(|attribute| self.table_for_attribute_and_value(attribute, value)),
|
.and_then(|attribute| self.table_for_attribute_and_value(attribute, value)),
|
||||||
// TODO: In a prepared context, defer this decision until a second algebrizing phase.
|
// TODO: In a prepared context, defer this decision until a second algebrizing phase.
|
||||||
// #278.
|
// #278.
|
||||||
&EvolvedNonValuePlace::Placeholder =>
|
EvolvedNonValuePlace::Placeholder => self.table_for_unknown_attribute(value),
|
||||||
self.table_for_unknown_attribute(value),
|
EvolvedNonValuePlace::Variable(ref v) => {
|
||||||
&EvolvedNonValuePlace::Variable(ref v) => {
|
|
||||||
// See if we have a binding for the variable.
|
// See if we have a binding for the variable.
|
||||||
match self.bound_value(v) {
|
match self.bound_value(v) {
|
||||||
// TODO: In a prepared context, defer this decision until a second algebrizing phase.
|
// TODO: In a prepared context, defer this decision until a second algebrizing phase.
|
||||||
// #278.
|
// #278.
|
||||||
None =>
|
None => self.table_for_unknown_attribute(value),
|
||||||
self.table_for_unknown_attribute(value),
|
|
||||||
Some(TypedValue::Ref(id)) =>
|
Some(TypedValue::Ref(id)) =>
|
||||||
// Recurse: it's easy.
|
// Recurse: it's easy.
|
||||||
self.table_for_places(schema, &EvolvedNonValuePlace::Entid(id), value),
|
{
|
||||||
|
self.table_for_places(schema, &EvolvedNonValuePlace::Entid(id), value)
|
||||||
|
}
|
||||||
Some(TypedValue::Keyword(ref kw)) =>
|
Some(TypedValue::Keyword(ref kw)) =>
|
||||||
// Don't recurse: avoid needing to clone the keyword.
|
// Don't recurse: avoid needing to clone the keyword.
|
||||||
schema.attribute_for_ident(kw)
|
{
|
||||||
|
schema
|
||||||
|
.attribute_for_ident(kw)
|
||||||
.ok_or_else(|| EmptyBecause::InvalidAttributeIdent(kw.cloned()))
|
.ok_or_else(|| EmptyBecause::InvalidAttributeIdent(kw.cloned()))
|
||||||
.and_then(|(attribute, _entid)| self.table_for_attribute_and_value(attribute, value)),
|
.and_then(|(attribute, _entid)| {
|
||||||
|
self.table_for_attribute_and_value(attribute, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
// This pattern cannot match: the caller has bound a non-entity value to an
|
// This pattern cannot match: the caller has bound a non-entity value to an
|
||||||
// attribute place.
|
// attribute place.
|
||||||
Err(EmptyBecause::InvalidBinding(Column::Fixed(DatomsColumn::Attribute), v.clone()))
|
Err(EmptyBecause::InvalidBinding(
|
||||||
},
|
Column::Fixed(DatomsColumn::Attribute),
|
||||||
|
v,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn next_alias_for_table(&mut self, table: DatomsTable) -> TableAlias {
|
pub(crate) fn next_alias_for_table(&mut self, table: DatomsTable) -> TableAlias {
|
||||||
match table {
|
match table {
|
||||||
DatomsTable::Computed(u) =>
|
DatomsTable::Computed(u) => format!("{}{:02}", table.name(), u),
|
||||||
format!("{}{:02}", table.name(), u),
|
_ => format!("{}{:02}", table.name(), self.alias_counter.next()),
|
||||||
_ =>
|
|
||||||
format!("{}{:02}", table.name(), self.alias_counter.next()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -868,7 +896,11 @@ impl ConjoiningClauses {
|
||||||
/// This is a mutating method because it mutates the aliaser function!
|
/// This is a mutating method because it mutates the aliaser function!
|
||||||
/// Note that if this function decides that a pattern cannot match, it will flip
|
/// Note that if this function decides that a pattern cannot match, it will flip
|
||||||
/// `empty_because`.
|
/// `empty_because`.
|
||||||
fn alias_table<'s, 'a>(&mut self, schema: &'s Schema, pattern: &'a EvolvedPattern) -> Option<SourceAlias> {
|
fn alias_table<'s, 'a>(
|
||||||
|
&mut self,
|
||||||
|
schema: &'s Schema,
|
||||||
|
pattern: &'a EvolvedPattern,
|
||||||
|
) -> Option<SourceAlias> {
|
||||||
self.table_for_places(schema, &pattern.attribute, &pattern.value)
|
self.table_for_places(schema, &pattern.attribute, &pattern.value)
|
||||||
.map_err(|reason| {
|
.map_err(|reason| {
|
||||||
self.mark_known_empty(reason);
|
self.mark_known_empty(reason);
|
||||||
|
@ -877,38 +909,54 @@ impl ConjoiningClauses {
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_attribute_for_value<'s>(&self, schema: &'s Schema, value: &TypedValue) -> Option<&'s Attribute> {
|
fn get_attribute_for_value<'s>(
|
||||||
|
&self,
|
||||||
|
schema: &'s Schema,
|
||||||
|
value: &TypedValue,
|
||||||
|
) -> Option<&'s Attribute> {
|
||||||
match value {
|
match value {
|
||||||
// We know this one is known if the attribute lookup succeeds…
|
// We know this one is known if the attribute lookup succeeds…
|
||||||
&TypedValue::Ref(id) => schema.attribute_for_entid(id),
|
TypedValue::Ref(id) => schema.attribute_for_entid(*id),
|
||||||
&TypedValue::Keyword(ref kw) => schema.attribute_for_ident(kw).map(|(a, _id)| a),
|
TypedValue::Keyword(ref kw) => schema.attribute_for_ident(kw).map(|(a, _id)| a),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_attribute<'s, 'a>(&self, schema: &'s Schema, pattern: &'a EvolvedPattern) -> Option<&'s Attribute> {
|
fn get_attribute<'s, 'a>(
|
||||||
|
&self,
|
||||||
|
schema: &'s Schema,
|
||||||
|
pattern: &'a EvolvedPattern,
|
||||||
|
) -> Option<&'s Attribute> {
|
||||||
match pattern.attribute {
|
match pattern.attribute {
|
||||||
EvolvedNonValuePlace::Entid(id) =>
|
EvolvedNonValuePlace::Entid(id) =>
|
||||||
// We know this one is known if the attribute lookup succeeds…
|
// We know this one is known if the attribute lookup succeeds…
|
||||||
schema.attribute_for_entid(id),
|
{
|
||||||
|
schema.attribute_for_entid(id)
|
||||||
|
}
|
||||||
EvolvedNonValuePlace::Variable(ref var) =>
|
EvolvedNonValuePlace::Variable(ref var) =>
|
||||||
// If the pattern has a variable, we've already determined that the binding -- if
|
// If the pattern has a variable, we've already determined that the binding -- if
|
||||||
// any -- is acceptable and yields a table. Here, simply look to see if it names
|
// any -- is acceptable and yields a table. Here, simply look to see if it names
|
||||||
// an attribute so we can find out the type.
|
// an attribute so we can find out the type.
|
||||||
self.value_bindings.get(var)
|
{
|
||||||
.and_then(|val| self.get_attribute_for_value(schema, val)),
|
self.value_bindings
|
||||||
|
.get(var)
|
||||||
|
.and_then(|val| self.get_attribute_for_value(schema, val))
|
||||||
|
}
|
||||||
EvolvedNonValuePlace::Placeholder => None,
|
EvolvedNonValuePlace::Placeholder => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_value_type<'s, 'a>(&self, schema: &'s Schema, pattern: &'a EvolvedPattern) -> Option<ValueType> {
|
fn get_value_type<'s, 'a>(
|
||||||
|
&self,
|
||||||
|
schema: &'s Schema,
|
||||||
|
pattern: &'a EvolvedPattern,
|
||||||
|
) -> Option<ValueType> {
|
||||||
self.get_attribute(schema, pattern).map(|a| a.value_type)
|
self.get_attribute(schema, pattern).map(|a| a.value_type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expansions.
|
/// Expansions.
|
||||||
impl ConjoiningClauses {
|
impl ConjoiningClauses {
|
||||||
|
|
||||||
/// Take the contents of `column_bindings` and generate inter-constraints for the appropriate
|
/// Take the contents of `column_bindings` and generate inter-constraints for the appropriate
|
||||||
/// columns into `wheres`.
|
/// columns into `wheres`.
|
||||||
///
|
///
|
||||||
|
@ -927,13 +975,16 @@ impl ConjoiningClauses {
|
||||||
pub(crate) fn expand_column_bindings(&mut self) {
|
pub(crate) fn expand_column_bindings(&mut self) {
|
||||||
for cols in self.column_bindings.values() {
|
for cols in self.column_bindings.values() {
|
||||||
if cols.len() > 1 {
|
if cols.len() > 1 {
|
||||||
let ref primary = cols[0];
|
let primary = &cols[0];
|
||||||
let secondaries = cols.iter().skip(1);
|
let secondaries = cols.iter().skip(1);
|
||||||
for secondary in secondaries {
|
for secondary in secondaries {
|
||||||
// TODO: if both primary and secondary are .v, should we make sure
|
// TODO: if both primary and secondary are .v, should we make sure
|
||||||
// the type tag columns also match?
|
// the type tag columns also match?
|
||||||
// We don't do so in the ClojureScript version.
|
// We don't do so in the ClojureScript version.
|
||||||
self.wheres.add_intersection(ColumnConstraint::Equals(primary.clone(), QueryValue::Column(secondary.clone())));
|
self.wheres.add_intersection(ColumnConstraint::Equals(
|
||||||
|
primary.clone(),
|
||||||
|
QueryValue::Column(secondary.clone()),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -962,7 +1013,7 @@ impl ConjoiningClauses {
|
||||||
/// This step also updates `known_types` to match.
|
/// This step also updates `known_types` to match.
|
||||||
pub(crate) fn process_required_types(&mut self) -> Result<()> {
|
pub(crate) fn process_required_types(&mut self) -> Result<()> {
|
||||||
if self.empty_because.is_some() {
|
if self.empty_because.is_some() {
|
||||||
return Ok(())
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can't call `mark_known_empty` inside the loop since it would be a
|
// We can't call `mark_known_empty` inside the loop since it would be a
|
||||||
|
@ -972,18 +1023,18 @@ impl ConjoiningClauses {
|
||||||
let mut empty_because: Option<EmptyBecause> = None;
|
let mut empty_because: Option<EmptyBecause> = None;
|
||||||
for (var, types) in self.required_types.clone().into_iter() {
|
for (var, types) in self.required_types.clone().into_iter() {
|
||||||
if let Some(already_known) = self.known_types.get(&var) {
|
if let Some(already_known) = self.known_types.get(&var) {
|
||||||
if already_known.is_disjoint(&types) {
|
if already_known.is_disjoint(types) {
|
||||||
// If we know the constraint can't be one of the types
|
// If we know the constraint can't be one of the types
|
||||||
// the variable could take, then we know we're empty.
|
// the variable could take, then we know we're empty.
|
||||||
empty_because = Some(EmptyBecause::TypeMismatch {
|
empty_because = Some(EmptyBecause::TypeMismatch {
|
||||||
var: var,
|
var,
|
||||||
existing: *already_known,
|
existing: *already_known,
|
||||||
desired: types,
|
desired: types,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if already_known.is_subset(&types) {
|
if already_known.is_subset(types) {
|
||||||
// TODO: I'm not convinced that we can do nothing here.
|
// TODO: I'm not convinced that we can do nothing here.
|
||||||
//
|
//
|
||||||
// Consider `[:find ?x ?v :where [_ _ ?v] [(> ?v 10)] [?x :foo/long ?v]]`.
|
// Consider `[:find ?x ?v :where [_ _ ?v] [(> ?v 10)] [?x :foo/long ?v]]`.
|
||||||
|
@ -1011,7 +1062,8 @@ impl ConjoiningClauses {
|
||||||
// Update known types.
|
// Update known types.
|
||||||
self.narrow_types_for_var(var.clone(), types);
|
self.narrow_types_for_var(var.clone(), types);
|
||||||
|
|
||||||
let qa = self.extracted_types
|
let qa = self
|
||||||
|
.extracted_types
|
||||||
.get(&var)
|
.get(&var)
|
||||||
.ok_or_else(|| AlgebrizerError::UnboundVariable(var.name()))?;
|
.ok_or_else(|| AlgebrizerError::UnboundVariable(var.name()))?;
|
||||||
self.wheres.add_intersection(ColumnConstraint::HasTypes {
|
self.wheres.add_intersection(ColumnConstraint::HasTypes {
|
||||||
|
@ -1053,44 +1105,52 @@ impl ConjoiningClauses {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConjoiningClauses {
|
impl ConjoiningClauses {
|
||||||
fn apply_evolved_patterns(&mut self, known: Known, mut patterns: VecDeque<EvolvedPattern>) -> Result<()> {
|
fn apply_evolved_patterns(
|
||||||
|
&mut self,
|
||||||
|
known: Known,
|
||||||
|
mut patterns: VecDeque<EvolvedPattern>,
|
||||||
|
) -> Result<()> {
|
||||||
while let Some(pattern) = patterns.pop_front() {
|
while let Some(pattern) = patterns.pop_front() {
|
||||||
match self.evolve_pattern(known, pattern) {
|
match self.evolve_pattern(known, pattern) {
|
||||||
PlaceOrEmpty::Place(re_evolved) => self.apply_pattern(known, re_evolved),
|
PlaceOrEmpty::Place(re_evolved) => self.apply_pattern(known, re_evolved),
|
||||||
PlaceOrEmpty::Empty(because) => {
|
PlaceOrEmpty::Empty(because) => {
|
||||||
self.mark_known_empty(because);
|
self.mark_known_empty(because);
|
||||||
patterns.clear();
|
patterns.clear();
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mark_as_ref(&mut self, pos: &PatternNonValuePlace) {
|
fn mark_as_ref(&mut self, pos: &PatternNonValuePlace) {
|
||||||
if let &PatternNonValuePlace::Variable(ref var) = pos {
|
if let PatternNonValuePlace::Variable(ref var) = pos {
|
||||||
self.constrain_var_to_type(var.clone(), ValueType::Ref)
|
self.constrain_var_to_type(var.clone(), ValueType::Ref)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn apply_clauses(&mut self, known: Known, where_clauses: Vec<WhereClause>) -> Result<()> {
|
pub(crate) fn apply_clauses(
|
||||||
|
&mut self,
|
||||||
|
known: Known,
|
||||||
|
where_clauses: Vec<WhereClause>,
|
||||||
|
) -> Result<()> {
|
||||||
// We apply (top level) type predicates first as an optimization.
|
// We apply (top level) type predicates first as an optimization.
|
||||||
for clause in where_clauses.iter() {
|
for clause in where_clauses.iter() {
|
||||||
match clause {
|
match clause {
|
||||||
&WhereClause::TypeAnnotation(ref anno) => {
|
WhereClause::TypeAnnotation(ref anno) => {
|
||||||
self.apply_type_anno(anno)?;
|
self.apply_type_anno(anno)?;
|
||||||
},
|
}
|
||||||
|
|
||||||
// Patterns are common, so let's grab as much type information from
|
// Patterns are common, so let's grab as much type information from
|
||||||
// them as we can.
|
// them as we can.
|
||||||
&WhereClause::Pattern(ref p) => {
|
WhereClause::Pattern(ref p) => {
|
||||||
self.mark_as_ref(&p.entity);
|
self.mark_as_ref(&p.entity);
|
||||||
self.mark_as_ref(&p.attribute);
|
self.mark_as_ref(&p.attribute);
|
||||||
self.mark_as_ref(&p.tx);
|
self.mark_as_ref(&p.tx);
|
||||||
},
|
}
|
||||||
|
|
||||||
// TODO: if we wish we can include other kinds of clauses in this type
|
// TODO: if we wish we can include other kinds of clauses in this type
|
||||||
// extraction phase.
|
// extraction phase.
|
||||||
_ => {},
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1101,18 +1161,16 @@ impl ConjoiningClauses {
|
||||||
let mut patterns: VecDeque<EvolvedPattern> = VecDeque::with_capacity(remaining);
|
let mut patterns: VecDeque<EvolvedPattern> = VecDeque::with_capacity(remaining);
|
||||||
for clause in where_clauses {
|
for clause in where_clauses {
|
||||||
remaining -= 1;
|
remaining -= 1;
|
||||||
if let &WhereClause::TypeAnnotation(_) = &clause {
|
if let WhereClause::TypeAnnotation(_) = &clause {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
match clause {
|
match clause {
|
||||||
WhereClause::Pattern(p) => {
|
WhereClause::Pattern(p) => match self.make_evolved_pattern(known, p) {
|
||||||
match self.make_evolved_pattern(known, p) {
|
|
||||||
PlaceOrEmpty::Place(evolved) => patterns.push_back(evolved),
|
PlaceOrEmpty::Place(evolved) => patterns.push_back(evolved),
|
||||||
PlaceOrEmpty::Empty(because) => {
|
PlaceOrEmpty::Empty(because) => {
|
||||||
self.mark_known_empty(because);
|
self.mark_known_empty(because);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
if !patterns.is_empty() {
|
if !patterns.is_empty() {
|
||||||
|
@ -1120,7 +1178,7 @@ impl ConjoiningClauses {
|
||||||
patterns = VecDeque::with_capacity(remaining);
|
patterns = VecDeque::with_capacity(remaining);
|
||||||
}
|
}
|
||||||
self.apply_clause(known, clause)?;
|
self.apply_clause(known, clause)?;
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.apply_evolved_patterns(known, patterns)
|
self.apply_evolved_patterns(known, patterns)
|
||||||
|
@ -1136,24 +1194,18 @@ impl ConjoiningClauses {
|
||||||
PlaceOrEmpty::Empty(because) => self.mark_known_empty(because),
|
PlaceOrEmpty::Empty(because) => self.mark_known_empty(because),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
WhereClause::Pred(p) => {
|
WhereClause::Pred(p) => self.apply_predicate(known, p),
|
||||||
self.apply_predicate(known, p)
|
WhereClause::WhereFn(f) => self.apply_where_fn(known, f),
|
||||||
},
|
|
||||||
WhereClause::WhereFn(f) => {
|
|
||||||
self.apply_where_fn(known, f)
|
|
||||||
},
|
|
||||||
WhereClause::OrJoin(o) => {
|
WhereClause::OrJoin(o) => {
|
||||||
validate_or_join(&o)?;
|
validate_or_join(&o)?;
|
||||||
self.apply_or_join(known, o)
|
self.apply_or_join(known, o)
|
||||||
},
|
}
|
||||||
WhereClause::NotJoin(n) => {
|
WhereClause::NotJoin(n) => {
|
||||||
validate_not_join(&n)?;
|
validate_not_join(&n)?;
|
||||||
self.apply_not_join(known, n)
|
self.apply_not_join(known, n)
|
||||||
},
|
}
|
||||||
WhereClause::TypeAnnotation(anno) => {
|
WhereClause::TypeAnnotation(anno) => self.apply_type_anno(&anno),
|
||||||
self.apply_type_anno(&anno)
|
|
||||||
},
|
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1175,7 +1227,7 @@ impl PushComputed for Vec<ComputedTable> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn associate_ident(schema: &mut Schema, i: Keyword, e: Entid) {
|
fn associate_ident(schema: &mut Schema, i: Keyword, e: Entid) {
|
||||||
schema.entid_map.insert(e, i.clone());
|
schema.entid_map.insert(e, i.clone());
|
||||||
schema.ident_map.insert(i.clone(), e);
|
schema.ident_map.insert(i, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -8,25 +8,15 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
use mentat_query::{
|
use edn::query::{ContainsVariables, NotJoin, UnifyVars};
|
||||||
ContainsVariables,
|
|
||||||
NotJoin,
|
|
||||||
UnifyVars,
|
|
||||||
};
|
|
||||||
|
|
||||||
use clauses::ConjoiningClauses;
|
use crate::clauses::ConjoiningClauses;
|
||||||
|
|
||||||
use errors::{
|
use query_algebrizer_traits::errors::{AlgebrizerError, Result};
|
||||||
AlgebrizerError,
|
|
||||||
Result,
|
|
||||||
};
|
|
||||||
|
|
||||||
use types::{
|
use crate::types::{ColumnConstraint, ComputedTable};
|
||||||
ColumnConstraint,
|
|
||||||
ComputedTable,
|
|
||||||
};
|
|
||||||
|
|
||||||
use Known;
|
use crate::Known;
|
||||||
|
|
||||||
impl ConjoiningClauses {
|
impl ConjoiningClauses {
|
||||||
pub(crate) fn apply_not_join(&mut self, known: Known, not_join: NotJoin) -> Result<()> {
|
pub(crate) fn apply_not_join(&mut self, known: Known, not_join: NotJoin) -> Result<()> {
|
||||||
|
@ -76,9 +66,10 @@ impl ConjoiningClauses {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let subquery = ComputedTable::Subquery(template);
|
let subquery = ComputedTable::Subquery(Box::new(template));
|
||||||
|
|
||||||
self.wheres.add_intersection(ColumnConstraint::NotExists(subquery));
|
self.wheres
|
||||||
|
.add_intersection(ColumnConstraint::NotExists(subquery));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -90,48 +81,22 @@ mod testing {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use mentat_core::{
|
use core_traits::{Attribute, TypedValue, ValueType, ValueTypeSet};
|
||||||
Attribute,
|
|
||||||
Schema,
|
use mentat_core::Schema;
|
||||||
TypedValue,
|
|
||||||
ValueType,
|
use edn::query::{Keyword, PlainSymbol, Variable};
|
||||||
ValueTypeSet,
|
|
||||||
|
use crate::clauses::{add_attribute, associate_ident, QueryInputs};
|
||||||
|
|
||||||
|
use query_algebrizer_traits::errors::AlgebrizerError;
|
||||||
|
|
||||||
|
use crate::types::{
|
||||||
|
ColumnAlternation, ColumnConstraint, ColumnConstraintOrAlternation, ColumnIntersection,
|
||||||
|
DatomsColumn, DatomsTable, Inequality, QualifiedAlias, QueryValue, SourceAlias,
|
||||||
};
|
};
|
||||||
|
|
||||||
use mentat_query::{
|
use crate::{algebrize, algebrize_with_inputs, parse_find_string};
|
||||||
Keyword,
|
|
||||||
PlainSymbol,
|
|
||||||
Variable
|
|
||||||
};
|
|
||||||
|
|
||||||
use clauses::{
|
|
||||||
QueryInputs,
|
|
||||||
add_attribute,
|
|
||||||
associate_ident,
|
|
||||||
};
|
|
||||||
|
|
||||||
use errors::{
|
|
||||||
AlgebrizerError,
|
|
||||||
};
|
|
||||||
|
|
||||||
use types::{
|
|
||||||
ColumnAlternation,
|
|
||||||
ColumnConstraint,
|
|
||||||
ColumnConstraintOrAlternation,
|
|
||||||
ColumnIntersection,
|
|
||||||
DatomsColumn,
|
|
||||||
DatomsTable,
|
|
||||||
Inequality,
|
|
||||||
QualifiedAlias,
|
|
||||||
QueryValue,
|
|
||||||
SourceAlias,
|
|
||||||
};
|
|
||||||
|
|
||||||
use {
|
|
||||||
algebrize,
|
|
||||||
algebrize_with_inputs,
|
|
||||||
parse_find_string,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn alg(schema: &Schema, input: &str) -> ConjoiningClauses {
|
fn alg(schema: &Schema, input: &str) -> ConjoiningClauses {
|
||||||
let known = Known::for_schema(schema);
|
let known = Known::for_schema(schema);
|
||||||
|
@ -142,7 +107,9 @@ mod testing {
|
||||||
fn alg_with_inputs(schema: &Schema, input: &str, inputs: QueryInputs) -> ConjoiningClauses {
|
fn alg_with_inputs(schema: &Schema, input: &str, inputs: QueryInputs) -> ConjoiningClauses {
|
||||||
let known = Known::for_schema(schema);
|
let known = Known::for_schema(schema);
|
||||||
let parsed = parse_find_string(input).expect("parse failed");
|
let parsed = parse_find_string(input).expect("parse failed");
|
||||||
algebrize_with_inputs(known, parsed, 0, inputs).expect("algebrize failed").cc
|
algebrize_with_inputs(known, parsed, 0, inputs)
|
||||||
|
.expect("algebrize failed")
|
||||||
|
.cc
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepopulated_schema() -> Schema {
|
fn prepopulated_schema() -> Schema {
|
||||||
|
@ -152,41 +119,51 @@ mod testing {
|
||||||
associate_ident(&mut schema, Keyword::namespaced("foo", "parent"), 67);
|
associate_ident(&mut schema, Keyword::namespaced("foo", "parent"), 67);
|
||||||
associate_ident(&mut schema, Keyword::namespaced("foo", "age"), 68);
|
associate_ident(&mut schema, Keyword::namespaced("foo", "age"), 68);
|
||||||
associate_ident(&mut schema, Keyword::namespaced("foo", "height"), 69);
|
associate_ident(&mut schema, Keyword::namespaced("foo", "height"), 69);
|
||||||
add_attribute(&mut schema,
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
65,
|
65,
|
||||||
Attribute {
|
Attribute {
|
||||||
value_type: ValueType::String,
|
value_type: ValueType::String,
|
||||||
multival: false,
|
multival: false,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
},
|
||||||
add_attribute(&mut schema,
|
);
|
||||||
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
66,
|
66,
|
||||||
Attribute {
|
Attribute {
|
||||||
value_type: ValueType::String,
|
value_type: ValueType::String,
|
||||||
multival: true,
|
multival: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
},
|
||||||
add_attribute(&mut schema,
|
);
|
||||||
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
67,
|
67,
|
||||||
Attribute {
|
Attribute {
|
||||||
value_type: ValueType::String,
|
value_type: ValueType::String,
|
||||||
multival: true,
|
multival: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
},
|
||||||
add_attribute(&mut schema,
|
);
|
||||||
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
68,
|
68,
|
||||||
Attribute {
|
Attribute {
|
||||||
value_type: ValueType::Long,
|
value_type: ValueType::Long,
|
||||||
multival: false,
|
multival: false,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
},
|
||||||
add_attribute(&mut schema,
|
);
|
||||||
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
69,
|
69,
|
||||||
Attribute {
|
Attribute {
|
||||||
value_type: ValueType::Long,
|
value_type: ValueType::Long,
|
||||||
multival: false,
|
multival: false,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
},
|
||||||
|
);
|
||||||
schema
|
schema
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,24 +208,43 @@ mod testing {
|
||||||
let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne"));
|
let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne"));
|
||||||
|
|
||||||
let mut subquery = ConjoiningClauses::default();
|
let mut subquery = ConjoiningClauses::default();
|
||||||
subquery.from = vec![SourceAlias(DatomsTable::Datoms, d1),
|
subquery.from = vec![
|
||||||
SourceAlias(DatomsTable::Datoms, d2)];
|
SourceAlias(DatomsTable::Datoms, d1),
|
||||||
subquery.column_bindings.insert(vx.clone(), vec![d0e.clone(), d1e.clone(), d2e.clone()]);
|
SourceAlias(DatomsTable::Datoms, d2),
|
||||||
subquery.wheres = ColumnIntersection(vec![ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), parent)),
|
];
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), ambar)),
|
subquery
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2a.clone(), knows.clone())),
|
.column_bindings
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2v.clone(), daphne)),
|
.insert(vx.clone(), vec![d0e.clone(), d1e.clone(), d2e.clone()]);
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d1e.clone()))),
|
subquery.wheres = ColumnIntersection(vec![
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d2e.clone())))]);
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a, parent)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v, ambar)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2a, knows.clone())),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2v, daphne)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
|
d0e.clone(),
|
||||||
|
QueryValue::Column(d1e),
|
||||||
|
)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
|
d0e.clone(),
|
||||||
|
QueryValue::Column(d2e),
|
||||||
|
)),
|
||||||
|
]);
|
||||||
|
|
||||||
subquery.known_types.insert(vx.clone(), ValueTypeSet::of_one(ValueType::Ref));
|
subquery
|
||||||
|
.known_types
|
||||||
|
.insert(vx.clone(), ValueTypeSet::of_one(ValueType::Ref));
|
||||||
|
|
||||||
assert!(!cc.is_known_empty());
|
assert!(!cc.is_known_empty());
|
||||||
assert_eq!(cc.wheres, ColumnIntersection(vec![
|
assert_eq!(
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows.clone())),
|
cc.wheres,
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v.clone(), john)),
|
ColumnIntersection(vec![
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(ComputedTable::Subquery(subquery))),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a, knows)),
|
||||||
]));
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v, john)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(
|
||||||
|
ComputedTable::Subquery(Box::new(subquery))
|
||||||
|
)),
|
||||||
|
])
|
||||||
|
);
|
||||||
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e]));
|
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e]));
|
||||||
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0)]);
|
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0)]);
|
||||||
}
|
}
|
||||||
|
@ -299,31 +295,60 @@ mod testing {
|
||||||
|
|
||||||
let mut subquery = ConjoiningClauses::default();
|
let mut subquery = ConjoiningClauses::default();
|
||||||
subquery.from = vec![SourceAlias(DatomsTable::Datoms, d3)];
|
subquery.from = vec![SourceAlias(DatomsTable::Datoms, d3)];
|
||||||
subquery.column_bindings.insert(vx.clone(), vec![d0e.clone(), d3e.clone()]);
|
subquery
|
||||||
subquery.column_bindings.insert(vy.clone(), vec![d0v.clone(), d3v.clone()]);
|
.column_bindings
|
||||||
subquery.wheres = ColumnIntersection(vec![ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d3a.clone(), parent)),
|
.insert(vx.clone(), vec![d0e.clone(), d3e.clone()]);
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d3e.clone()))),
|
subquery
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v.clone(), QueryValue::Column(d3v.clone())))]);
|
.column_bindings
|
||||||
|
.insert(vy.clone(), vec![d0v.clone(), d3v.clone()]);
|
||||||
|
subquery.wheres = ColumnIntersection(vec![
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d3a, parent)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
|
d0e.clone(),
|
||||||
|
QueryValue::Column(d3e),
|
||||||
|
)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
|
d0v,
|
||||||
|
QueryValue::Column(d3v),
|
||||||
|
)),
|
||||||
|
]);
|
||||||
|
|
||||||
subquery.known_types.insert(vx.clone(), ValueTypeSet::of_one(ValueType::Ref));
|
subquery
|
||||||
subquery.known_types.insert(vy.clone(), ValueTypeSet::of_one(ValueType::String));
|
.known_types
|
||||||
|
.insert(vx.clone(), ValueTypeSet::of_one(ValueType::Ref));
|
||||||
|
subquery
|
||||||
|
.known_types
|
||||||
|
.insert(vy, ValueTypeSet::of_one(ValueType::String));
|
||||||
|
|
||||||
assert!(!cc.is_known_empty());
|
assert!(!cc.is_known_empty());
|
||||||
let expected_wheres = ColumnIntersection(vec![
|
let expected_wheres = ColumnIntersection(vec![
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows)),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a, knows)),
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), age.clone())),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a, age)),
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), eleven)),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v, eleven)),
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2a.clone(), name.clone())),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2a, name)),
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2v.clone(), john)),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2v, john)),
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(ComputedTable::Subquery(subquery))),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d1e.clone()))),
|
ComputedTable::Subquery(Box::new(subquery)),
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d2e.clone()))),
|
)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
|
d0e.clone(),
|
||||||
|
QueryValue::Column(d1e.clone()),
|
||||||
|
)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
|
d0e.clone(),
|
||||||
|
QueryValue::Column(d2e.clone()),
|
||||||
|
)),
|
||||||
]);
|
]);
|
||||||
assert_eq!(cc.wheres, expected_wheres);
|
assert_eq!(cc.wheres, expected_wheres);
|
||||||
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e, d1e, d2e]));
|
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e, d1e, d2e]));
|
||||||
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0),
|
assert_eq!(
|
||||||
|
cc.from,
|
||||||
|
vec![
|
||||||
|
SourceAlias(DatomsTable::Datoms, d0),
|
||||||
SourceAlias(DatomsTable::Datoms, d1),
|
SourceAlias(DatomsTable::Datoms, d1),
|
||||||
SourceAlias(DatomsTable::Datoms, d2)]);
|
SourceAlias(DatomsTable::Datoms, d2)
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not with a pattern and a predicate.
|
// Not with a pattern and a predicate.
|
||||||
|
@ -363,28 +388,47 @@ mod testing {
|
||||||
let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne"));
|
let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne"));
|
||||||
|
|
||||||
let mut subquery = ConjoiningClauses::default();
|
let mut subquery = ConjoiningClauses::default();
|
||||||
subquery.from = vec![SourceAlias(DatomsTable::Datoms, d1),
|
subquery.from = vec![
|
||||||
SourceAlias(DatomsTable::Datoms, d2)];
|
SourceAlias(DatomsTable::Datoms, d1),
|
||||||
subquery.column_bindings.insert(vx.clone(), vec![d0e.clone(), d1e.clone(), d2e.clone()]);
|
SourceAlias(DatomsTable::Datoms, d2),
|
||||||
subquery.wheres = ColumnIntersection(vec![ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows.clone())),
|
];
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), john.clone())),
|
subquery
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2a.clone(), knows.clone())),
|
.column_bindings
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2v.clone(), daphne.clone())),
|
.insert(vx.clone(), vec![d0e.clone(), d1e.clone(), d2e.clone()]);
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d1e.clone()))),
|
subquery.wheres = ColumnIntersection(vec![
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d2e.clone())))]);
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a, knows.clone())),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v, john)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2a, knows)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2v, daphne)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
|
d0e.clone(),
|
||||||
|
QueryValue::Column(d1e),
|
||||||
|
)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
|
d0e.clone(),
|
||||||
|
QueryValue::Column(d2e),
|
||||||
|
)),
|
||||||
|
]);
|
||||||
|
|
||||||
subquery.known_types.insert(vx.clone(), ValueTypeSet::of_one(ValueType::Ref));
|
subquery
|
||||||
|
.known_types
|
||||||
|
.insert(vx.clone(), ValueTypeSet::of_one(ValueType::Ref));
|
||||||
|
|
||||||
assert!(!cc.is_known_empty());
|
assert!(!cc.is_known_empty());
|
||||||
assert_eq!(cc.wheres, ColumnIntersection(vec![
|
assert_eq!(
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), age.clone())),
|
cc.wheres,
|
||||||
|
ColumnIntersection(vec![
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a, age)),
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Inequality {
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Inequality {
|
||||||
operator: Inequality::LessThan,
|
operator: Inequality::LessThan,
|
||||||
left: QueryValue::Column(d0v.clone()),
|
left: QueryValue::Column(d0v),
|
||||||
right: QueryValue::TypedValue(TypedValue::Long(30)),
|
right: QueryValue::TypedValue(TypedValue::Long(30)),
|
||||||
}),
|
}),
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(ComputedTable::Subquery(subquery))),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(
|
||||||
]));
|
ComputedTable::Subquery(Box::new(subquery))
|
||||||
|
)),
|
||||||
|
])
|
||||||
|
);
|
||||||
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e]));
|
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e]));
|
||||||
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0)]);
|
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0)]);
|
||||||
}
|
}
|
||||||
|
@ -404,7 +448,7 @@ mod testing {
|
||||||
let d0 = "datoms00".to_string();
|
let d0 = "datoms00".to_string();
|
||||||
let d0e = QualifiedAlias::new(d0.clone(), DatomsColumn::Entity);
|
let d0e = QualifiedAlias::new(d0.clone(), DatomsColumn::Entity);
|
||||||
let d0a = QualifiedAlias::new(d0.clone(), DatomsColumn::Attribute);
|
let d0a = QualifiedAlias::new(d0.clone(), DatomsColumn::Attribute);
|
||||||
let d0v = QualifiedAlias::new(d0.clone(), DatomsColumn::Value);
|
let d0v = QualifiedAlias::new(d0, DatomsColumn::Value);
|
||||||
|
|
||||||
let d1 = "datoms01".to_string();
|
let d1 = "datoms01".to_string();
|
||||||
let d1e = QualifiedAlias::new(d1.clone(), DatomsColumn::Entity);
|
let d1e = QualifiedAlias::new(d1.clone(), DatomsColumn::Entity);
|
||||||
|
@ -426,32 +470,61 @@ mod testing {
|
||||||
let ambar = QueryValue::TypedValue(TypedValue::typed_string("Ámbar"));
|
let ambar = QueryValue::TypedValue(TypedValue::typed_string("Ámbar"));
|
||||||
let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne"));
|
let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne"));
|
||||||
|
|
||||||
|
|
||||||
let mut subquery = ConjoiningClauses::default();
|
let mut subquery = ConjoiningClauses::default();
|
||||||
subquery.from = vec![SourceAlias(DatomsTable::Datoms, d1),
|
subquery.from = vec![
|
||||||
SourceAlias(DatomsTable::Datoms, d2)];
|
SourceAlias(DatomsTable::Datoms, d1),
|
||||||
subquery.column_bindings.insert(vx.clone(), vec![d0e.clone(), d1e.clone(), d2e.clone()]);
|
SourceAlias(DatomsTable::Datoms, d2),
|
||||||
subquery.wheres = ColumnIntersection(vec![ColumnConstraintOrAlternation::Alternation(ColumnAlternation(vec![
|
];
|
||||||
|
subquery
|
||||||
|
.column_bindings
|
||||||
|
.insert(vx.clone(), vec![d0e.clone(), d1e.clone(), d2e.clone()]);
|
||||||
|
subquery.wheres = ColumnIntersection(vec![
|
||||||
|
ColumnConstraintOrAlternation::Alternation(ColumnAlternation(vec![
|
||||||
ColumnIntersection(vec![
|
ColumnIntersection(vec![
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows.clone())),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), john))]),
|
d1a.clone(),
|
||||||
|
knows.clone(),
|
||||||
|
)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
|
d1v.clone(),
|
||||||
|
john,
|
||||||
|
)),
|
||||||
|
]),
|
||||||
ColumnIntersection(vec![
|
ColumnIntersection(vec![
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows.clone())),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), ambar))]),
|
d1a,
|
||||||
|
knows.clone(),
|
||||||
|
)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v, ambar)),
|
||||||
|
]),
|
||||||
])),
|
])),
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2a.clone(), parent)),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2a, parent)),
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2v.clone(), daphne)),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d2v, daphne)),
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d1e.clone()))),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d2e.clone())))]);
|
d0e.clone(),
|
||||||
|
QueryValue::Column(d1e),
|
||||||
|
)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
|
d0e,
|
||||||
|
QueryValue::Column(d2e),
|
||||||
|
)),
|
||||||
|
]);
|
||||||
|
|
||||||
subquery.known_types.insert(vx.clone(), ValueTypeSet::of_one(ValueType::Ref));
|
subquery
|
||||||
|
.known_types
|
||||||
|
.insert(vx, ValueTypeSet::of_one(ValueType::Ref));
|
||||||
|
|
||||||
assert!(!cc.is_known_empty());
|
assert!(!cc.is_known_empty());
|
||||||
assert_eq!(cc.wheres, ColumnIntersection(vec![
|
assert_eq!(
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows)),
|
cc.wheres,
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v.clone(), bill)),
|
ColumnIntersection(vec![
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(ComputedTable::Subquery(subquery))),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a, knows)),
|
||||||
]));
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v, bill)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(
|
||||||
|
ComputedTable::Subquery(Box::new(subquery))
|
||||||
|
)),
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// not-join with an input variable
|
// not-join with an input variable
|
||||||
|
@ -464,9 +537,10 @@ mod testing {
|
||||||
:where [?x :foo/knows "Bill"]
|
:where [?x :foo/knows "Bill"]
|
||||||
(not [?x :foo/knows ?y])]"#;
|
(not [?x :foo/knows ?y])]"#;
|
||||||
|
|
||||||
let inputs = QueryInputs::with_value_sequence(vec![
|
let inputs = QueryInputs::with_value_sequence(vec![(
|
||||||
(Variable::from_valid_name("?y"), "John".into())
|
Variable::from_valid_name("?y"),
|
||||||
]);
|
"John".into(),
|
||||||
|
)]);
|
||||||
let cc = alg_with_inputs(&schema, query, inputs);
|
let cc = alg_with_inputs(&schema, query, inputs);
|
||||||
|
|
||||||
let vx = Variable::from_valid_name("?x");
|
let vx = Variable::from_valid_name("?x");
|
||||||
|
@ -480,7 +554,7 @@ mod testing {
|
||||||
let d0 = "datoms00".to_string();
|
let d0 = "datoms00".to_string();
|
||||||
let d0e = QualifiedAlias::new(d0.clone(), DatomsColumn::Entity);
|
let d0e = QualifiedAlias::new(d0.clone(), DatomsColumn::Entity);
|
||||||
let d0a = QualifiedAlias::new(d0.clone(), DatomsColumn::Attribute);
|
let d0a = QualifiedAlias::new(d0.clone(), DatomsColumn::Attribute);
|
||||||
let d0v = QualifiedAlias::new(d0.clone(), DatomsColumn::Value);
|
let d0v = QualifiedAlias::new(d0, DatomsColumn::Value);
|
||||||
|
|
||||||
let d1 = "datoms01".to_string();
|
let d1 = "datoms01".to_string();
|
||||||
let d1e = QualifiedAlias::new(d1.clone(), DatomsColumn::Entity);
|
let d1e = QualifiedAlias::new(d1.clone(), DatomsColumn::Entity);
|
||||||
|
@ -489,25 +563,43 @@ mod testing {
|
||||||
|
|
||||||
let mut subquery = ConjoiningClauses::default();
|
let mut subquery = ConjoiningClauses::default();
|
||||||
subquery.from = vec![SourceAlias(DatomsTable::Datoms, d1)];
|
subquery.from = vec![SourceAlias(DatomsTable::Datoms, d1)];
|
||||||
subquery.column_bindings.insert(vx.clone(), vec![d0e.clone(), d1e.clone()]);
|
subquery
|
||||||
subquery.wheres = ColumnIntersection(vec![ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows.clone())),
|
.column_bindings
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), john)),
|
.insert(vx.clone(), vec![d0e.clone(), d1e.clone()]);
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d1e.clone())))]);
|
subquery.wheres = ColumnIntersection(vec![
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a, knows.clone())),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v, john)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
|
d0e,
|
||||||
|
QueryValue::Column(d1e),
|
||||||
|
)),
|
||||||
|
]);
|
||||||
|
|
||||||
subquery.known_types.insert(vx.clone(), ValueTypeSet::of_one(ValueType::Ref));
|
subquery
|
||||||
subquery.known_types.insert(vy.clone(), ValueTypeSet::of_one(ValueType::String));
|
.known_types
|
||||||
|
.insert(vx, ValueTypeSet::of_one(ValueType::Ref));
|
||||||
|
subquery
|
||||||
|
.known_types
|
||||||
|
.insert(vy.clone(), ValueTypeSet::of_one(ValueType::String));
|
||||||
|
|
||||||
let mut input_vars: BTreeSet<Variable> = BTreeSet::default();
|
let mut input_vars: BTreeSet<Variable> = BTreeSet::default();
|
||||||
input_vars.insert(vy.clone());
|
input_vars.insert(vy.clone());
|
||||||
subquery.input_variables = input_vars;
|
subquery.input_variables = input_vars;
|
||||||
subquery.value_bindings.insert(vy.clone(), TypedValue::typed_string("John"));
|
subquery
|
||||||
|
.value_bindings
|
||||||
|
.insert(vy, TypedValue::typed_string("John"));
|
||||||
|
|
||||||
assert!(!cc.is_known_empty());
|
assert!(!cc.is_known_empty());
|
||||||
assert_eq!(cc.wheres, ColumnIntersection(vec![
|
assert_eq!(
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows)),
|
cc.wheres,
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v.clone(), bill)),
|
ColumnIntersection(vec![
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(ComputedTable::Subquery(subquery))),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a, knows)),
|
||||||
]));
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v, bill)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(
|
||||||
|
ComputedTable::Subquery(Box::new(subquery))
|
||||||
|
)),
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that if any single clause in the `not` fails to resolve the whole clause is considered empty
|
// Test that if any single clause in the `not` fails to resolve the whole clause is considered empty
|
||||||
|
@ -522,9 +614,10 @@ mod testing {
|
||||||
[?x :foo/nope "Daphne"])]"#;
|
[?x :foo/nope "Daphne"])]"#;
|
||||||
let cc = alg(&schema, query);
|
let cc = alg(&schema, query);
|
||||||
assert!(!cc.is_known_empty());
|
assert!(!cc.is_known_empty());
|
||||||
compare_ccs(cc,
|
compare_ccs(
|
||||||
alg(&schema,
|
cc,
|
||||||
r#"[:find ?x :where [?x :foo/knows "Bill"]]"#));
|
alg(&schema, r#"[:find ?x :where [?x :foo/knows "Bill"]]"#),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test that if all the attributes in an `not` fail to resolve, the `cc` isn't considered empty.
|
/// Test that if all the attributes in an `not` fail to resolve, the `cc` isn't considered empty.
|
||||||
|
@ -538,9 +631,10 @@ mod testing {
|
||||||
[?x :foo/nope "Daphne"])]"#;
|
[?x :foo/nope "Daphne"])]"#;
|
||||||
let cc = alg(&schema, query);
|
let cc = alg(&schema, query);
|
||||||
assert!(!cc.is_known_empty());
|
assert!(!cc.is_known_empty());
|
||||||
compare_ccs(cc,
|
compare_ccs(
|
||||||
alg(&schema, r#"[:find ?x :where [?x :foo/knows "John"]]"#));
|
cc,
|
||||||
|
alg(&schema, r#"[:find ?x :where [?x :foo/knows "John"]]"#),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -554,7 +648,9 @@ mod testing {
|
||||||
let parsed = parse_find_string(query).expect("parse failed");
|
let parsed = parse_find_string(query).expect("parse failed");
|
||||||
let err = algebrize(known, parsed).expect_err("algebrization should have failed");
|
let err = algebrize(known, parsed).expect_err("algebrization should have failed");
|
||||||
match err {
|
match err {
|
||||||
AlgebrizerError::UnboundVariable(var) => { assert_eq!(var, PlainSymbol("?x".to_string())); },
|
AlgebrizerError::UnboundVariable(var) => {
|
||||||
|
assert_eq!(var, PlainSymbol("?x".to_string()));
|
||||||
|
}
|
||||||
x => panic!("expected Unbound Variable error, got {:?}", x),
|
x => panic!("expected Unbound Variable error, got {:?}", x),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,50 +9,26 @@
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
use std::collections::btree_map::Entry;
|
use std::collections::btree_map::Entry;
|
||||||
use std::collections::{
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
BTreeMap,
|
|
||||||
BTreeSet,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mentat_core::{
|
use core_traits::ValueTypeSet;
|
||||||
ValueTypeSet,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mentat_query::{
|
use edn::query::{
|
||||||
OrJoin,
|
OrJoin, OrWhereClause, Pattern, PatternNonValuePlace, PatternValuePlace, UnifyVars, Variable,
|
||||||
OrWhereClause,
|
|
||||||
Pattern,
|
|
||||||
PatternValuePlace,
|
|
||||||
PatternNonValuePlace,
|
|
||||||
UnifyVars,
|
|
||||||
Variable,
|
|
||||||
WhereClause,
|
WhereClause,
|
||||||
};
|
};
|
||||||
|
|
||||||
use clauses::{
|
use crate::clauses::{ConjoiningClauses, PushComputed};
|
||||||
ConjoiningClauses,
|
|
||||||
PushComputed,
|
|
||||||
};
|
|
||||||
|
|
||||||
use errors::{
|
use query_algebrizer_traits::errors::Result;
|
||||||
Result,
|
|
||||||
};
|
|
||||||
|
|
||||||
use types::{
|
use crate::types::{
|
||||||
ColumnConstraintOrAlternation,
|
ColumnAlternation, ColumnConstraintOrAlternation, ColumnIntersection, ComputedTable,
|
||||||
ColumnAlternation,
|
DatomsTable, EmptyBecause, EvolvedPattern, PlaceOrEmpty, QualifiedAlias, SourceAlias,
|
||||||
ColumnIntersection,
|
|
||||||
ComputedTable,
|
|
||||||
DatomsTable,
|
|
||||||
EmptyBecause,
|
|
||||||
EvolvedPattern,
|
|
||||||
PlaceOrEmpty,
|
|
||||||
QualifiedAlias,
|
|
||||||
SourceAlias,
|
|
||||||
VariableColumn,
|
VariableColumn,
|
||||||
};
|
};
|
||||||
|
|
||||||
use Known;
|
use crate::Known;
|
||||||
|
|
||||||
/// Return true if both left and right are the same variable or both are non-variable.
|
/// Return true if both left and right are the same variable or both are non-variable.
|
||||||
fn _simply_matches_place(left: &PatternNonValuePlace, right: &PatternNonValuePlace) -> bool {
|
fn _simply_matches_place(left: &PatternNonValuePlace, right: &PatternNonValuePlace) -> bool {
|
||||||
|
@ -101,7 +77,7 @@ impl ConjoiningClauses {
|
||||||
OrWhereClause::And(clauses) => {
|
OrWhereClause::And(clauses) => {
|
||||||
self.apply_clauses(known, clauses)?;
|
self.apply_clauses(known, clauses)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +93,7 @@ impl ConjoiningClauses {
|
||||||
1 if or_join.is_fully_unified() => {
|
1 if or_join.is_fully_unified() => {
|
||||||
let clause = or_join.clauses.pop().expect("there's a clause");
|
let clause = or_join.clauses.pop().expect("there's a clause");
|
||||||
self.apply_or_where_clause(known, clause)
|
self.apply_or_where_clause(known, clause)
|
||||||
},
|
}
|
||||||
// Either there's only one clause pattern, and it's not fully unified, or we
|
// Either there's only one clause pattern, and it's not fully unified, or we
|
||||||
// have multiple clauses.
|
// have multiple clauses.
|
||||||
// In the former case we can't just apply it: it includes a variable that we don't want
|
// In the former case we can't just apply it: it includes a variable that we don't want
|
||||||
|
@ -222,12 +198,10 @@ impl ConjoiningClauses {
|
||||||
let table = match self.make_evolved_attribute(&known, p.attribute.clone()) {
|
let table = match self.make_evolved_attribute(&known, p.attribute.clone()) {
|
||||||
Place((aaa, value_type)) => {
|
Place((aaa, value_type)) => {
|
||||||
match self.make_evolved_value(&known, value_type, p.value.clone()) {
|
match self.make_evolved_value(&known, value_type, p.value.clone()) {
|
||||||
Place(v) => {
|
Place(v) => self.table_for_places(known.schema, &aaa, &v),
|
||||||
self.table_for_places(known.schema, &aaa, &v)
|
|
||||||
},
|
|
||||||
Empty(e) => Err(e),
|
Empty(e) => Err(e),
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Empty(e) => Err(e),
|
Empty(e) => Err(e),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -237,11 +211,10 @@ impl ConjoiningClauses {
|
||||||
|
|
||||||
// Do not accumulate this pattern at all. Add lightness!
|
// Do not accumulate this pattern at all. Add lightness!
|
||||||
continue;
|
continue;
|
||||||
},
|
}
|
||||||
Ok(table) => {
|
Ok(table) => {
|
||||||
// Check the shape of the pattern against a previous pattern.
|
// Check the shape of the pattern against a previous pattern.
|
||||||
let same_shape =
|
let same_shape = if let Some(template) = patterns.get(0) {
|
||||||
if let Some(template) = patterns.get(0) {
|
|
||||||
template.source == p.source && // or-arms all use the same source anyway.
|
template.source == p.source && // or-arms all use the same source anyway.
|
||||||
_simply_matches_place(&template.entity, &p.entity) &&
|
_simply_matches_place(&template.entity, &p.entity) &&
|
||||||
_simply_matches_place(&template.attribute, &p.attribute) &&
|
_simply_matches_place(&template.attribute, &p.attribute) &&
|
||||||
|
@ -286,10 +259,7 @@ impl ConjoiningClauses {
|
||||||
.chain(clauses)
|
.chain(clauses)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
return DeconstructedOrJoin::Complex(OrJoin::new(
|
return DeconstructedOrJoin::Complex(OrJoin::new(UnifyVars::Implicit, reconstructed));
|
||||||
UnifyVars::Implicit,
|
|
||||||
reconstructed,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we got here without returning, then `patterns` is what we're working with.
|
// If we got here without returning, then `patterns` is what we're working with.
|
||||||
|
@ -298,7 +268,7 @@ impl ConjoiningClauses {
|
||||||
0 => {
|
0 => {
|
||||||
assert!(empty_because.is_some());
|
assert!(empty_because.is_some());
|
||||||
DeconstructedOrJoin::KnownEmpty(empty_because.unwrap())
|
DeconstructedOrJoin::KnownEmpty(empty_because.unwrap())
|
||||||
},
|
}
|
||||||
1 => DeconstructedOrJoin::UnitPattern(patterns.pop().unwrap()),
|
1 => DeconstructedOrJoin::UnitPattern(patterns.pop().unwrap()),
|
||||||
_ => DeconstructedOrJoin::Simple(patterns, mentioned_vars),
|
_ => DeconstructedOrJoin::Simple(patterns, mentioned_vars),
|
||||||
}
|
}
|
||||||
|
@ -309,42 +279,41 @@ impl ConjoiningClauses {
|
||||||
DeconstructedOrJoin::KnownSuccess => {
|
DeconstructedOrJoin::KnownSuccess => {
|
||||||
// The pattern came to us empty -- `(or)`. Do nothing.
|
// The pattern came to us empty -- `(or)`. Do nothing.
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
DeconstructedOrJoin::KnownEmpty(reason) => {
|
DeconstructedOrJoin::KnownEmpty(reason) => {
|
||||||
// There were no arms of the join that could be mapped to a table.
|
// There were no arms of the join that could be mapped to a table.
|
||||||
// The entire `or`, and thus the CC, cannot yield results.
|
// The entire `or`, and thus the CC, cannot yield results.
|
||||||
self.mark_known_empty(reason);
|
self.mark_known_empty(reason);
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
DeconstructedOrJoin::Unit(clause) => {
|
DeconstructedOrJoin::Unit(clause) => {
|
||||||
// There was only one clause. We're unifying all variables, so we can just apply here.
|
// There was only one clause. We're unifying all variables, so we can just apply here.
|
||||||
self.apply_or_where_clause(known, clause)
|
self.apply_or_where_clause(known, clause)
|
||||||
},
|
}
|
||||||
DeconstructedOrJoin::UnitPattern(pattern) => {
|
DeconstructedOrJoin::UnitPattern(pattern) => {
|
||||||
// Same, but simpler.
|
// Same, but simpler.
|
||||||
match self.make_evolved_pattern(known, pattern) {
|
match self.make_evolved_pattern(known, pattern) {
|
||||||
PlaceOrEmpty::Empty(e) => {
|
PlaceOrEmpty::Empty(e) => {
|
||||||
self.mark_known_empty(e);
|
self.mark_known_empty(e);
|
||||||
},
|
}
|
||||||
PlaceOrEmpty::Place(pattern) => {
|
PlaceOrEmpty::Place(pattern) => {
|
||||||
self.apply_pattern(known, pattern);
|
self.apply_pattern(known, pattern);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
DeconstructedOrJoin::Simple(patterns, mentioned_vars) => {
|
DeconstructedOrJoin::Simple(patterns, mentioned_vars) => {
|
||||||
// Hooray! Fully unified and plain ol' patterns that all use the same table.
|
// Hooray! Fully unified and plain ol' patterns that all use the same table.
|
||||||
// Go right ahead and produce a set of constraint alternations that we can collect,
|
// Go right ahead and produce a set of constraint alternations that we can collect,
|
||||||
// using a single table alias.
|
// using a single table alias.
|
||||||
self.apply_simple_or_join(known, patterns, mentioned_vars)
|
self.apply_simple_or_join(known, patterns, mentioned_vars)
|
||||||
},
|
}
|
||||||
DeconstructedOrJoin::Complex(or_join) => {
|
DeconstructedOrJoin::Complex(or_join) => {
|
||||||
// Do this the hard way.
|
// Do this the hard way.
|
||||||
self.apply_complex_or_join(known, or_join)
|
self.apply_complex_or_join(known, or_join)
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A simple `or` join is effectively a single pattern in which an individual column's bindings
|
/// A simple `or` join is effectively a single pattern in which an individual column's bindings
|
||||||
/// are not a single value. Rather than a pattern like
|
/// are not a single value. Rather than a pattern like
|
||||||
|
@ -373,27 +342,30 @@ impl ConjoiningClauses {
|
||||||
/// OR (datoms00.a = 98 AND datoms00.v = 'Peter')
|
/// OR (datoms00.a = 98 AND datoms00.v = 'Peter')
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
fn apply_simple_or_join(&mut self,
|
fn apply_simple_or_join(
|
||||||
|
&mut self,
|
||||||
known: Known,
|
known: Known,
|
||||||
patterns: Vec<Pattern>,
|
patterns: Vec<Pattern>,
|
||||||
mentioned_vars: BTreeSet<Variable>)
|
mentioned_vars: BTreeSet<Variable>,
|
||||||
-> Result<()> {
|
) -> Result<()> {
|
||||||
if self.is_known_empty() {
|
if self.is_known_empty() {
|
||||||
return Ok(())
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
assert!(patterns.len() >= 2);
|
assert!(patterns.len() >= 2);
|
||||||
|
|
||||||
let patterns: Vec<EvolvedPattern> = patterns.into_iter().filter_map(|pattern| {
|
let patterns: Vec<EvolvedPattern> = patterns
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|pattern| {
|
||||||
match self.make_evolved_pattern(known, pattern) {
|
match self.make_evolved_pattern(known, pattern) {
|
||||||
PlaceOrEmpty::Empty(_e) => {
|
PlaceOrEmpty::Empty(_e) => {
|
||||||
// Never mind.
|
// Never mind.
|
||||||
None
|
None
|
||||||
},
|
}
|
||||||
PlaceOrEmpty::Place(p) => Some(p),
|
PlaceOrEmpty::Place(p) => Some(p),
|
||||||
}
|
}
|
||||||
}).collect();
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
// Begin by building a base CC that we'll use to produce constraints from each pattern.
|
// Begin by building a base CC that we'll use to produce constraints from each pattern.
|
||||||
// Populate this base CC with whatever variables are already known from the CC to which
|
// Populate this base CC with whatever variables are already known from the CC to which
|
||||||
|
@ -405,7 +377,9 @@ impl ConjoiningClauses {
|
||||||
|
|
||||||
// We expect this to always work: if it doesn't, it means we should never have got to this
|
// We expect this to always work: if it doesn't, it means we should never have got to this
|
||||||
// point.
|
// point.
|
||||||
let source_alias = self.alias_table(known.schema, &patterns[0]).expect("couldn't get table");
|
let source_alias = self
|
||||||
|
.alias_table(known.schema, &patterns[0])
|
||||||
|
.expect("couldn't get table");
|
||||||
|
|
||||||
// This is where we'll collect everything we eventually add to the destination CC.
|
// This is where we'll collect everything we eventually add to the destination CC.
|
||||||
let mut folded = ConjoiningClauses::default();
|
let mut folded = ConjoiningClauses::default();
|
||||||
|
@ -432,8 +406,8 @@ impl ConjoiningClauses {
|
||||||
// :where [?a :some/int ?x]
|
// :where [?a :some/int ?x]
|
||||||
// [_ :some/otherint ?x]]
|
// [_ :some/otherint ?x]]
|
||||||
// ```
|
// ```
|
||||||
let mut receptacles =
|
let mut receptacles = patterns
|
||||||
patterns.into_iter()
|
.into_iter()
|
||||||
.map(|pattern| {
|
.map(|pattern| {
|
||||||
let mut receptacle = template.make_receptacle();
|
let mut receptacle = template.make_receptacle();
|
||||||
receptacle.apply_pattern_clause_for_alias(known, &pattern, &source_alias);
|
receptacle.apply_pattern_clause_for_alias(known, &pattern, &source_alias);
|
||||||
|
@ -443,12 +417,14 @@ impl ConjoiningClauses {
|
||||||
|
|
||||||
// Let's see if we can grab a reason if every pattern failed.
|
// Let's see if we can grab a reason if every pattern failed.
|
||||||
// If every pattern failed, we can just take the first!
|
// If every pattern failed, we can just take the first!
|
||||||
let reason = receptacles.peek()
|
let reason = receptacles
|
||||||
|
.peek()
|
||||||
.map(|r| r.empty_because.clone())
|
.map(|r| r.empty_because.clone())
|
||||||
.unwrap_or(None);
|
.unwrap_or(None);
|
||||||
|
|
||||||
// Filter out empties.
|
// Filter out empties.
|
||||||
let mut receptacles = receptacles.filter(|receptacle| !receptacle.is_known_empty())
|
let mut receptacles = receptacles
|
||||||
|
.filter(|receptacle| !receptacle.is_known_empty())
|
||||||
.peekable();
|
.peekable();
|
||||||
|
|
||||||
// We need to copy the column bindings from one of the receptacles. Because this is a simple
|
// We need to copy the column bindings from one of the receptacles. Because this is a simple
|
||||||
|
@ -460,10 +436,10 @@ impl ConjoiningClauses {
|
||||||
match self.column_bindings.entry(v.clone()) {
|
match self.column_bindings.entry(v.clone()) {
|
||||||
Entry::Vacant(e) => {
|
Entry::Vacant(e) => {
|
||||||
e.insert(cols.clone());
|
e.insert(cols.clone());
|
||||||
},
|
}
|
||||||
Entry::Occupied(mut e) => {
|
Entry::Occupied(mut e) => {
|
||||||
e.get_mut().append(&mut cols.clone());
|
e.get_mut().append(&mut cols.clone());
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -483,7 +459,10 @@ impl ConjoiningClauses {
|
||||||
// we might know that if `?x` is an integer then `?y` is a string, or vice versa, but at
|
// we might know that if `?x` is an integer then `?y` is a string, or vice versa, but at
|
||||||
// this point we'll simply state that `?x` and `?y` can both be integers or strings.
|
// this point we'll simply state that `?x` and `?y` can both be integers or strings.
|
||||||
|
|
||||||
fn vec_for_iterator<T, I, U>(iter: &I) -> Vec<T> where I: Iterator<Item=U> {
|
fn vec_for_iterator<T, I, U>(iter: &I) -> Vec<T>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = U>,
|
||||||
|
{
|
||||||
match iter.size_hint().1 {
|
match iter.size_hint().1 {
|
||||||
None => Vec::new(),
|
None => Vec::new(),
|
||||||
Some(expected) => Vec::with_capacity(expected),
|
Some(expected) => Vec::with_capacity(expected),
|
||||||
|
@ -593,10 +572,10 @@ impl ConjoiningClauses {
|
||||||
match clause {
|
match clause {
|
||||||
OrWhereClause::And(clauses) => {
|
OrWhereClause::And(clauses) => {
|
||||||
receptacle.apply_clauses(known, clauses)?;
|
receptacle.apply_clauses(known, clauses)?;
|
||||||
},
|
}
|
||||||
OrWhereClause::Clause(clause) => {
|
OrWhereClause::Clause(clause) => {
|
||||||
receptacle.apply_clause(known, clause)?;
|
receptacle.apply_clause(known, clause)?;
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
if receptacle.is_known_empty() {
|
if receptacle.is_known_empty() {
|
||||||
empty_because = receptacle.empty_because;
|
empty_because = receptacle.empty_because;
|
||||||
|
@ -663,7 +642,7 @@ impl ConjoiningClauses {
|
||||||
// For any variable which has an imprecise type anywhere in the UNION, add it to the
|
// For any variable which has an imprecise type anywhere in the UNION, add it to the
|
||||||
// set that needs type extraction. All UNION arms must project the same columns.
|
// set that needs type extraction. All UNION arms must project the same columns.
|
||||||
for var in projection.iter() {
|
for var in projection.iter() {
|
||||||
if acc.iter().any(|cc| !cc.known_type(var).is_some()) {
|
if acc.iter().any(|cc| cc.known_type(var).is_none()) {
|
||||||
type_needed.insert(var.clone());
|
type_needed.insert(var.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -681,7 +660,8 @@ impl ConjoiningClauses {
|
||||||
// Note that we start with the first clause's type information.
|
// Note that we start with the first clause's type information.
|
||||||
{
|
{
|
||||||
let mut clauses = acc.iter();
|
let mut clauses = acc.iter();
|
||||||
let mut additional_types = clauses.next()
|
let mut additional_types = clauses
|
||||||
|
.next()
|
||||||
.expect("there to be at least one clause")
|
.expect("there to be at least one clause")
|
||||||
.known_types
|
.known_types
|
||||||
.clone();
|
.clone();
|
||||||
|
@ -692,7 +672,7 @@ impl ConjoiningClauses {
|
||||||
}
|
}
|
||||||
|
|
||||||
let union = ComputedTable::Union {
|
let union = ComputedTable::Union {
|
||||||
projection: projection,
|
projection,
|
||||||
type_extraction: type_needed,
|
type_extraction: type_needed,
|
||||||
arms: acc,
|
arms: acc,
|
||||||
};
|
};
|
||||||
|
@ -702,10 +682,18 @@ impl ConjoiningClauses {
|
||||||
// Stitch the computed table into column_bindings, so we get cross-linking.
|
// Stitch the computed table into column_bindings, so we get cross-linking.
|
||||||
let schema = known.schema;
|
let schema = known.schema;
|
||||||
for var in var_associations.into_iter() {
|
for var in var_associations.into_iter() {
|
||||||
self.bind_column_to_var(schema, alias.clone(), VariableColumn::Variable(var.clone()), var);
|
self.bind_column_to_var(
|
||||||
|
schema,
|
||||||
|
alias.clone(),
|
||||||
|
VariableColumn::Variable(var.clone()),
|
||||||
|
var,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
for var in type_associations.into_iter() {
|
for var in type_associations.into_iter() {
|
||||||
self.extracted_types.insert(var.clone(), QualifiedAlias::new(alias.clone(), VariableColumn::VariableTypeTag(var)));
|
self.extracted_types.insert(
|
||||||
|
var.clone(),
|
||||||
|
QualifiedAlias::new(alias.clone(), VariableColumn::VariableTypeTag(var)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
self.from.push(SourceAlias(table, alias));
|
self.from.push(SourceAlias(table, alias));
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -713,8 +701,10 @@ impl ConjoiningClauses {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper to fold together a set of type maps.
|
/// Helper to fold together a set of type maps.
|
||||||
fn union_types(into: &mut BTreeMap<Variable, ValueTypeSet>,
|
fn union_types(
|
||||||
additional_types: &BTreeMap<Variable, ValueTypeSet>) {
|
into: &mut BTreeMap<Variable, ValueTypeSet>,
|
||||||
|
additional_types: &BTreeMap<Variable, ValueTypeSet>,
|
||||||
|
) {
|
||||||
// We want the exclusive disjunction -- any variable not mentioned in both sets -- to default
|
// We want the exclusive disjunction -- any variable not mentioned in both sets -- to default
|
||||||
// to ValueTypeSet::Any.
|
// to ValueTypeSet::Any.
|
||||||
// This is necessary because we lazily populate known_types, so sometimes the type set will
|
// This is necessary because we lazily populate known_types, so sometimes the type set will
|
||||||
|
@ -727,7 +717,8 @@ fn union_types(into: &mut BTreeMap<Variable, ValueTypeSet>,
|
||||||
{
|
{
|
||||||
let i: BTreeSet<&Variable> = into.keys().collect();
|
let i: BTreeSet<&Variable> = into.keys().collect();
|
||||||
let a: BTreeSet<&Variable> = additional_types.keys().collect();
|
let a: BTreeSet<&Variable> = additional_types.keys().collect();
|
||||||
any = i.symmetric_difference(&a)
|
any = i
|
||||||
|
.symmetric_difference(&a)
|
||||||
.map(|v| ((*v).clone(), ValueTypeSet::any()))
|
.map(|v| ((*v).clone(), ValueTypeSet::any()))
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
|
@ -736,12 +727,12 @@ fn union_types(into: &mut BTreeMap<Variable, ValueTypeSet>,
|
||||||
for (var, new_types) in additional_types {
|
for (var, new_types) in additional_types {
|
||||||
match into.entry(var.clone()) {
|
match into.entry(var.clone()) {
|
||||||
Entry::Vacant(e) => {
|
Entry::Vacant(e) => {
|
||||||
e.insert(new_types.clone());
|
e.insert(*new_types);
|
||||||
},
|
}
|
||||||
Entry::Occupied(mut e) => {
|
Entry::Occupied(mut e) => {
|
||||||
let new = e.get().union(&new_types);
|
let new = e.get().union(*new_types);
|
||||||
e.insert(new);
|
e.insert(new);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -753,38 +744,20 @@ fn union_types(into: &mut BTreeMap<Variable, ValueTypeSet>,
|
||||||
mod testing {
|
mod testing {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use mentat_core::{
|
use core_traits::{Attribute, TypedValue, ValueType};
|
||||||
Attribute,
|
|
||||||
Schema,
|
|
||||||
TypedValue,
|
|
||||||
ValueType,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mentat_query::{
|
use mentat_core::Schema;
|
||||||
Keyword,
|
|
||||||
Variable,
|
|
||||||
};
|
|
||||||
|
|
||||||
use clauses::{
|
use edn::query::{Keyword, Variable};
|
||||||
add_attribute,
|
|
||||||
associate_ident,
|
|
||||||
};
|
|
||||||
|
|
||||||
use types::{
|
use crate::clauses::{add_attribute, associate_ident};
|
||||||
ColumnConstraint,
|
|
||||||
DatomsColumn,
|
use crate::types::{
|
||||||
DatomsTable,
|
ColumnConstraint, DatomsColumn, DatomsTable, Inequality, QualifiedAlias, QueryValue,
|
||||||
Inequality,
|
|
||||||
QualifiedAlias,
|
|
||||||
QueryValue,
|
|
||||||
SourceAlias,
|
SourceAlias,
|
||||||
};
|
};
|
||||||
|
|
||||||
use {
|
use crate::{algebrize, algebrize_with_counter, parse_find_string};
|
||||||
algebrize,
|
|
||||||
algebrize_with_counter,
|
|
||||||
parse_find_string,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn alg(known: Known, input: &str) -> ConjoiningClauses {
|
fn alg(known: Known, input: &str) -> ConjoiningClauses {
|
||||||
let parsed = parse_find_string(input).expect("parse failed");
|
let parsed = parse_find_string(input).expect("parse failed");
|
||||||
|
@ -795,7 +768,9 @@ mod testing {
|
||||||
/// simpler version.
|
/// simpler version.
|
||||||
fn alg_c(known: Known, counter: usize, input: &str) -> ConjoiningClauses {
|
fn alg_c(known: Known, counter: usize, input: &str) -> ConjoiningClauses {
|
||||||
let parsed = parse_find_string(input).expect("parse failed");
|
let parsed = parse_find_string(input).expect("parse failed");
|
||||||
algebrize_with_counter(known, parsed, counter).expect("algebrize failed").cc
|
algebrize_with_counter(known, parsed, counter)
|
||||||
|
.expect("algebrize failed")
|
||||||
|
.cc
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compare_ccs(left: ConjoiningClauses, right: ConjoiningClauses) {
|
fn compare_ccs(left: ConjoiningClauses, right: ConjoiningClauses) {
|
||||||
|
@ -810,31 +785,51 @@ mod testing {
|
||||||
associate_ident(&mut schema, Keyword::namespaced("foo", "parent"), 67);
|
associate_ident(&mut schema, Keyword::namespaced("foo", "parent"), 67);
|
||||||
associate_ident(&mut schema, Keyword::namespaced("foo", "age"), 68);
|
associate_ident(&mut schema, Keyword::namespaced("foo", "age"), 68);
|
||||||
associate_ident(&mut schema, Keyword::namespaced("foo", "height"), 69);
|
associate_ident(&mut schema, Keyword::namespaced("foo", "height"), 69);
|
||||||
add_attribute(&mut schema, 65, Attribute {
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
|
65,
|
||||||
|
Attribute {
|
||||||
value_type: ValueType::String,
|
value_type: ValueType::String,
|
||||||
multival: false,
|
multival: false,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
},
|
||||||
add_attribute(&mut schema, 66, Attribute {
|
);
|
||||||
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
|
66,
|
||||||
|
Attribute {
|
||||||
value_type: ValueType::String,
|
value_type: ValueType::String,
|
||||||
multival: true,
|
multival: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
},
|
||||||
add_attribute(&mut schema, 67, Attribute {
|
);
|
||||||
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
|
67,
|
||||||
|
Attribute {
|
||||||
value_type: ValueType::String,
|
value_type: ValueType::String,
|
||||||
multival: true,
|
multival: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
},
|
||||||
add_attribute(&mut schema, 68, Attribute {
|
);
|
||||||
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
|
68,
|
||||||
|
Attribute {
|
||||||
value_type: ValueType::Long,
|
value_type: ValueType::Long,
|
||||||
multival: false,
|
multival: false,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
},
|
||||||
add_attribute(&mut schema, 69, Attribute {
|
);
|
||||||
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
|
69,
|
||||||
|
Attribute {
|
||||||
value_type: ValueType::Long,
|
value_type: ValueType::Long,
|
||||||
multival: false,
|
multival: false,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
},
|
||||||
|
);
|
||||||
schema
|
schema
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -850,7 +845,12 @@ mod testing {
|
||||||
[?x :foo/nope3 "Daphne"])]"#;
|
[?x :foo/nope3 "Daphne"])]"#;
|
||||||
let cc = alg(known, query);
|
let cc = alg(known, query);
|
||||||
assert!(cc.is_known_empty());
|
assert!(cc.is_known_empty());
|
||||||
assert_eq!(cc.empty_because, Some(EmptyBecause::UnresolvedIdent(Keyword::namespaced("foo", "nope3"))));
|
assert_eq!(
|
||||||
|
cc.empty_because,
|
||||||
|
Some(EmptyBecause::UnresolvedIdent(Keyword::namespaced(
|
||||||
|
"foo", "nope3"
|
||||||
|
)))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test that if only one of the attributes in an `or` resolves, it's equivalent to a simple query.
|
/// Test that if only one of the attributes in an `or` resolves, it's equivalent to a simple query.
|
||||||
|
@ -865,7 +865,10 @@ mod testing {
|
||||||
[?x :foo/nope "Daphne"])]"#;
|
[?x :foo/nope "Daphne"])]"#;
|
||||||
let cc = alg(known, query);
|
let cc = alg(known, query);
|
||||||
assert!(!cc.is_known_empty());
|
assert!(!cc.is_known_empty());
|
||||||
compare_ccs(cc, alg(known, r#"[:find ?x :where [?x :foo/parent "Ámbar"]]"#));
|
compare_ccs(
|
||||||
|
cc,
|
||||||
|
alg(known, r#"[:find ?x :where [?x :foo/parent "Ámbar"]]"#),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple alternation.
|
// Simple alternation.
|
||||||
|
@ -891,19 +894,41 @@ mod testing {
|
||||||
let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne"));
|
let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne"));
|
||||||
|
|
||||||
assert!(!cc.is_known_empty());
|
assert!(!cc.is_known_empty());
|
||||||
assert_eq!(cc.wheres, ColumnIntersection(vec![
|
assert_eq!(
|
||||||
ColumnConstraintOrAlternation::Alternation(
|
cc.wheres,
|
||||||
|
ColumnIntersection(vec![ColumnConstraintOrAlternation::Alternation(
|
||||||
ColumnAlternation(vec![
|
ColumnAlternation(vec![
|
||||||
ColumnIntersection(vec![
|
ColumnIntersection(vec![
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows.clone())),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v.clone(), john))]),
|
d0a.clone(),
|
||||||
|
knows.clone()
|
||||||
|
)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
|
d0v.clone(),
|
||||||
|
john
|
||||||
|
))
|
||||||
|
]),
|
||||||
ColumnIntersection(vec![
|
ColumnIntersection(vec![
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), parent)),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v.clone(), ambar))]),
|
d0a.clone(),
|
||||||
|
parent
|
||||||
|
)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
|
d0v.clone(),
|
||||||
|
ambar
|
||||||
|
))
|
||||||
|
]),
|
||||||
ColumnIntersection(vec![
|
ColumnIntersection(vec![
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows)),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v.clone(), daphne))]),
|
d0a, knows
|
||||||
]))]));
|
)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
|
d0v, daphne
|
||||||
|
))
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
)])
|
||||||
|
);
|
||||||
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e]));
|
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e]));
|
||||||
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0)]);
|
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0)]);
|
||||||
}
|
}
|
||||||
|
@ -937,26 +962,55 @@ mod testing {
|
||||||
let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne"));
|
let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne"));
|
||||||
|
|
||||||
assert!(!cc.is_known_empty());
|
assert!(!cc.is_known_empty());
|
||||||
assert_eq!(cc.wheres, ColumnIntersection(vec![
|
assert_eq!(
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), name.clone())),
|
cc.wheres,
|
||||||
ColumnConstraintOrAlternation::Alternation(
|
|
||||||
ColumnAlternation(vec![
|
|
||||||
ColumnIntersection(vec![
|
ColumnIntersection(vec![
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows.clone())),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a, name)),
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), john))]),
|
ColumnConstraintOrAlternation::Alternation(ColumnAlternation(vec![
|
||||||
ColumnIntersection(vec![
|
ColumnIntersection(vec![
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), parent)),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), ambar))]),
|
d1a.clone(),
|
||||||
|
knows.clone()
|
||||||
|
)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
|
d1v.clone(),
|
||||||
|
john
|
||||||
|
))
|
||||||
|
]),
|
||||||
ColumnIntersection(vec![
|
ColumnIntersection(vec![
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows)),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), daphne))]),
|
d1a.clone(),
|
||||||
|
parent
|
||||||
|
)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
|
d1v.clone(),
|
||||||
|
ambar
|
||||||
|
))
|
||||||
|
]),
|
||||||
|
ColumnIntersection(vec![
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
|
d1a, knows
|
||||||
|
)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
|
d1v, daphne
|
||||||
|
))
|
||||||
|
]),
|
||||||
])),
|
])),
|
||||||
// The outer pattern joins against the `or`.
|
// The outer pattern joins against the `or`.
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d1e.clone()))),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
]));
|
d0e.clone(),
|
||||||
|
QueryValue::Column(d1e.clone())
|
||||||
|
)),
|
||||||
|
])
|
||||||
|
);
|
||||||
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e, d1e]));
|
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e, d1e]));
|
||||||
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0),
|
assert_eq!(
|
||||||
SourceAlias(DatomsTable::Datoms, d1)]);
|
cc.from,
|
||||||
|
vec![
|
||||||
|
SourceAlias(DatomsTable::Datoms, d0),
|
||||||
|
SourceAlias(DatomsTable::Datoms, d1)
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alternation with a pattern and a predicate.
|
// Alternation with a pattern and a predicate.
|
||||||
|
@ -987,28 +1041,50 @@ mod testing {
|
||||||
let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne"));
|
let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne"));
|
||||||
|
|
||||||
assert!(!cc.is_known_empty());
|
assert!(!cc.is_known_empty());
|
||||||
assert_eq!(cc.wheres, ColumnIntersection(vec![
|
assert_eq!(
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), age.clone())),
|
cc.wheres,
|
||||||
|
ColumnIntersection(vec![
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a, age)),
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Inequality {
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Inequality {
|
||||||
operator: Inequality::LessThan,
|
operator: Inequality::LessThan,
|
||||||
left: QueryValue::Column(d0v.clone()),
|
left: QueryValue::Column(d0v),
|
||||||
right: QueryValue::TypedValue(TypedValue::Long(30)),
|
right: QueryValue::TypedValue(TypedValue::Long(30)),
|
||||||
}),
|
}),
|
||||||
ColumnConstraintOrAlternation::Alternation(
|
ColumnConstraintOrAlternation::Alternation(ColumnAlternation(vec![
|
||||||
ColumnAlternation(vec![
|
|
||||||
ColumnIntersection(vec![
|
ColumnIntersection(vec![
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows.clone())),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), john))]),
|
d1a.clone(),
|
||||||
|
knows.clone()
|
||||||
|
)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
|
d1v.clone(),
|
||||||
|
john
|
||||||
|
))
|
||||||
|
]),
|
||||||
ColumnIntersection(vec![
|
ColumnIntersection(vec![
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows)),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), daphne))]),
|
d1a, knows
|
||||||
|
)),
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
|
d1v, daphne
|
||||||
|
))
|
||||||
|
]),
|
||||||
])),
|
])),
|
||||||
// The outer pattern joins against the `or`.
|
// The outer pattern joins against the `or`.
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d1e.clone()))),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
]));
|
d0e.clone(),
|
||||||
|
QueryValue::Column(d1e.clone())
|
||||||
|
)),
|
||||||
|
])
|
||||||
|
);
|
||||||
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e, d1e]));
|
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e, d1e]));
|
||||||
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0),
|
assert_eq!(
|
||||||
SourceAlias(DatomsTable::Datoms, d1)]);
|
cc.from,
|
||||||
|
vec![
|
||||||
|
SourceAlias(DatomsTable::Datoms, d0),
|
||||||
|
SourceAlias(DatomsTable::Datoms, d1)
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// These two are not equivalent:
|
// These two are not equivalent:
|
||||||
|
@ -1033,18 +1109,29 @@ mod testing {
|
||||||
let knows = QueryValue::Entid(66);
|
let knows = QueryValue::Entid(66);
|
||||||
|
|
||||||
assert!(!cc.is_known_empty());
|
assert!(!cc.is_known_empty());
|
||||||
assert_eq!(cc.wheres, ColumnIntersection(vec![
|
assert_eq!(
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows.clone())),
|
cc.wheres,
|
||||||
|
ColumnIntersection(vec![
|
||||||
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a, knows)),
|
||||||
// The outer pattern joins against the `or` on the entity, but not value -- ?y means
|
// The outer pattern joins against the `or` on the entity, but not value -- ?y means
|
||||||
// different things in each place.
|
// different things in each place.
|
||||||
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(c0x.clone()))),
|
ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(
|
||||||
]));
|
d0e.clone(),
|
||||||
|
QueryValue::Column(c0x.clone())
|
||||||
|
)),
|
||||||
|
])
|
||||||
|
);
|
||||||
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e, c0x]));
|
assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e, c0x]));
|
||||||
|
|
||||||
// ?y does not have a binding in the `or-join` pattern.
|
// ?y does not have a binding in the `or-join` pattern.
|
||||||
assert_eq!(cc.column_bindings.get(&vy), Some(&vec![d0v]));
|
assert_eq!(cc.column_bindings.get(&vy), Some(&vec![d0v]));
|
||||||
assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0),
|
assert_eq!(
|
||||||
SourceAlias(DatomsTable::Computed(0), c0)]);
|
cc.from,
|
||||||
|
vec![
|
||||||
|
SourceAlias(DatomsTable::Datoms, d0),
|
||||||
|
SourceAlias(DatomsTable::Computed(0), c0)
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// These two are equivalent:
|
// These two are equivalent:
|
||||||
|
@ -1060,8 +1147,7 @@ mod testing {
|
||||||
let flat_query = r#"[:find ?x
|
let flat_query = r#"[:find ?x
|
||||||
:where [?x :foo/knows ?y]
|
:where [?x :foo/knows ?y]
|
||||||
[?x :foo/parent ?y]]"#;
|
[?x :foo/parent ?y]]"#;
|
||||||
compare_ccs(alg(known, or_query),
|
compare_ccs(alg(known, or_query), alg(known, flat_query));
|
||||||
alg(known, flat_query));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Elision of `and`.
|
// Elision of `and`.
|
||||||
|
@ -1075,8 +1161,7 @@ mod testing {
|
||||||
let flat_query = r#"[:find ?x
|
let flat_query = r#"[:find ?x
|
||||||
:where [?x :foo/parent ?y]
|
:where [?x :foo/parent ?y]
|
||||||
[?x :foo/age 7]]"#;
|
[?x :foo/age 7]]"#;
|
||||||
compare_ccs(alg(known, or_query),
|
compare_ccs(alg(known, or_query), alg(known, flat_query));
|
||||||
alg(known, flat_query));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alternation with `and`.
|
// Alternation with `and`.
|
||||||
|
@ -1098,31 +1183,45 @@ mod testing {
|
||||||
let cc = alg(known, query);
|
let cc = alg(known, query);
|
||||||
let mut tables = cc.computed_tables.into_iter();
|
let mut tables = cc.computed_tables.into_iter();
|
||||||
match (tables.next(), tables.next()) {
|
match (tables.next(), tables.next()) {
|
||||||
(Some(ComputedTable::Union { projection, type_extraction, arms }), None) => {
|
(
|
||||||
assert_eq!(projection, vec![Variable::from_valid_name("?x")].into_iter().collect());
|
Some(ComputedTable::Union {
|
||||||
|
projection,
|
||||||
|
type_extraction,
|
||||||
|
arms,
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
) => {
|
||||||
|
assert_eq!(
|
||||||
|
projection,
|
||||||
|
vec![Variable::from_valid_name("?x")].into_iter().collect()
|
||||||
|
);
|
||||||
assert!(type_extraction.is_empty());
|
assert!(type_extraction.is_empty());
|
||||||
|
|
||||||
let mut arms = arms.into_iter();
|
let mut arms = arms.into_iter();
|
||||||
match (arms.next(), arms.next(), arms.next()) {
|
match (arms.next(), arms.next(), arms.next()) {
|
||||||
(Some(and), Some(pattern), None) => {
|
(Some(and), Some(pattern), None) => {
|
||||||
let expected_and = alg_c(known,
|
let expected_and = alg_c(
|
||||||
|
known,
|
||||||
0, // The first pattern to be processed.
|
0, // The first pattern to be processed.
|
||||||
r#"[:find ?x :where [?x :foo/knows "John"] [?x :foo/parent "Ámbar"]]"#);
|
r#"[:find ?x :where [?x :foo/knows "John"] [?x :foo/parent "Ámbar"]]"#,
|
||||||
|
);
|
||||||
compare_ccs(and, expected_and);
|
compare_ccs(and, expected_and);
|
||||||
|
|
||||||
let expected_pattern = alg_c(known,
|
let expected_pattern = alg_c(
|
||||||
|
known,
|
||||||
2, // Two aliases taken by the other arm.
|
2, // Two aliases taken by the other arm.
|
||||||
r#"[:find ?x :where [?x :foo/knows "Daphne"]]"#);
|
r#"[:find ?x :where [?x :foo/knows "Daphne"]]"#,
|
||||||
|
);
|
||||||
compare_ccs(pattern, expected_pattern);
|
compare_ccs(pattern, expected_pattern);
|
||||||
},
|
}
|
||||||
_ => {
|
_ => {
|
||||||
panic!("Expected two arms");
|
panic!("Expected two arms");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
_ => {
|
_ => {
|
||||||
panic!("Didn't get two inner tables.");
|
panic!("Didn't get two inner tables.");
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -8,36 +8,21 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
use mentat_core::{
|
use core_traits::{ValueType, ValueTypeSet};
|
||||||
Schema,
|
|
||||||
ValueType,
|
|
||||||
ValueTypeSet,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mentat_query::{
|
use mentat_core::Schema;
|
||||||
FnArg,
|
|
||||||
PlainSymbol,
|
|
||||||
Predicate,
|
|
||||||
TypeAnnotation,
|
|
||||||
};
|
|
||||||
|
|
||||||
use clauses::ConjoiningClauses;
|
use edn::query::{FnArg, PlainSymbol, Predicate, TypeAnnotation};
|
||||||
|
|
||||||
use clauses::convert::ValueTypes;
|
use crate::clauses::ConjoiningClauses;
|
||||||
|
|
||||||
use errors::{
|
use crate::clauses::convert::ValueTypes;
|
||||||
AlgebrizerError,
|
|
||||||
Result,
|
|
||||||
};
|
|
||||||
|
|
||||||
use types::{
|
use query_algebrizer_traits::errors::{AlgebrizerError, Result};
|
||||||
ColumnConstraint,
|
|
||||||
EmptyBecause,
|
|
||||||
Inequality,
|
|
||||||
QueryValue,
|
|
||||||
};
|
|
||||||
|
|
||||||
use Known;
|
use crate::types::{ColumnConstraint, EmptyBecause, Inequality, QueryValue};
|
||||||
|
|
||||||
|
use crate::Known;
|
||||||
|
|
||||||
/// Application of predicates.
|
/// Application of predicates.
|
||||||
impl ConjoiningClauses {
|
impl ConjoiningClauses {
|
||||||
|
@ -59,7 +44,7 @@ impl ConjoiningClauses {
|
||||||
|
|
||||||
fn potential_types(&self, schema: &Schema, fn_arg: &FnArg) -> Result<ValueTypeSet> {
|
fn potential_types(&self, schema: &Schema, fn_arg: &FnArg) -> Result<ValueTypeSet> {
|
||||||
match fn_arg {
|
match fn_arg {
|
||||||
&FnArg::Variable(ref v) => Ok(self.known_type_set(v)),
|
FnArg::Variable(ref v) => Ok(self.known_type_set(v)),
|
||||||
_ => fn_arg.potential_types(schema),
|
_ => fn_arg.potential_types(schema),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,8 +53,14 @@ impl ConjoiningClauses {
|
||||||
/// to be a specific ValueType.
|
/// to be a specific ValueType.
|
||||||
pub(crate) fn apply_type_anno(&mut self, anno: &TypeAnnotation) -> Result<()> {
|
pub(crate) fn apply_type_anno(&mut self, anno: &TypeAnnotation) -> Result<()> {
|
||||||
match ValueType::from_keyword(&anno.value_type) {
|
match ValueType::from_keyword(&anno.value_type) {
|
||||||
Some(value_type) => self.add_type_requirement(anno.variable.clone(), ValueTypeSet::of_one(value_type)),
|
Some(value_type) => {
|
||||||
None => bail!(AlgebrizerError::InvalidArgumentType(PlainSymbol::plain("type"), ValueTypeSet::any(), 2)),
|
self.add_type_requirement(anno.variable.clone(), ValueTypeSet::of_one(value_type))
|
||||||
|
}
|
||||||
|
None => bail!(AlgebrizerError::InvalidArgumentType(
|
||||||
|
PlainSymbol::plain("type"),
|
||||||
|
ValueTypeSet::any(),
|
||||||
|
2
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -78,9 +69,18 @@ impl ConjoiningClauses {
|
||||||
/// - Resolves variables and converts types to those more amenable to SQL.
|
/// - Resolves variables and converts types to those more amenable to SQL.
|
||||||
/// - Ensures that the predicate functions name a known operator.
|
/// - Ensures that the predicate functions name a known operator.
|
||||||
/// - Accumulates an `Inequality` constraint into the `wheres` list.
|
/// - Accumulates an `Inequality` constraint into the `wheres` list.
|
||||||
pub(crate) fn apply_inequality(&mut self, known: Known, comparison: Inequality, predicate: Predicate) -> Result<()> {
|
pub(crate) fn apply_inequality(
|
||||||
|
&mut self,
|
||||||
|
known: Known,
|
||||||
|
comparison: Inequality,
|
||||||
|
predicate: Predicate,
|
||||||
|
) -> Result<()> {
|
||||||
if predicate.args.len() != 2 {
|
if predicate.args.len() != 2 {
|
||||||
bail!(AlgebrizerError::InvalidNumberOfArguments(predicate.operator.clone(), predicate.args.len(), 2));
|
bail!(AlgebrizerError::InvalidNumberOfArguments(
|
||||||
|
predicate.operator.clone(),
|
||||||
|
predicate.args.len(),
|
||||||
|
2
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Go from arguments -- parser output -- to columns or values.
|
// Go from arguments -- parser output -- to columns or values.
|
||||||
|
@ -90,20 +90,29 @@ impl ConjoiningClauses {
|
||||||
let left = args.next().expect("two args");
|
let left = args.next().expect("two args");
|
||||||
let right = args.next().expect("two args");
|
let right = args.next().expect("two args");
|
||||||
|
|
||||||
|
|
||||||
// The types we're handling here must be the intersection of the possible types of the arguments,
|
// The types we're handling here must be the intersection of the possible types of the arguments,
|
||||||
// the known types of any variables, and the types supported by our inequality operators.
|
// the known types of any variables, and the types supported by our inequality operators.
|
||||||
let supported_types = comparison.supported_types();
|
let supported_types = comparison.supported_types();
|
||||||
let mut left_types = self.potential_types(known.schema, &left)?
|
let mut left_types = self
|
||||||
.intersection(&supported_types);
|
.potential_types(known.schema, &left)?
|
||||||
|
.intersection(supported_types);
|
||||||
if left_types.is_empty() {
|
if left_types.is_empty() {
|
||||||
bail!(AlgebrizerError::InvalidArgumentType(predicate.operator.clone(), supported_types, 0));
|
bail!(AlgebrizerError::InvalidArgumentType(
|
||||||
|
predicate.operator,
|
||||||
|
supported_types,
|
||||||
|
0
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut right_types = self.potential_types(known.schema, &right)?
|
let mut right_types = self
|
||||||
.intersection(&supported_types);
|
.potential_types(known.schema, &right)?
|
||||||
|
.intersection(supported_types);
|
||||||
if right_types.is_empty() {
|
if right_types.is_empty() {
|
||||||
bail!(AlgebrizerError::InvalidArgumentType(predicate.operator.clone(), supported_types, 1));
|
bail!(AlgebrizerError::InvalidArgumentType(
|
||||||
|
predicate.operator,
|
||||||
|
supported_types,
|
||||||
|
1
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// We would like to allow longs to compare to doubles.
|
// We would like to allow longs to compare to doubles.
|
||||||
|
@ -116,7 +125,7 @@ impl ConjoiningClauses {
|
||||||
left_types.insert(ValueType::Double);
|
left_types.insert(ValueType::Double);
|
||||||
}
|
}
|
||||||
|
|
||||||
let shared_types = left_types.intersection(&right_types);
|
let shared_types = left_types.intersection(right_types);
|
||||||
if shared_types.is_empty() {
|
if shared_types.is_empty() {
|
||||||
// In isolation these are both valid inputs to the operator, but the query cannot
|
// In isolation these are both valid inputs to the operator, but the query cannot
|
||||||
// succeed because the types don't match.
|
// succeed because the types don't match.
|
||||||
|
@ -132,7 +141,8 @@ impl ConjoiningClauses {
|
||||||
left: left_types,
|
left: left_types,
|
||||||
right: right_types,
|
right: right_types,
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +160,11 @@ impl ConjoiningClauses {
|
||||||
left_v = self.resolve_ref_argument(known.schema, &predicate.operator, 0, left)?;
|
left_v = self.resolve_ref_argument(known.schema, &predicate.operator, 0, left)?;
|
||||||
right_v = self.resolve_ref_argument(known.schema, &predicate.operator, 1, right)?;
|
right_v = self.resolve_ref_argument(known.schema, &predicate.operator, 1, right)?;
|
||||||
} else {
|
} else {
|
||||||
bail!(AlgebrizerError::InvalidArgumentType(predicate.operator.clone(), supported_types, 0));
|
bail!(AlgebrizerError::InvalidArgumentType(
|
||||||
|
predicate.operator,
|
||||||
|
supported_types,
|
||||||
|
0
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// These arguments must be variables or instant/numeric constants.
|
// These arguments must be variables or instant/numeric constants.
|
||||||
|
@ -162,23 +176,21 @@ impl ConjoiningClauses {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Inequality {
|
impl Inequality {
|
||||||
fn to_constraint(&self, left: QueryValue, right: QueryValue) -> ColumnConstraint {
|
fn to_constraint(self, left: QueryValue, right: QueryValue) -> ColumnConstraint {
|
||||||
match *self {
|
match self {
|
||||||
Inequality::TxAfter |
|
Inequality::TxAfter | Inequality::TxBefore => {
|
||||||
Inequality::TxBefore => {
|
|
||||||
// TODO: both ends of the range must be inside the tx partition!
|
// TODO: both ends of the range must be inside the tx partition!
|
||||||
// If we know the partition map -- and at this point we do, it's just
|
// If we know the partition map -- and at this point we do, it's just
|
||||||
// not passed to this function -- then we can generate two constraints,
|
// not passed to this function -- then we can generate two constraints,
|
||||||
// or clamp a fixed value.
|
// or clamp a fixed value.
|
||||||
},
|
}
|
||||||
_ => {
|
_ => {}
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnConstraint::Inequality {
|
ColumnConstraint::Inequality {
|
||||||
operator: *self,
|
operator: self,
|
||||||
left: left,
|
left,
|
||||||
right: right,
|
right,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -187,34 +199,16 @@ impl Inequality {
|
||||||
mod testing {
|
mod testing {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use mentat_core::attribute::Unique;
|
use core_traits::attribute::Unique;
|
||||||
use mentat_core::{
|
use core_traits::{Attribute, TypedValue, ValueType};
|
||||||
Attribute,
|
|
||||||
TypedValue,
|
use edn::query::{
|
||||||
ValueType,
|
FnArg, Keyword, Pattern, PatternNonValuePlace, PatternValuePlace, PlainSymbol, Variable,
|
||||||
};
|
};
|
||||||
|
|
||||||
use mentat_query::{
|
use crate::clauses::{add_attribute, associate_ident, ident};
|
||||||
FnArg,
|
|
||||||
Keyword,
|
|
||||||
Pattern,
|
|
||||||
PatternNonValuePlace,
|
|
||||||
PatternValuePlace,
|
|
||||||
PlainSymbol,
|
|
||||||
Variable,
|
|
||||||
};
|
|
||||||
|
|
||||||
use clauses::{
|
use crate::types::{ColumnConstraint, EmptyBecause, QueryValue};
|
||||||
add_attribute,
|
|
||||||
associate_ident,
|
|
||||||
ident,
|
|
||||||
};
|
|
||||||
|
|
||||||
use types::{
|
|
||||||
ColumnConstraint,
|
|
||||||
EmptyBecause,
|
|
||||||
QueryValue,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
/// Apply two patterns: a pattern and a numeric predicate.
|
/// Apply two patterns: a pattern and a numeric predicate.
|
||||||
|
@ -225,30 +219,45 @@ mod testing {
|
||||||
let mut schema = Schema::default();
|
let mut schema = Schema::default();
|
||||||
|
|
||||||
associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 99);
|
associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 99);
|
||||||
add_attribute(&mut schema, 99, Attribute {
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
|
99,
|
||||||
|
Attribute {
|
||||||
value_type: ValueType::Long,
|
value_type: ValueType::Long,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let x = Variable::from_valid_name("?x");
|
let x = Variable::from_valid_name("?x");
|
||||||
let y = Variable::from_valid_name("?y");
|
let y = Variable::from_valid_name("?y");
|
||||||
let known = Known::for_schema(&schema);
|
let known = Known::for_schema(&schema);
|
||||||
cc.apply_parsed_pattern(known, Pattern {
|
cc.apply_parsed_pattern(
|
||||||
|
known,
|
||||||
|
Pattern {
|
||||||
source: None,
|
source: None,
|
||||||
entity: PatternNonValuePlace::Variable(x.clone()),
|
entity: PatternNonValuePlace::Variable(x),
|
||||||
attribute: PatternNonValuePlace::Placeholder,
|
attribute: PatternNonValuePlace::Placeholder,
|
||||||
value: PatternValuePlace::Variable(y.clone()),
|
value: PatternValuePlace::Variable(y.clone()),
|
||||||
tx: PatternNonValuePlace::Placeholder,
|
tx: PatternNonValuePlace::Placeholder,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
assert!(!cc.is_known_empty());
|
assert!(!cc.is_known_empty());
|
||||||
|
|
||||||
let op = PlainSymbol::plain("<");
|
let op = PlainSymbol::plain("<");
|
||||||
let comp = Inequality::from_datalog_operator(op.name()).unwrap();
|
let comp = Inequality::from_datalog_operator(op.name()).unwrap();
|
||||||
assert!(cc.apply_inequality(known, comp, Predicate {
|
assert!(cc
|
||||||
|
.apply_inequality(
|
||||||
|
known,
|
||||||
|
comp,
|
||||||
|
Predicate {
|
||||||
operator: op,
|
operator: op,
|
||||||
args: vec![
|
args: vec![
|
||||||
FnArg::Variable(Variable::from_valid_name("?y")), FnArg::EntidOrInteger(10),
|
FnArg::Variable(Variable::from_valid_name("?y")),
|
||||||
]}).is_ok());
|
FnArg::EntidOrInteger(10),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
assert!(!cc.is_known_empty());
|
assert!(!cc.is_known_empty());
|
||||||
|
|
||||||
|
@ -264,11 +273,15 @@ mod testing {
|
||||||
|
|
||||||
let clauses = cc.wheres;
|
let clauses = cc.wheres;
|
||||||
assert_eq!(clauses.len(), 1);
|
assert_eq!(clauses.len(), 1);
|
||||||
assert_eq!(clauses.0[0], ColumnConstraint::Inequality {
|
assert_eq!(
|
||||||
|
clauses.0[0],
|
||||||
|
ColumnConstraint::Inequality {
|
||||||
operator: Inequality::LessThan,
|
operator: Inequality::LessThan,
|
||||||
left: QueryValue::Column(cc.column_bindings.get(&y).unwrap()[0].clone()),
|
left: QueryValue::Column(cc.column_bindings.get(&y).unwrap()[0].clone()),
|
||||||
right: QueryValue::TypedValue(TypedValue::Long(10)),
|
right: QueryValue::TypedValue(TypedValue::Long(10)),
|
||||||
}.into());
|
}
|
||||||
|
.into()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -281,54 +294,78 @@ mod testing {
|
||||||
|
|
||||||
associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 99);
|
associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 99);
|
||||||
associate_ident(&mut schema, Keyword::namespaced("foo", "roz"), 98);
|
associate_ident(&mut schema, Keyword::namespaced("foo", "roz"), 98);
|
||||||
add_attribute(&mut schema, 99, Attribute {
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
|
99,
|
||||||
|
Attribute {
|
||||||
value_type: ValueType::Long,
|
value_type: ValueType::Long,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
},
|
||||||
add_attribute(&mut schema, 98, Attribute {
|
);
|
||||||
|
add_attribute(
|
||||||
|
&mut schema,
|
||||||
|
98,
|
||||||
|
Attribute {
|
||||||
value_type: ValueType::String,
|
value_type: ValueType::String,
|
||||||
unique: Some(Unique::Identity),
|
unique: Some(Unique::Identity),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let x = Variable::from_valid_name("?x");
|
let x = Variable::from_valid_name("?x");
|
||||||
let y = Variable::from_valid_name("?y");
|
let y = Variable::from_valid_name("?y");
|
||||||
let known = Known::for_schema(&schema);
|
let known = Known::for_schema(&schema);
|
||||||
cc.apply_parsed_pattern(known, Pattern {
|
cc.apply_parsed_pattern(
|
||||||
|
known,
|
||||||
|
Pattern {
|
||||||
source: None,
|
source: None,
|
||||||
entity: PatternNonValuePlace::Variable(x.clone()),
|
entity: PatternNonValuePlace::Variable(x.clone()),
|
||||||
attribute: PatternNonValuePlace::Placeholder,
|
attribute: PatternNonValuePlace::Placeholder,
|
||||||
value: PatternValuePlace::Variable(y.clone()),
|
value: PatternValuePlace::Variable(y.clone()),
|
||||||
tx: PatternNonValuePlace::Placeholder,
|
tx: PatternNonValuePlace::Placeholder,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
assert!(!cc.is_known_empty());
|
assert!(!cc.is_known_empty());
|
||||||
|
|
||||||
let op = PlainSymbol::plain(">=");
|
let op = PlainSymbol::plain(">=");
|
||||||
let comp = Inequality::from_datalog_operator(op.name()).unwrap();
|
let comp = Inequality::from_datalog_operator(op.name()).unwrap();
|
||||||
assert!(cc.apply_inequality(known, comp, Predicate {
|
assert!(cc
|
||||||
|
.apply_inequality(
|
||||||
|
known,
|
||||||
|
comp,
|
||||||
|
Predicate {
|
||||||
operator: op,
|
operator: op,
|
||||||
args: vec![
|
args: vec![
|
||||||
FnArg::Variable(Variable::from_valid_name("?y")), FnArg::EntidOrInteger(10),
|
FnArg::Variable(Variable::from_valid_name("?y")),
|
||||||
]}).is_ok());
|
FnArg::EntidOrInteger(10),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
assert!(!cc.is_known_empty());
|
assert!(!cc.is_known_empty());
|
||||||
cc.apply_parsed_pattern(known, Pattern {
|
cc.apply_parsed_pattern(
|
||||||
|
known,
|
||||||
|
Pattern {
|
||||||
source: None,
|
source: None,
|
||||||
entity: PatternNonValuePlace::Variable(x.clone()),
|
entity: PatternNonValuePlace::Variable(x),
|
||||||
attribute: ident("foo", "roz"),
|
attribute: ident("foo", "roz"),
|
||||||
value: PatternValuePlace::Variable(y.clone()),
|
value: PatternValuePlace::Variable(y.clone()),
|
||||||
tx: PatternNonValuePlace::Placeholder,
|
tx: PatternNonValuePlace::Placeholder,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Finally, expand column bindings to get the overlaps for ?x.
|
// Finally, expand column bindings to get the overlaps for ?x.
|
||||||
cc.expand_column_bindings();
|
cc.expand_column_bindings();
|
||||||
|
|
||||||
assert!(cc.is_known_empty());
|
assert!(cc.is_known_empty());
|
||||||
assert_eq!(cc.empty_because.unwrap(),
|
assert_eq!(
|
||||||
|
cc.empty_because.unwrap(),
|
||||||
EmptyBecause::TypeMismatch {
|
EmptyBecause::TypeMismatch {
|
||||||
var: y.clone(),
|
var: y,
|
||||||
existing: ValueTypeSet::of_numeric_types(),
|
existing: ValueTypeSet::of_numeric_types(),
|
||||||
desired: ValueTypeSet::of_one(ValueType::String),
|
desired: ValueTypeSet::of_one(ValueType::String),
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,30 +8,17 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
use mentat_core::{
|
use core_traits::{TypedValue, ValueType};
|
||||||
HasSchema,
|
|
||||||
Schema,
|
|
||||||
TypedValue,
|
|
||||||
ValueType,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mentat_query::{
|
use mentat_core::{HasSchema, Schema};
|
||||||
FnArg,
|
|
||||||
NonIntegerConstant,
|
|
||||||
PlainSymbol,
|
|
||||||
};
|
|
||||||
|
|
||||||
use clauses::ConjoiningClauses;
|
use edn::query::{FnArg, NonIntegerConstant, PlainSymbol};
|
||||||
|
|
||||||
use errors::{
|
use crate::clauses::ConjoiningClauses;
|
||||||
AlgebrizerError,
|
|
||||||
Result,
|
|
||||||
};
|
|
||||||
|
|
||||||
use types::{
|
use query_algebrizer_traits::errors::{AlgebrizerError, Result};
|
||||||
EmptyBecause,
|
|
||||||
QueryValue,
|
use crate::types::{EmptyBecause, QueryValue};
|
||||||
};
|
|
||||||
|
|
||||||
/// Argument resolution.
|
/// Argument resolution.
|
||||||
impl ConjoiningClauses {
|
impl ConjoiningClauses {
|
||||||
|
@ -40,7 +27,12 @@ impl ConjoiningClauses {
|
||||||
/// Additionally, do two things:
|
/// Additionally, do two things:
|
||||||
/// - Mark the pattern as known-empty if any argument is known non-numeric.
|
/// - Mark the pattern as known-empty if any argument is known non-numeric.
|
||||||
/// - Mark any variables encountered as numeric.
|
/// - Mark any variables encountered as numeric.
|
||||||
pub(crate) fn resolve_numeric_argument(&mut self, function: &PlainSymbol, position: usize, arg: FnArg) -> Result<QueryValue> {
|
pub(crate) fn resolve_numeric_argument(
|
||||||
|
&mut self,
|
||||||
|
function: &PlainSymbol,
|
||||||
|
position: usize,
|
||||||
|
arg: FnArg,
|
||||||
|
) -> Result<QueryValue> {
|
||||||
use self::FnArg::*;
|
use self::FnArg::*;
|
||||||
match arg {
|
match arg {
|
||||||
FnArg::Variable(var) => {
|
FnArg::Variable(var) => {
|
||||||
|
@ -49,14 +41,14 @@ impl ConjoiningClauses {
|
||||||
if v.value_type().is_numeric() {
|
if v.value_type().is_numeric() {
|
||||||
Ok(QueryValue::TypedValue(v))
|
Ok(QueryValue::TypedValue(v))
|
||||||
} else {
|
} else {
|
||||||
bail!(AlgebrizerError::InputTypeDisagreement(var.name().clone(), ValueType::Long, v.value_type()))
|
bail!(AlgebrizerError::InputTypeDisagreement(var.name(), ValueType::Long, v.value_type()))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.constrain_var_to_numeric(var.clone());
|
self.constrain_var_to_numeric(var.clone());
|
||||||
self.column_bindings
|
self.column_bindings
|
||||||
.get(&var)
|
.get(&var)
|
||||||
.and_then(|cols| cols.first().map(|col| QueryValue::Column(col.clone())))
|
.and_then(|cols| cols.first().map(|col| QueryValue::Column(col.clone())))
|
||||||
.ok_or_else(|| AlgebrizerError::UnboundVariable(var.name()).into())
|
.ok_or_else(|| AlgebrizerError::UnboundVariable(var.name()))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Can't be an entid.
|
// Can't be an entid.
|
||||||
|
@ -77,45 +69,62 @@ impl ConjoiningClauses {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Just like `resolve_numeric_argument`, but for `ValueType::Instant`.
|
/// Just like `resolve_numeric_argument`, but for `ValueType::Instant`.
|
||||||
pub(crate) fn resolve_instant_argument(&mut self, function: &PlainSymbol, position: usize, arg: FnArg) -> Result<QueryValue> {
|
pub(crate) fn resolve_instant_argument(
|
||||||
|
&mut self,
|
||||||
|
function: &PlainSymbol,
|
||||||
|
position: usize,
|
||||||
|
arg: FnArg,
|
||||||
|
) -> Result<QueryValue> {
|
||||||
use self::FnArg::*;
|
use self::FnArg::*;
|
||||||
match arg {
|
match arg {
|
||||||
FnArg::Variable(var) => {
|
FnArg::Variable(var) => match self.bound_value(&var) {
|
||||||
match self.bound_value(&var) {
|
|
||||||
Some(TypedValue::Instant(v)) => Ok(QueryValue::TypedValue(TypedValue::Instant(v))),
|
Some(TypedValue::Instant(v)) => Ok(QueryValue::TypedValue(TypedValue::Instant(v))),
|
||||||
Some(v) => bail!(AlgebrizerError::InputTypeDisagreement(var.name().clone(), ValueType::Instant, v.value_type())),
|
Some(v) => bail!(AlgebrizerError::InputTypeDisagreement(
|
||||||
|
var.name(),
|
||||||
|
ValueType::Instant,
|
||||||
|
v.value_type()
|
||||||
|
)),
|
||||||
None => {
|
None => {
|
||||||
self.constrain_var_to_type(var.clone(), ValueType::Instant);
|
self.constrain_var_to_type(var.clone(), ValueType::Instant);
|
||||||
self.column_bindings
|
self.column_bindings
|
||||||
.get(&var)
|
.get(&var)
|
||||||
.and_then(|cols| cols.first().map(|col| QueryValue::Column(col.clone())))
|
.and_then(|cols| cols.first().map(|col| QueryValue::Column(col.clone())))
|
||||||
.ok_or_else(|| AlgebrizerError::UnboundVariable(var.name()).into())
|
.ok_or_else(|| AlgebrizerError::UnboundVariable(var.name()))
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Constant(NonIntegerConstant::Instant(v)) => {
|
Constant(NonIntegerConstant::Instant(v)) => {
|
||||||
Ok(QueryValue::TypedValue(TypedValue::Instant(v)))
|
Ok(QueryValue::TypedValue(TypedValue::Instant(v)))
|
||||||
},
|
}
|
||||||
|
|
||||||
// TODO: should we allow integers if they seem to be timestamps? It's ambiguous…
|
// TODO: should we allow integers if they seem to be timestamps? It's ambiguous…
|
||||||
EntidOrInteger(_) |
|
EntidOrInteger(_)
|
||||||
IdentOrKeyword(_) |
|
| IdentOrKeyword(_)
|
||||||
SrcVar(_) |
|
| SrcVar(_)
|
||||||
Constant(NonIntegerConstant::Boolean(_)) |
|
| Constant(NonIntegerConstant::Boolean(_))
|
||||||
Constant(NonIntegerConstant::Float(_)) |
|
| Constant(NonIntegerConstant::Float(_))
|
||||||
Constant(NonIntegerConstant::Text(_)) |
|
| Constant(NonIntegerConstant::Text(_))
|
||||||
Constant(NonIntegerConstant::Uuid(_)) |
|
| Constant(NonIntegerConstant::Uuid(_))
|
||||||
Constant(NonIntegerConstant::BigInteger(_)) |
|
| Constant(NonIntegerConstant::BigInteger(_))
|
||||||
Vector(_) => {
|
| Vector(_) => {
|
||||||
self.mark_known_empty(EmptyBecause::NonInstantArgument);
|
self.mark_known_empty(EmptyBecause::NonInstantArgument);
|
||||||
bail!(AlgebrizerError::InvalidArgumentType(function.clone(), ValueType::Instant.into(), position))
|
bail!(AlgebrizerError::InvalidArgumentType(
|
||||||
},
|
function.clone(),
|
||||||
|
ValueType::Instant.into(),
|
||||||
|
position
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Take a function argument and turn it into a `QueryValue` suitable for use in a concrete
|
/// Take a function argument and turn it into a `QueryValue` suitable for use in a concrete
|
||||||
/// constraint.
|
/// constraint.
|
||||||
pub(crate) fn resolve_ref_argument(&mut self, schema: &Schema, function: &PlainSymbol, position: usize, arg: FnArg) -> Result<QueryValue> {
|
pub(crate) fn resolve_ref_argument(
|
||||||
|
&mut self,
|
||||||
|
schema: &Schema,
|
||||||
|
function: &PlainSymbol,
|
||||||
|
position: usize,
|
||||||
|
arg: FnArg,
|
||||||
|
) -> Result<QueryValue> {
|
||||||
use self::FnArg::*;
|
use self::FnArg::*;
|
||||||
match arg {
|
match arg {
|
||||||
FnArg::Variable(var) => {
|
FnArg::Variable(var) => {
|
||||||
|
@ -127,33 +136,41 @@ impl ConjoiningClauses {
|
||||||
self.column_bindings
|
self.column_bindings
|
||||||
.get(&var)
|
.get(&var)
|
||||||
.and_then(|cols| cols.first().map(|col| QueryValue::Column(col.clone())))
|
.and_then(|cols| cols.first().map(|col| QueryValue::Column(col.clone())))
|
||||||
.ok_or_else(|| AlgebrizerError::UnboundVariable(var.name()).into())
|
.ok_or_else(|| AlgebrizerError::UnboundVariable(var.name()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
EntidOrInteger(i) => Ok(QueryValue::TypedValue(TypedValue::Ref(i))),
|
EntidOrInteger(i) => Ok(QueryValue::TypedValue(TypedValue::Ref(i))),
|
||||||
IdentOrKeyword(i) => {
|
IdentOrKeyword(i) => schema
|
||||||
schema.get_entid(&i)
|
.get_entid(&i)
|
||||||
.map(|known_entid| QueryValue::Entid(known_entid.into()))
|
.map(|known_entid| QueryValue::Entid(known_entid.into()))
|
||||||
.ok_or_else(|| AlgebrizerError::UnrecognizedIdent(i.to_string()).into())
|
.ok_or_else(|| AlgebrizerError::UnrecognizedIdent(i.to_string())),
|
||||||
},
|
Constant(NonIntegerConstant::Boolean(_))
|
||||||
Constant(NonIntegerConstant::Boolean(_)) |
|
| Constant(NonIntegerConstant::Float(_))
|
||||||
Constant(NonIntegerConstant::Float(_)) |
|
| Constant(NonIntegerConstant::Text(_))
|
||||||
Constant(NonIntegerConstant::Text(_)) |
|
| Constant(NonIntegerConstant::Uuid(_))
|
||||||
Constant(NonIntegerConstant::Uuid(_)) |
|
| Constant(NonIntegerConstant::Instant(_))
|
||||||
Constant(NonIntegerConstant::Instant(_)) |
|
| Constant(NonIntegerConstant::BigInteger(_))
|
||||||
Constant(NonIntegerConstant::BigInteger(_)) |
|
| SrcVar(_)
|
||||||
SrcVar(_) |
|
| Vector(_) => {
|
||||||
Vector(_) => {
|
|
||||||
self.mark_known_empty(EmptyBecause::NonEntityArgument);
|
self.mark_known_empty(EmptyBecause::NonEntityArgument);
|
||||||
bail!(AlgebrizerError::InvalidArgumentType(function.clone(), ValueType::Ref.into(), position))
|
bail!(AlgebrizerError::InvalidArgumentType(
|
||||||
},
|
function.clone(),
|
||||||
|
ValueType::Ref.into(),
|
||||||
|
position
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Take a transaction ID function argument and turn it into a `QueryValue` suitable for use in
|
/// Take a transaction ID function argument and turn it into a `QueryValue` suitable for use in
|
||||||
/// a concrete constraint.
|
/// a concrete constraint.
|
||||||
pub(crate) fn resolve_tx_argument(&mut self, schema: &Schema, function: &PlainSymbol, position: usize, arg: FnArg) -> Result<QueryValue> {
|
pub(crate) fn resolve_tx_argument(
|
||||||
|
&mut self,
|
||||||
|
schema: &Schema,
|
||||||
|
function: &PlainSymbol,
|
||||||
|
position: usize,
|
||||||
|
arg: FnArg,
|
||||||
|
) -> Result<QueryValue> {
|
||||||
// Under the hood there's nothing special about a transaction ID -- it's just another ref.
|
// Under the hood there's nothing special about a transaction ID -- it's just another ref.
|
||||||
// In the future, we might handle instants specially.
|
// In the future, we might handle instants specially.
|
||||||
self.resolve_ref_argument(schema, function, position, arg)
|
self.resolve_ref_argument(schema, function, position, arg)
|
||||||
|
@ -165,24 +182,31 @@ impl ConjoiningClauses {
|
||||||
fn resolve_argument(&self, arg: FnArg) -> Result<QueryValue> {
|
fn resolve_argument(&self, arg: FnArg) -> Result<QueryValue> {
|
||||||
use self::FnArg::*;
|
use self::FnArg::*;
|
||||||
match arg {
|
match arg {
|
||||||
FnArg::Variable(var) => {
|
FnArg::Variable(var) => match self.bound_value(&var) {
|
||||||
match self.bound_value(&var) {
|
|
||||||
Some(v) => Ok(QueryValue::TypedValue(v)),
|
Some(v) => Ok(QueryValue::TypedValue(v)),
|
||||||
None => {
|
None => self
|
||||||
self.column_bindings
|
.column_bindings
|
||||||
.get(&var)
|
.get(&var)
|
||||||
.and_then(|cols| cols.first().map(|col| QueryValue::Column(col.clone())))
|
.and_then(|cols| cols.first().map(|col| QueryValue::Column(col.clone())))
|
||||||
.ok_or_else(|| AlgebrizerError::UnboundVariable(var.name()).into())
|
.ok_or_else(|| AlgebrizerError::UnboundVariable(var.name())),
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
EntidOrInteger(i) => Ok(QueryValue::PrimitiveLong(i)),
|
EntidOrInteger(i) => Ok(QueryValue::PrimitiveLong(i)),
|
||||||
IdentOrKeyword(_) => unimplemented!(), // TODO
|
IdentOrKeyword(_) => unimplemented!(), // TODO
|
||||||
Constant(NonIntegerConstant::Boolean(val)) => Ok(QueryValue::TypedValue(TypedValue::Boolean(val))),
|
Constant(NonIntegerConstant::Boolean(val)) => {
|
||||||
Constant(NonIntegerConstant::Float(f)) => Ok(QueryValue::TypedValue(TypedValue::Double(f))),
|
Ok(QueryValue::TypedValue(TypedValue::Boolean(val)))
|
||||||
Constant(NonIntegerConstant::Text(s)) => Ok(QueryValue::TypedValue(TypedValue::typed_string(s.as_str()))),
|
}
|
||||||
Constant(NonIntegerConstant::Uuid(u)) => Ok(QueryValue::TypedValue(TypedValue::Uuid(u))),
|
Constant(NonIntegerConstant::Float(f)) => {
|
||||||
Constant(NonIntegerConstant::Instant(u)) => Ok(QueryValue::TypedValue(TypedValue::Instant(u))),
|
Ok(QueryValue::TypedValue(TypedValue::Double(f)))
|
||||||
|
}
|
||||||
|
Constant(NonIntegerConstant::Text(s)) => {
|
||||||
|
Ok(QueryValue::TypedValue(TypedValue::typed_string(s.as_str())))
|
||||||
|
}
|
||||||
|
Constant(NonIntegerConstant::Uuid(u)) => {
|
||||||
|
Ok(QueryValue::TypedValue(TypedValue::Uuid(u)))
|
||||||
|
}
|
||||||
|
Constant(NonIntegerConstant::Instant(u)) => {
|
||||||
|
Ok(QueryValue::TypedValue(TypedValue::Instant(u)))
|
||||||
|
}
|
||||||
Constant(NonIntegerConstant::BigInteger(_)) => unimplemented!(),
|
Constant(NonIntegerConstant::BigInteger(_)) => unimplemented!(),
|
||||||
SrcVar(_) => unimplemented!(),
|
SrcVar(_) => unimplemented!(),
|
||||||
Vector(_) => unimplemented!(), // TODO
|
Vector(_) => unimplemented!(), // TODO
|
||||||
|
|
|
@ -8,40 +8,20 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
use mentat_core::{
|
use core_traits::ValueType;
|
||||||
ValueType,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mentat_query::{
|
use edn::query::{Binding, FnArg, SrcVar, VariableOrPlaceholder, WhereFn};
|
||||||
Binding,
|
|
||||||
FnArg,
|
|
||||||
SrcVar,
|
|
||||||
VariableOrPlaceholder,
|
|
||||||
WhereFn,
|
|
||||||
};
|
|
||||||
|
|
||||||
use clauses::{
|
use crate::clauses::ConjoiningClauses;
|
||||||
ConjoiningClauses,
|
|
||||||
};
|
|
||||||
|
|
||||||
use errors::{
|
use query_algebrizer_traits::errors::{AlgebrizerError, BindingError, Result};
|
||||||
AlgebrizerError,
|
|
||||||
BindingError,
|
|
||||||
Result,
|
|
||||||
};
|
|
||||||
|
|
||||||
use types::{
|
use crate::types::{
|
||||||
Column,
|
Column, ColumnConstraint, DatomsTable, Inequality, QualifiedAlias, QueryValue, SourceAlias,
|
||||||
ColumnConstraint,
|
|
||||||
DatomsTable,
|
|
||||||
Inequality,
|
|
||||||
QualifiedAlias,
|
|
||||||
QueryValue,
|
|
||||||
SourceAlias,
|
|
||||||
TransactionsColumn,
|
TransactionsColumn,
|
||||||
};
|
};
|
||||||
|
|
||||||
use Known;
|
use crate::Known;
|
||||||
|
|
||||||
impl ConjoiningClauses {
|
impl ConjoiningClauses {
|
||||||
// Log in Query: tx-ids and tx-data
|
// Log in Query: tx-ids and tx-data
|
||||||
|
@ -60,17 +40,27 @@ impl ConjoiningClauses {
|
||||||
// transactions that impact one of the given attributes.
|
// transactions that impact one of the given attributes.
|
||||||
pub(crate) fn apply_tx_ids(&mut self, known: Known, where_fn: WhereFn) -> Result<()> {
|
pub(crate) fn apply_tx_ids(&mut self, known: Known, where_fn: WhereFn) -> Result<()> {
|
||||||
if where_fn.args.len() != 3 {
|
if where_fn.args.len() != 3 {
|
||||||
bail!(AlgebrizerError::InvalidNumberOfArguments(where_fn.operator.clone(), where_fn.args.len(), 3));
|
bail!(AlgebrizerError::InvalidNumberOfArguments(
|
||||||
|
where_fn.operator.clone(),
|
||||||
|
where_fn.args.len(),
|
||||||
|
3
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if where_fn.binding.is_empty() {
|
if where_fn.binding.is_empty() {
|
||||||
// The binding must introduce at least one bound variable.
|
// The binding must introduce at least one bound variable.
|
||||||
bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::NoBoundVariable));
|
bail!(AlgebrizerError::InvalidBinding(
|
||||||
|
where_fn.operator.clone(),
|
||||||
|
BindingError::NoBoundVariable
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !where_fn.binding.is_valid() {
|
if !where_fn.binding.is_valid() {
|
||||||
// The binding must not duplicate bound variables.
|
// The binding must not duplicate bound variables.
|
||||||
bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::RepeatedBoundVariable));
|
bail!(AlgebrizerError::InvalidBinding(
|
||||||
|
where_fn.operator.clone(),
|
||||||
|
BindingError::RepeatedBoundVariable
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// We should have exactly one binding. Destructure it now.
|
// We should have exactly one binding. Destructure it now.
|
||||||
|
@ -78,38 +68,49 @@ impl ConjoiningClauses {
|
||||||
Binding::BindRel(bindings) => {
|
Binding::BindRel(bindings) => {
|
||||||
let bindings_count = bindings.len();
|
let bindings_count = bindings.len();
|
||||||
if bindings_count != 1 {
|
if bindings_count != 1 {
|
||||||
bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(),
|
bail!(AlgebrizerError::InvalidBinding(
|
||||||
|
where_fn.operator.clone(),
|
||||||
BindingError::InvalidNumberOfBindings {
|
BindingError::InvalidNumberOfBindings {
|
||||||
number: bindings_count,
|
number: bindings_count,
|
||||||
expected: 1,
|
expected: 1,
|
||||||
}));
|
}
|
||||||
|
));
|
||||||
}
|
}
|
||||||
match bindings.into_iter().next().unwrap() {
|
match bindings.into_iter().next().unwrap() {
|
||||||
VariableOrPlaceholder::Placeholder => unreachable!("binding.is_empty()!"),
|
VariableOrPlaceholder::Placeholder => unreachable!("binding.is_empty()!"),
|
||||||
VariableOrPlaceholder::Variable(v) => v,
|
VariableOrPlaceholder::Variable(v) => v,
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Binding::BindColl(v) => v,
|
Binding::BindColl(v) => v,
|
||||||
Binding::BindScalar(_) |
|
Binding::BindScalar(_) | Binding::BindTuple(_) => {
|
||||||
Binding::BindTuple(_) => {
|
bail!(AlgebrizerError::InvalidBinding(
|
||||||
bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::ExpectedBindRelOrBindColl))
|
where_fn.operator.clone(),
|
||||||
},
|
BindingError::ExpectedBindRelOrBindColl
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut args = where_fn.args.into_iter();
|
let mut args = where_fn.args.into_iter();
|
||||||
|
|
||||||
// TODO: process source variables.
|
// TODO: process source variables.
|
||||||
match args.next().unwrap() {
|
match args.next().unwrap() {
|
||||||
FnArg::SrcVar(SrcVar::DefaultSrc) => {},
|
FnArg::SrcVar(SrcVar::DefaultSrc) => {}
|
||||||
_ => bail!(AlgebrizerError::InvalidArgument(where_fn.operator.clone(), "source variable", 0)),
|
_ => bail!(AlgebrizerError::InvalidArgument(
|
||||||
|
where_fn.operator.clone(),
|
||||||
|
"source variable",
|
||||||
|
0
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
|
|
||||||
let tx1 = self.resolve_tx_argument(&known.schema, &where_fn.operator, 1, args.next().unwrap())?;
|
let tx1 =
|
||||||
let tx2 = self.resolve_tx_argument(&known.schema, &where_fn.operator, 2, args.next().unwrap())?;
|
self.resolve_tx_argument(&known.schema, &where_fn.operator, 1, args.next().unwrap())?;
|
||||||
|
let tx2 =
|
||||||
|
self.resolve_tx_argument(&known.schema, &where_fn.operator, 2, args.next().unwrap())?;
|
||||||
|
|
||||||
let transactions = self.next_alias_for_table(DatomsTable::Transactions);
|
let transactions = self.next_alias_for_table(DatomsTable::Transactions);
|
||||||
|
|
||||||
self.from.push(SourceAlias(DatomsTable::Transactions, transactions.clone()));
|
self.from
|
||||||
|
.push(SourceAlias(DatomsTable::Transactions, transactions.clone()));
|
||||||
|
|
||||||
// Bound variable must be a ref.
|
// Bound variable must be a ref.
|
||||||
self.constrain_var_to_type(tx_var.clone(), ValueType::Ref);
|
self.constrain_var_to_type(tx_var.clone(), ValueType::Ref);
|
||||||
|
@ -117,18 +118,29 @@ impl ConjoiningClauses {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.bind_column_to_var(known.schema, transactions.clone(), TransactionsColumn::Tx, tx_var.clone());
|
self.bind_column_to_var(
|
||||||
|
known.schema,
|
||||||
|
transactions.clone(),
|
||||||
|
TransactionsColumn::Tx,
|
||||||
|
tx_var,
|
||||||
|
);
|
||||||
|
|
||||||
let after_constraint = ColumnConstraint::Inequality {
|
let after_constraint = ColumnConstraint::Inequality {
|
||||||
operator: Inequality::LessThanOrEquals,
|
operator: Inequality::LessThanOrEquals,
|
||||||
left: tx1,
|
left: tx1,
|
||||||
right: QueryValue::Column(QualifiedAlias(transactions.clone(), Column::Transactions(TransactionsColumn::Tx))),
|
right: QueryValue::Column(QualifiedAlias(
|
||||||
|
transactions.clone(),
|
||||||
|
Column::Transactions(TransactionsColumn::Tx),
|
||||||
|
)),
|
||||||
};
|
};
|
||||||
self.wheres.add_intersection(after_constraint);
|
self.wheres.add_intersection(after_constraint);
|
||||||
|
|
||||||
let before_constraint = ColumnConstraint::Inequality {
|
let before_constraint = ColumnConstraint::Inequality {
|
||||||
operator: Inequality::LessThan,
|
operator: Inequality::LessThan,
|
||||||
left: QueryValue::Column(QualifiedAlias(transactions.clone(), Column::Transactions(TransactionsColumn::Tx))),
|
left: QueryValue::Column(QualifiedAlias(
|
||||||
|
transactions,
|
||||||
|
Column::Transactions(TransactionsColumn::Tx),
|
||||||
|
)),
|
||||||
right: tx2,
|
right: tx2,
|
||||||
};
|
};
|
||||||
self.wheres.add_intersection(before_constraint);
|
self.wheres.add_intersection(before_constraint);
|
||||||
|
@ -138,17 +150,27 @@ impl ConjoiningClauses {
|
||||||
|
|
||||||
pub(crate) fn apply_tx_data(&mut self, known: Known, where_fn: WhereFn) -> Result<()> {
|
pub(crate) fn apply_tx_data(&mut self, known: Known, where_fn: WhereFn) -> Result<()> {
|
||||||
if where_fn.args.len() != 2 {
|
if where_fn.args.len() != 2 {
|
||||||
bail!(AlgebrizerError::InvalidNumberOfArguments(where_fn.operator.clone(), where_fn.args.len(), 2));
|
bail!(AlgebrizerError::InvalidNumberOfArguments(
|
||||||
|
where_fn.operator.clone(),
|
||||||
|
where_fn.args.len(),
|
||||||
|
2
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if where_fn.binding.is_empty() {
|
if where_fn.binding.is_empty() {
|
||||||
// The binding must introduce at least one bound variable.
|
// The binding must introduce at least one bound variable.
|
||||||
bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::NoBoundVariable));
|
bail!(AlgebrizerError::InvalidBinding(
|
||||||
|
where_fn.operator.clone(),
|
||||||
|
BindingError::NoBoundVariable
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !where_fn.binding.is_valid() {
|
if !where_fn.binding.is_valid() {
|
||||||
// The binding must not duplicate bound variables.
|
// The binding must not duplicate bound variables.
|
||||||
bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::RepeatedBoundVariable));
|
bail!(AlgebrizerError::InvalidBinding(
|
||||||
|
where_fn.operator.clone(),
|
||||||
|
BindingError::RepeatedBoundVariable
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// We should have at most five bindings. Destructure them now.
|
// We should have at most five bindings. Destructure them now.
|
||||||
|
@ -156,42 +178,67 @@ impl ConjoiningClauses {
|
||||||
Binding::BindRel(bindings) => {
|
Binding::BindRel(bindings) => {
|
||||||
let bindings_count = bindings.len();
|
let bindings_count = bindings.len();
|
||||||
if bindings_count < 1 || bindings_count > 5 {
|
if bindings_count < 1 || bindings_count > 5 {
|
||||||
bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(),
|
bail!(AlgebrizerError::InvalidBinding(
|
||||||
|
where_fn.operator.clone(),
|
||||||
BindingError::InvalidNumberOfBindings {
|
BindingError::InvalidNumberOfBindings {
|
||||||
number: bindings.len(),
|
number: bindings.len(),
|
||||||
expected: 5,
|
expected: 5,
|
||||||
}));
|
}
|
||||||
|
));
|
||||||
}
|
}
|
||||||
bindings
|
bindings
|
||||||
},
|
}
|
||||||
Binding::BindScalar(_) |
|
Binding::BindScalar(_) | Binding::BindTuple(_) | Binding::BindColl(_) => {
|
||||||
Binding::BindTuple(_) |
|
bail!(AlgebrizerError::InvalidBinding(
|
||||||
Binding::BindColl(_) => bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::ExpectedBindRel)),
|
where_fn.operator.clone(),
|
||||||
|
BindingError::ExpectedBindRel
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let mut bindings = bindings.into_iter();
|
let mut bindings = bindings.into_iter();
|
||||||
let b_e = bindings.next().unwrap_or(VariableOrPlaceholder::Placeholder);
|
let b_e = bindings
|
||||||
let b_a = bindings.next().unwrap_or(VariableOrPlaceholder::Placeholder);
|
.next()
|
||||||
let b_v = bindings.next().unwrap_or(VariableOrPlaceholder::Placeholder);
|
.unwrap_or(VariableOrPlaceholder::Placeholder);
|
||||||
let b_tx = bindings.next().unwrap_or(VariableOrPlaceholder::Placeholder);
|
let b_a = bindings
|
||||||
let b_op = bindings.next().unwrap_or(VariableOrPlaceholder::Placeholder);
|
.next()
|
||||||
|
.unwrap_or(VariableOrPlaceholder::Placeholder);
|
||||||
|
let b_v = bindings
|
||||||
|
.next()
|
||||||
|
.unwrap_or(VariableOrPlaceholder::Placeholder);
|
||||||
|
let b_tx = bindings
|
||||||
|
.next()
|
||||||
|
.unwrap_or(VariableOrPlaceholder::Placeholder);
|
||||||
|
let b_op = bindings
|
||||||
|
.next()
|
||||||
|
.unwrap_or(VariableOrPlaceholder::Placeholder);
|
||||||
|
|
||||||
let mut args = where_fn.args.into_iter();
|
let mut args = where_fn.args.into_iter();
|
||||||
|
|
||||||
// TODO: process source variables.
|
// TODO: process source variables.
|
||||||
match args.next().unwrap() {
|
match args.next().unwrap() {
|
||||||
FnArg::SrcVar(SrcVar::DefaultSrc) => {},
|
FnArg::SrcVar(SrcVar::DefaultSrc) => {}
|
||||||
_ => bail!(AlgebrizerError::InvalidArgument(where_fn.operator.clone(), "source variable", 0)),
|
_ => bail!(AlgebrizerError::InvalidArgument(
|
||||||
|
where_fn.operator.clone(),
|
||||||
|
"source variable",
|
||||||
|
0
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
|
|
||||||
let tx = self.resolve_tx_argument(&known.schema, &where_fn.operator, 1, args.next().unwrap())?;
|
let tx =
|
||||||
|
self.resolve_tx_argument(&known.schema, &where_fn.operator, 1, args.next().unwrap())?;
|
||||||
|
|
||||||
let transactions = self.next_alias_for_table(DatomsTable::Transactions);
|
let transactions = self.next_alias_for_table(DatomsTable::Transactions);
|
||||||
|
|
||||||
self.from.push(SourceAlias(DatomsTable::Transactions, transactions.clone()));
|
self.from
|
||||||
|
.push(SourceAlias(DatomsTable::Transactions, transactions.clone()));
|
||||||
|
|
||||||
let tx_constraint = ColumnConstraint::Equals(
|
let tx_constraint = ColumnConstraint::Equals(
|
||||||
QualifiedAlias(transactions.clone(), Column::Transactions(TransactionsColumn::Tx)),
|
QualifiedAlias(
|
||||||
tx);
|
transactions.clone(),
|
||||||
|
Column::Transactions(TransactionsColumn::Tx),
|
||||||
|
),
|
||||||
|
tx,
|
||||||
|
);
|
||||||
self.wheres.add_intersection(tx_constraint);
|
self.wheres.add_intersection(tx_constraint);
|
||||||
|
|
||||||
if let VariableOrPlaceholder::Variable(ref var) = b_e {
|
if let VariableOrPlaceholder::Variable(ref var) = b_e {
|
||||||
|
@ -201,7 +248,12 @@ impl ConjoiningClauses {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.bind_column_to_var(known.schema, transactions.clone(), TransactionsColumn::Entity, var.clone());
|
self.bind_column_to_var(
|
||||||
|
known.schema,
|
||||||
|
transactions.clone(),
|
||||||
|
TransactionsColumn::Entity,
|
||||||
|
var.clone(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let VariableOrPlaceholder::Variable(ref var) = b_a {
|
if let VariableOrPlaceholder::Variable(ref var) = b_a {
|
||||||
|
@ -211,11 +263,21 @@ impl ConjoiningClauses {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.bind_column_to_var(known.schema, transactions.clone(), TransactionsColumn::Attribute, var.clone());
|
self.bind_column_to_var(
|
||||||
|
known.schema,
|
||||||
|
transactions.clone(),
|
||||||
|
TransactionsColumn::Attribute,
|
||||||
|
var.clone(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let VariableOrPlaceholder::Variable(ref var) = b_v {
|
if let VariableOrPlaceholder::Variable(ref var) = b_v {
|
||||||
self.bind_column_to_var(known.schema, transactions.clone(), TransactionsColumn::Value, var.clone());
|
self.bind_column_to_var(
|
||||||
|
known.schema,
|
||||||
|
transactions.clone(),
|
||||||
|
TransactionsColumn::Value,
|
||||||
|
var.clone(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let VariableOrPlaceholder::Variable(ref var) = b_tx {
|
if let VariableOrPlaceholder::Variable(ref var) = b_tx {
|
||||||
|
@ -227,7 +289,12 @@ impl ConjoiningClauses {
|
||||||
|
|
||||||
// TODO: this might be a programming error if var is our tx argument. Perhaps we can be
|
// TODO: this might be a programming error if var is our tx argument. Perhaps we can be
|
||||||
// helpful in that case.
|
// helpful in that case.
|
||||||
self.bind_column_to_var(known.schema, transactions.clone(), TransactionsColumn::Tx, var.clone());
|
self.bind_column_to_var(
|
||||||
|
known.schema,
|
||||||
|
transactions.clone(),
|
||||||
|
TransactionsColumn::Tx,
|
||||||
|
var.clone(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let VariableOrPlaceholder::Variable(ref var) = b_op {
|
if let VariableOrPlaceholder::Variable(ref var) = b_op {
|
||||||
|
@ -237,7 +304,12 @@ impl ConjoiningClauses {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.bind_column_to_var(known.schema, transactions.clone(), TransactionsColumn::Added, var.clone());
|
self.bind_column_to_var(
|
||||||
|
known.schema,
|
||||||
|
transactions,
|
||||||
|
TransactionsColumn::Added,
|
||||||
|
var.clone(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -248,18 +320,11 @@ impl ConjoiningClauses {
|
||||||
mod testing {
|
mod testing {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use mentat_core::{
|
use core_traits::{TypedValue, ValueType};
|
||||||
Schema,
|
|
||||||
TypedValue,
|
|
||||||
ValueType,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mentat_query::{
|
use mentat_core::Schema;
|
||||||
Binding,
|
|
||||||
FnArg,
|
use edn::query::{Binding, FnArg, PlainSymbol, Variable};
|
||||||
PlainSymbol,
|
|
||||||
Variable,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_apply_tx_ids() {
|
fn test_apply_tx_ids() {
|
||||||
|
@ -269,16 +334,21 @@ mod testing {
|
||||||
let known = Known::for_schema(&schema);
|
let known = Known::for_schema(&schema);
|
||||||
|
|
||||||
let op = PlainSymbol::plain("tx-ids");
|
let op = PlainSymbol::plain("tx-ids");
|
||||||
cc.apply_tx_ids(known, WhereFn {
|
cc.apply_tx_ids(
|
||||||
|
known,
|
||||||
|
WhereFn {
|
||||||
operator: op,
|
operator: op,
|
||||||
args: vec![
|
args: vec![
|
||||||
FnArg::SrcVar(SrcVar::DefaultSrc),
|
FnArg::SrcVar(SrcVar::DefaultSrc),
|
||||||
FnArg::EntidOrInteger(1000),
|
FnArg::EntidOrInteger(1000),
|
||||||
FnArg::EntidOrInteger(2000),
|
FnArg::EntidOrInteger(2000),
|
||||||
],
|
],
|
||||||
binding: Binding::BindRel(vec![VariableOrPlaceholder::Variable(Variable::from_valid_name("?tx")),
|
binding: Binding::BindRel(vec![VariableOrPlaceholder::Variable(
|
||||||
]),
|
Variable::from_valid_name("?tx"),
|
||||||
}).expect("to be able to apply_tx_ids");
|
)]),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("to be able to apply_tx_ids");
|
||||||
|
|
||||||
assert!(!cc.is_known_empty());
|
assert!(!cc.is_known_empty());
|
||||||
|
|
||||||
|
@ -289,31 +359,56 @@ mod testing {
|
||||||
let clauses = cc.wheres;
|
let clauses = cc.wheres;
|
||||||
assert_eq!(clauses.len(), 2);
|
assert_eq!(clauses.len(), 2);
|
||||||
|
|
||||||
assert_eq!(clauses.0[0],
|
assert_eq!(
|
||||||
|
clauses.0[0],
|
||||||
ColumnConstraint::Inequality {
|
ColumnConstraint::Inequality {
|
||||||
operator: Inequality::LessThanOrEquals,
|
operator: Inequality::LessThanOrEquals,
|
||||||
left: QueryValue::TypedValue(TypedValue::Ref(1000)),
|
left: QueryValue::TypedValue(TypedValue::Ref(1000)),
|
||||||
right: QueryValue::Column(QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Tx))),
|
right: QueryValue::Column(QualifiedAlias(
|
||||||
}.into());
|
"transactions00".to_string(),
|
||||||
|
Column::Transactions(TransactionsColumn::Tx)
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(clauses.0[1],
|
assert_eq!(
|
||||||
|
clauses.0[1],
|
||||||
ColumnConstraint::Inequality {
|
ColumnConstraint::Inequality {
|
||||||
operator: Inequality::LessThan,
|
operator: Inequality::LessThan,
|
||||||
left: QueryValue::Column(QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Tx))),
|
left: QueryValue::Column(QualifiedAlias(
|
||||||
|
"transactions00".to_string(),
|
||||||
|
Column::Transactions(TransactionsColumn::Tx)
|
||||||
|
)),
|
||||||
right: QueryValue::TypedValue(TypedValue::Ref(2000)),
|
right: QueryValue::TypedValue(TypedValue::Ref(2000)),
|
||||||
}.into());
|
}
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
|
||||||
let bindings = cc.column_bindings;
|
let bindings = cc.column_bindings;
|
||||||
assert_eq!(bindings.len(), 1);
|
assert_eq!(bindings.len(), 1);
|
||||||
|
|
||||||
assert_eq!(bindings.get(&Variable::from_valid_name("?tx")).expect("column binding for ?tx").clone(),
|
assert_eq!(
|
||||||
vec![QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Tx))]);
|
bindings
|
||||||
|
.get(&Variable::from_valid_name("?tx"))
|
||||||
|
.expect("column binding for ?tx")
|
||||||
|
.clone(),
|
||||||
|
vec![QualifiedAlias(
|
||||||
|
"transactions00".to_string(),
|
||||||
|
Column::Transactions(TransactionsColumn::Tx)
|
||||||
|
)]
|
||||||
|
);
|
||||||
|
|
||||||
let known_types = cc.known_types;
|
let known_types = cc.known_types;
|
||||||
assert_eq!(known_types.len(), 1);
|
assert_eq!(known_types.len(), 1);
|
||||||
|
|
||||||
assert_eq!(known_types.get(&Variable::from_valid_name("?tx")).expect("known types for ?tx").clone(),
|
assert_eq!(
|
||||||
vec![ValueType::Ref].into_iter().collect());
|
known_types
|
||||||
|
.get(&Variable::from_valid_name("?tx"))
|
||||||
|
.expect("known types for ?tx")
|
||||||
|
.clone(),
|
||||||
|
vec![ValueType::Ref].into_iter().collect()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -324,7 +419,9 @@ mod testing {
|
||||||
let known = Known::for_schema(&schema);
|
let known = Known::for_schema(&schema);
|
||||||
|
|
||||||
let op = PlainSymbol::plain("tx-data");
|
let op = PlainSymbol::plain("tx-data");
|
||||||
cc.apply_tx_data(known, WhereFn {
|
cc.apply_tx_data(
|
||||||
|
known,
|
||||||
|
WhereFn {
|
||||||
operator: op,
|
operator: op,
|
||||||
args: vec![
|
args: vec![
|
||||||
FnArg::SrcVar(SrcVar::DefaultSrc),
|
FnArg::SrcVar(SrcVar::DefaultSrc),
|
||||||
|
@ -337,7 +434,9 @@ mod testing {
|
||||||
VariableOrPlaceholder::Variable(Variable::from_valid_name("?tx")),
|
VariableOrPlaceholder::Variable(Variable::from_valid_name("?tx")),
|
||||||
VariableOrPlaceholder::Variable(Variable::from_valid_name("?added")),
|
VariableOrPlaceholder::Variable(Variable::from_valid_name("?added")),
|
||||||
]),
|
]),
|
||||||
}).expect("to be able to apply_tx_data");
|
},
|
||||||
|
)
|
||||||
|
.expect("to be able to apply_tx_data");
|
||||||
|
|
||||||
assert!(!cc.is_known_empty());
|
assert!(!cc.is_known_empty());
|
||||||
|
|
||||||
|
@ -348,47 +447,123 @@ mod testing {
|
||||||
let clauses = cc.wheres;
|
let clauses = cc.wheres;
|
||||||
assert_eq!(clauses.len(), 1);
|
assert_eq!(clauses.len(), 1);
|
||||||
|
|
||||||
assert_eq!(clauses.0[0],
|
assert_eq!(
|
||||||
ColumnConstraint::Equals(QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Tx)),
|
clauses.0[0],
|
||||||
QueryValue::TypedValue(TypedValue::Ref(1000))).into());
|
ColumnConstraint::Equals(
|
||||||
|
QualifiedAlias(
|
||||||
|
"transactions00".to_string(),
|
||||||
|
Column::Transactions(TransactionsColumn::Tx)
|
||||||
|
),
|
||||||
|
QueryValue::TypedValue(TypedValue::Ref(1000))
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
|
||||||
let bindings = cc.column_bindings;
|
let bindings = cc.column_bindings;
|
||||||
assert_eq!(bindings.len(), 5);
|
assert_eq!(bindings.len(), 5);
|
||||||
|
|
||||||
assert_eq!(bindings.get(&Variable::from_valid_name("?e")).expect("column binding for ?e").clone(),
|
assert_eq!(
|
||||||
vec![QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Entity))]);
|
bindings
|
||||||
|
.get(&Variable::from_valid_name("?e"))
|
||||||
|
.expect("column binding for ?e")
|
||||||
|
.clone(),
|
||||||
|
vec![QualifiedAlias(
|
||||||
|
"transactions00".to_string(),
|
||||||
|
Column::Transactions(TransactionsColumn::Entity)
|
||||||
|
)]
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(bindings.get(&Variable::from_valid_name("?a")).expect("column binding for ?a").clone(),
|
assert_eq!(
|
||||||
vec![QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Attribute))]);
|
bindings
|
||||||
|
.get(&Variable::from_valid_name("?a"))
|
||||||
|
.expect("column binding for ?a")
|
||||||
|
.clone(),
|
||||||
|
vec![QualifiedAlias(
|
||||||
|
"transactions00".to_string(),
|
||||||
|
Column::Transactions(TransactionsColumn::Attribute)
|
||||||
|
)]
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(bindings.get(&Variable::from_valid_name("?v")).expect("column binding for ?v").clone(),
|
assert_eq!(
|
||||||
vec![QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Value))]);
|
bindings
|
||||||
|
.get(&Variable::from_valid_name("?v"))
|
||||||
|
.expect("column binding for ?v")
|
||||||
|
.clone(),
|
||||||
|
vec![QualifiedAlias(
|
||||||
|
"transactions00".to_string(),
|
||||||
|
Column::Transactions(TransactionsColumn::Value)
|
||||||
|
)]
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(bindings.get(&Variable::from_valid_name("?tx")).expect("column binding for ?tx").clone(),
|
assert_eq!(
|
||||||
vec![QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Tx))]);
|
bindings
|
||||||
|
.get(&Variable::from_valid_name("?tx"))
|
||||||
|
.expect("column binding for ?tx")
|
||||||
|
.clone(),
|
||||||
|
vec![QualifiedAlias(
|
||||||
|
"transactions00".to_string(),
|
||||||
|
Column::Transactions(TransactionsColumn::Tx)
|
||||||
|
)]
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(bindings.get(&Variable::from_valid_name("?added")).expect("column binding for ?added").clone(),
|
assert_eq!(
|
||||||
vec![QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Added))]);
|
bindings
|
||||||
|
.get(&Variable::from_valid_name("?added"))
|
||||||
|
.expect("column binding for ?added")
|
||||||
|
.clone(),
|
||||||
|
vec![QualifiedAlias(
|
||||||
|
"transactions00".to_string(),
|
||||||
|
Column::Transactions(TransactionsColumn::Added)
|
||||||
|
)]
|
||||||
|
);
|
||||||
|
|
||||||
let known_types = cc.known_types;
|
let known_types = cc.known_types;
|
||||||
assert_eq!(known_types.len(), 4);
|
assert_eq!(known_types.len(), 4);
|
||||||
|
|
||||||
assert_eq!(known_types.get(&Variable::from_valid_name("?e")).expect("known types for ?e").clone(),
|
assert_eq!(
|
||||||
vec![ValueType::Ref].into_iter().collect());
|
known_types
|
||||||
|
.get(&Variable::from_valid_name("?e"))
|
||||||
|
.expect("known types for ?e")
|
||||||
|
.clone(),
|
||||||
|
vec![ValueType::Ref].into_iter().collect()
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(known_types.get(&Variable::from_valid_name("?a")).expect("known types for ?a").clone(),
|
assert_eq!(
|
||||||
vec![ValueType::Ref].into_iter().collect());
|
known_types
|
||||||
|
.get(&Variable::from_valid_name("?a"))
|
||||||
|
.expect("known types for ?a")
|
||||||
|
.clone(),
|
||||||
|
vec![ValueType::Ref].into_iter().collect()
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(known_types.get(&Variable::from_valid_name("?tx")).expect("known types for ?tx").clone(),
|
assert_eq!(
|
||||||
vec![ValueType::Ref].into_iter().collect());
|
known_types
|
||||||
|
.get(&Variable::from_valid_name("?tx"))
|
||||||
|
.expect("known types for ?tx")
|
||||||
|
.clone(),
|
||||||
|
vec![ValueType::Ref].into_iter().collect()
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(known_types.get(&Variable::from_valid_name("?added")).expect("known types for ?added").clone(),
|
assert_eq!(
|
||||||
vec![ValueType::Boolean].into_iter().collect());
|
known_types
|
||||||
|
.get(&Variable::from_valid_name("?added"))
|
||||||
|
.expect("known types for ?added")
|
||||||
|
.clone(),
|
||||||
|
vec![ValueType::Boolean].into_iter().collect()
|
||||||
|
);
|
||||||
|
|
||||||
let extracted_types = cc.extracted_types;
|
let extracted_types = cc.extracted_types;
|
||||||
assert_eq!(extracted_types.len(), 1);
|
assert_eq!(extracted_types.len(), 1);
|
||||||
|
|
||||||
assert_eq!(extracted_types.get(&Variable::from_valid_name("?v")).expect("extracted types for ?v").clone(),
|
assert_eq!(
|
||||||
QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::ValueTypeTag)));
|
extracted_types
|
||||||
|
.get(&Variable::from_valid_name("?v"))
|
||||||
|
.expect("extracted types for ?v")
|
||||||
|
.clone(),
|
||||||
|
QualifiedAlias(
|
||||||
|
"transactions00".to_string(),
|
||||||
|
Column::Transactions(TransactionsColumn::ValueTypeTag)
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,20 +8,13 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
use mentat_query::{
|
use edn::query::WhereFn;
|
||||||
WhereFn,
|
|
||||||
};
|
|
||||||
|
|
||||||
use clauses::{
|
use crate::clauses::ConjoiningClauses;
|
||||||
ConjoiningClauses,
|
|
||||||
};
|
|
||||||
|
|
||||||
use errors::{
|
use query_algebrizer_traits::errors::{AlgebrizerError, Result};
|
||||||
AlgebrizerError,
|
|
||||||
Result,
|
|
||||||
};
|
|
||||||
|
|
||||||
use Known;
|
use crate::Known;
|
||||||
|
|
||||||
/// Application of `where` functions.
|
/// Application of `where` functions.
|
||||||
impl ConjoiningClauses {
|
impl ConjoiningClauses {
|
||||||
|
|
|
@ -10,58 +10,33 @@
|
||||||
|
|
||||||
extern crate failure;
|
extern crate failure;
|
||||||
|
|
||||||
#[macro_use] extern crate failure_derive;
|
extern crate edn;
|
||||||
|
|
||||||
extern crate mentat_core;
|
extern crate mentat_core;
|
||||||
extern crate mentat_query;
|
#[macro_use]
|
||||||
|
extern crate core_traits;
|
||||||
|
extern crate query_algebrizer_traits;
|
||||||
|
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::ops::Sub;
|
use std::ops::Sub;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
#[macro_use]
|
mod clauses;
|
||||||
mod errors;
|
|
||||||
mod types;
|
mod types;
|
||||||
mod validate;
|
mod validate;
|
||||||
mod clauses;
|
|
||||||
|
|
||||||
use mentat_core::{
|
use core_traits::{Entid, TypedValue, ValueType};
|
||||||
CachedAttributes,
|
|
||||||
Entid,
|
use mentat_core::{parse_query, CachedAttributes, Schema};
|
||||||
Schema,
|
|
||||||
TypedValue,
|
|
||||||
ValueType,
|
|
||||||
parse_query,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mentat_core::counter::RcCounter;
|
use mentat_core::counter::RcCounter;
|
||||||
|
|
||||||
use mentat_query::{
|
use edn::query::{Element, FindSpec, Limit, Order, ParsedQuery, SrcVar, Variable, WhereClause};
|
||||||
Element,
|
|
||||||
FindSpec,
|
|
||||||
Limit,
|
|
||||||
Order,
|
|
||||||
ParsedQuery,
|
|
||||||
SrcVar,
|
|
||||||
Variable,
|
|
||||||
WhereClause,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use errors::{
|
use query_algebrizer_traits::errors::{AlgebrizerError, Result};
|
||||||
AlgebrizerError,
|
|
||||||
BindingError,
|
|
||||||
Result,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use clauses::{
|
pub use crate::clauses::{QueryInputs, VariableBindings};
|
||||||
QueryInputs,
|
|
||||||
VariableBindings,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use types::{
|
pub use crate::types::{EmptyBecause, FindQuery};
|
||||||
EmptyBecause,
|
|
||||||
FindQuery,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A convenience wrapper around things known in memory: the schema and caches.
|
/// A convenience wrapper around things known in memory: the schema and caches.
|
||||||
/// We use a trait object here to avoid making dozens of functions generic over the type
|
/// We use a trait object here to avoid making dozens of functions generic over the type
|
||||||
|
@ -70,7 +45,7 @@ pub use types::{
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct Known<'s, 'c> {
|
pub struct Known<'s, 'c> {
|
||||||
pub schema: &'s Schema,
|
pub schema: &'s Schema,
|
||||||
pub cache: Option<&'c CachedAttributes>,
|
pub cache: Option<&'c dyn CachedAttributes>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s, 'c> Known<'s, 'c> {
|
impl<'s, 'c> Known<'s, 'c> {
|
||||||
|
@ -81,7 +56,7 @@ impl<'s, 'c> Known<'s, 'c> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(s: &'s Schema, c: Option<&'c CachedAttributes>) -> Known<'s, 'c> {
|
pub fn new(s: &'s Schema, c: Option<&'c dyn CachedAttributes>) -> Known<'s, 'c> {
|
||||||
Known {
|
Known {
|
||||||
schema: s,
|
schema: s,
|
||||||
cache: c,
|
cache: c,
|
||||||
|
@ -92,36 +67,70 @@ impl<'s, 'c> Known<'s, 'c> {
|
||||||
/// This is `CachedAttributes`, but with handy generic parameters.
|
/// This is `CachedAttributes`, but with handy generic parameters.
|
||||||
/// Why not make the trait generic? Because then we can't use it as a trait object in `Known`.
|
/// Why not make the trait generic? Because then we can't use it as a trait object in `Known`.
|
||||||
impl<'s, 'c> Known<'s, 'c> {
|
impl<'s, 'c> Known<'s, 'c> {
|
||||||
pub fn is_attribute_cached_reverse<U>(&self, entid: U) -> bool where U: Into<Entid> {
|
pub fn is_attribute_cached_reverse<U>(&self, entid: U) -> bool
|
||||||
|
where
|
||||||
|
U: Into<Entid>,
|
||||||
|
{
|
||||||
self.cache
|
self.cache
|
||||||
.map(|cache| cache.is_attribute_cached_reverse(entid.into()))
|
.map(|cache| cache.is_attribute_cached_reverse(entid.into()))
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_attribute_cached_forward<U>(&self, entid: U) -> bool where U: Into<Entid> {
|
pub fn is_attribute_cached_forward<U>(&self, entid: U) -> bool
|
||||||
|
where
|
||||||
|
U: Into<Entid>,
|
||||||
|
{
|
||||||
self.cache
|
self.cache
|
||||||
.map(|cache| cache.is_attribute_cached_forward(entid.into()))
|
.map(|cache| cache.is_attribute_cached_forward(entid.into()))
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_values_for_entid<U, V>(&self, schema: &Schema, attribute: U, entid: V) -> Option<&Vec<TypedValue>>
|
pub fn get_values_for_entid<U, V>(
|
||||||
where U: Into<Entid>, V: Into<Entid> {
|
&self,
|
||||||
self.cache.and_then(|cache| cache.get_values_for_entid(schema, attribute.into(), entid.into()))
|
schema: &Schema,
|
||||||
|
attribute: U,
|
||||||
|
entid: V,
|
||||||
|
) -> Option<&Vec<TypedValue>>
|
||||||
|
where
|
||||||
|
U: Into<Entid>,
|
||||||
|
V: Into<Entid>,
|
||||||
|
{
|
||||||
|
self.cache
|
||||||
|
.and_then(|cache| cache.get_values_for_entid(schema, attribute.into(), entid.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_value_for_entid<U, V>(&self, schema: &Schema, attribute: U, entid: V) -> Option<&TypedValue>
|
pub fn get_value_for_entid<U, V>(
|
||||||
where U: Into<Entid>, V: Into<Entid> {
|
&self,
|
||||||
self.cache.and_then(|cache| cache.get_value_for_entid(schema, attribute.into(), entid.into()))
|
schema: &Schema,
|
||||||
|
attribute: U,
|
||||||
|
entid: V,
|
||||||
|
) -> Option<&TypedValue>
|
||||||
|
where
|
||||||
|
U: Into<Entid>,
|
||||||
|
V: Into<Entid>,
|
||||||
|
{
|
||||||
|
self.cache
|
||||||
|
.and_then(|cache| cache.get_value_for_entid(schema, attribute.into(), entid.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_entid_for_value<U>(&self, attribute: U, value: &TypedValue) -> Option<Entid>
|
pub fn get_entid_for_value<U>(&self, attribute: U, value: &TypedValue) -> Option<Entid>
|
||||||
where U: Into<Entid> {
|
where
|
||||||
self.cache.and_then(|cache| cache.get_entid_for_value(attribute.into(), value))
|
U: Into<Entid>,
|
||||||
|
{
|
||||||
|
self.cache
|
||||||
|
.and_then(|cache| cache.get_entid_for_value(attribute.into(), value))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_entids_for_value<U>(&self, attribute: U, value: &TypedValue) -> Option<&BTreeSet<Entid>>
|
pub fn get_entids_for_value<U>(
|
||||||
where U: Into<Entid> {
|
&self,
|
||||||
self.cache.and_then(|cache| cache.get_entids_for_value(attribute.into(), value))
|
attribute: U,
|
||||||
|
value: &TypedValue,
|
||||||
|
) -> Option<&BTreeSet<Entid>>
|
||||||
|
where
|
||||||
|
U: Into<Entid>,
|
||||||
|
{
|
||||||
|
self.cache
|
||||||
|
.and_then(|cache| cache.get_entids_for_value(attribute.into(), value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,15 +165,14 @@ impl AlgebraicQuery {
|
||||||
|
|
||||||
/// Return true if every variable in the find spec is fully bound to a single value.
|
/// Return true if every variable in the find spec is fully bound to a single value.
|
||||||
pub fn is_fully_bound(&self) -> bool {
|
pub fn is_fully_bound(&self) -> bool {
|
||||||
self.find_spec
|
self.find_spec.columns().all(|e| match e {
|
||||||
.columns()
|
|
||||||
.all(|e| match e {
|
|
||||||
// Pull expressions are never fully bound.
|
// Pull expressions are never fully bound.
|
||||||
// TODO: but the 'inside' of a pull expression certainly can be.
|
// TODO: but the 'inside' of a pull expression certainly can be.
|
||||||
&Element::Pull(_) => false,
|
&Element::Pull(_) => false,
|
||||||
|
|
||||||
&Element::Variable(ref var) |
|
&Element::Variable(ref var) | &Element::Corresponding(ref var) => {
|
||||||
&Element::Corresponding(ref var) => self.cc.is_value_bound(var),
|
self.cc.is_value_bound(var)
|
||||||
|
}
|
||||||
|
|
||||||
// For now, we pretend that aggregate functions are never fully bound:
|
// For now, we pretend that aggregate functions are never fully bound:
|
||||||
// we don't statically compute them, even if we know the value of the var.
|
// we don't statically compute them, even if we know the value of the var.
|
||||||
|
@ -175,19 +183,23 @@ impl AlgebraicQuery {
|
||||||
/// Return true if every variable in the find spec is fully bound to a single value,
|
/// Return true if every variable in the find spec is fully bound to a single value,
|
||||||
/// and evaluating the query doesn't require running SQL.
|
/// and evaluating the query doesn't require running SQL.
|
||||||
pub fn is_fully_unit_bound(&self) -> bool {
|
pub fn is_fully_unit_bound(&self) -> bool {
|
||||||
self.cc.wheres.is_empty() &&
|
self.cc.wheres.is_empty() && self.is_fully_bound()
|
||||||
self.is_fully_bound()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Return a set of the input variables mentioned in the `:in` clause that have not yet been
|
/// Return a set of the input variables mentioned in the `:in` clause that have not yet been
|
||||||
/// bound. We do this by looking at the CC.
|
/// bound. We do this by looking at the CC.
|
||||||
pub fn unbound_variables(&self) -> BTreeSet<Variable> {
|
pub fn unbound_variables(&self) -> BTreeSet<Variable> {
|
||||||
self.cc.input_variables.sub(&self.cc.value_bound_variable_set())
|
self.cc
|
||||||
|
.input_variables
|
||||||
|
.sub(&self.cc.value_bound_variable_set())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn algebrize_with_counter(known: Known, parsed: FindQuery, counter: usize) -> Result<AlgebraicQuery> {
|
pub fn algebrize_with_counter(
|
||||||
|
known: Known,
|
||||||
|
parsed: FindQuery,
|
||||||
|
counter: usize,
|
||||||
|
) -> Result<AlgebraicQuery> {
|
||||||
algebrize_with_inputs(known, parsed, counter, QueryInputs::default())
|
algebrize_with_inputs(known, parsed, counter, QueryInputs::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,8 +211,10 @@ pub fn algebrize(known: Known, parsed: FindQuery) -> Result<AlgebraicQuery> {
|
||||||
/// a vector of `OrderBy` instances, including type comparisons if necessary. This function also
|
/// a vector of `OrderBy` instances, including type comparisons if necessary. This function also
|
||||||
/// returns a set of variables that should be added to the `with` clause to make the ordering
|
/// returns a set of variables that should be added to the `with` clause to make the ordering
|
||||||
/// clauses possible.
|
/// clauses possible.
|
||||||
fn validate_and_simplify_order(cc: &ConjoiningClauses, order: Option<Vec<Order>>)
|
fn validate_and_simplify_order(
|
||||||
-> Result<(Option<Vec<OrderBy>>, BTreeSet<Variable>)> {
|
cc: &ConjoiningClauses,
|
||||||
|
order: Option<Vec<Order>>,
|
||||||
|
) -> Result<(Option<Vec<OrderBy>>, BTreeSet<Variable>)> {
|
||||||
match order {
|
match order {
|
||||||
None => Ok((None, BTreeSet::default())),
|
None => Ok((None, BTreeSet::default())),
|
||||||
Some(order) => {
|
Some(order) => {
|
||||||
|
@ -220,36 +234,50 @@ fn validate_and_simplify_order(cc: &ConjoiningClauses, order: Option<Vec<Order>>
|
||||||
|
|
||||||
// Otherwise, determine if we also need to order by type…
|
// Otherwise, determine if we also need to order by type…
|
||||||
if cc.known_type(&var).is_none() {
|
if cc.known_type(&var).is_none() {
|
||||||
order_bys.push(OrderBy(direction.clone(), VariableColumn::VariableTypeTag(var.clone())));
|
order_bys.push(OrderBy(
|
||||||
|
direction.clone(),
|
||||||
|
VariableColumn::VariableTypeTag(var.clone()),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
order_bys.push(OrderBy(direction, VariableColumn::Variable(var.clone())));
|
order_bys.push(OrderBy(direction, VariableColumn::Variable(var.clone())));
|
||||||
vars.insert(var.clone());
|
vars.insert(var.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((if order_bys.is_empty() { None } else { Some(order_bys) }, vars))
|
Ok((
|
||||||
|
if order_bys.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(order_bys)
|
||||||
|
},
|
||||||
|
vars,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn simplify_limit(mut query: AlgebraicQuery) -> Result<AlgebraicQuery> {
|
fn simplify_limit(mut query: AlgebraicQuery) -> Result<AlgebraicQuery> {
|
||||||
// Unpack any limit variables in place.
|
// Unpack any limit variables in place.
|
||||||
let refined_limit =
|
let refined_limit = match query.limit {
|
||||||
match query.limit {
|
|
||||||
Limit::Variable(ref v) => {
|
Limit::Variable(ref v) => {
|
||||||
match query.cc.bound_value(v) {
|
match query.cc.bound_value(v) {
|
||||||
Some(TypedValue::Long(n)) => {
|
Some(TypedValue::Long(n)) => {
|
||||||
if n <= 0 {
|
if n <= 0 {
|
||||||
// User-specified limits should always be natural numbers (> 0).
|
// User-specified limits should always be natural numbers (> 0).
|
||||||
bail!(AlgebrizerError::InvalidLimit(n.to_string(), ValueType::Long))
|
bail!(AlgebrizerError::InvalidLimit(
|
||||||
|
n.to_string(),
|
||||||
|
ValueType::Long
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
Some(Limit::Fixed(n as u64))
|
Some(Limit::Fixed(n as u64))
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Some(val) => {
|
Some(val) => {
|
||||||
// Same.
|
// Same.
|
||||||
bail!(AlgebrizerError::InvalidLimit(format!("{:?}", val), val.value_type()))
|
bail!(AlgebrizerError::InvalidLimit(
|
||||||
},
|
format!("{:?}", val),
|
||||||
|
val.value_type()
|
||||||
|
))
|
||||||
|
}
|
||||||
None => {
|
None => {
|
||||||
// We know that the limit variable is mentioned in `:in`.
|
// We know that the limit variable is mentioned in `:in`.
|
||||||
// That it's not bound here implies that we haven't got all the variables
|
// That it's not bound here implies that we haven't got all the variables
|
||||||
|
@ -257,9 +285,9 @@ fn simplify_limit(mut query: AlgebraicQuery) -> Result<AlgebraicQuery> {
|
||||||
// (We should never hit this in `q_once`.)
|
// (We should never hit this in `q_once`.)
|
||||||
// Simply pass the `Limit` through to `SelectQuery` untouched.
|
// Simply pass the `Limit` through to `SelectQuery` untouched.
|
||||||
None
|
None
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
}
|
||||||
Limit::None => None,
|
Limit::None => None,
|
||||||
Limit::Fixed(_) => None,
|
Limit::Fixed(_) => None,
|
||||||
};
|
};
|
||||||
|
@ -270,18 +298,21 @@ fn simplify_limit(mut query: AlgebraicQuery) -> Result<AlgebraicQuery> {
|
||||||
Ok(query)
|
Ok(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn algebrize_with_inputs(known: Known,
|
pub fn algebrize_with_inputs(
|
||||||
|
known: Known,
|
||||||
parsed: FindQuery,
|
parsed: FindQuery,
|
||||||
counter: usize,
|
counter: usize,
|
||||||
inputs: QueryInputs) -> Result<AlgebraicQuery> {
|
inputs: QueryInputs,
|
||||||
|
) -> Result<AlgebraicQuery> {
|
||||||
let alias_counter = RcCounter::with_initial(counter);
|
let alias_counter = RcCounter::with_initial(counter);
|
||||||
let mut cc = ConjoiningClauses::with_inputs_and_alias_counter(parsed.in_vars, inputs, alias_counter);
|
let mut cc =
|
||||||
|
ConjoiningClauses::with_inputs_and_alias_counter(parsed.in_vars, inputs, alias_counter);
|
||||||
|
|
||||||
// This is so the rest of the query knows that `?x` is a ref if `(pull ?x …)` appears in `:find`.
|
// This is so the rest of the query knows that `?x` is a ref if `(pull ?x …)` appears in `:find`.
|
||||||
cc.derive_types_from_find_spec(&parsed.find_spec);
|
cc.derive_types_from_find_spec(&parsed.find_spec);
|
||||||
|
|
||||||
// Do we have a variable limit? If so, tell the CC that the var must be numeric.
|
// Do we have a variable limit? If so, tell the CC that the var must be numeric.
|
||||||
if let &Limit::Variable(ref var) = &parsed.limit {
|
if let Limit::Variable(ref var) = parsed.limit {
|
||||||
cc.constrain_var_to_long(var.clone());
|
cc.constrain_var_to_long(var.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,46 +327,34 @@ pub fn algebrize_with_inputs(known: Known,
|
||||||
let (order, extra_vars) = validate_and_simplify_order(&cc, parsed.order)?;
|
let (order, extra_vars) = validate_and_simplify_order(&cc, parsed.order)?;
|
||||||
|
|
||||||
// This might leave us with an unused `:in` variable.
|
// This might leave us with an unused `:in` variable.
|
||||||
let limit = if parsed.find_spec.is_unit_limited() { Limit::Fixed(1) } else { parsed.limit };
|
let limit = if parsed.find_spec.is_unit_limited() {
|
||||||
|
Limit::Fixed(1)
|
||||||
|
} else {
|
||||||
|
parsed.limit
|
||||||
|
};
|
||||||
let q = AlgebraicQuery {
|
let q = AlgebraicQuery {
|
||||||
default_source: parsed.default_source,
|
default_source: parsed.default_source,
|
||||||
find_spec: Rc::new(parsed.find_spec),
|
find_spec: Rc::new(parsed.find_spec),
|
||||||
has_aggregates: false, // TODO: we don't parse them yet.
|
has_aggregates: false, // TODO: we don't parse them yet.
|
||||||
with: parsed.with,
|
with: parsed.with,
|
||||||
named_projection: extra_vars,
|
named_projection: extra_vars,
|
||||||
order: order,
|
order,
|
||||||
limit: limit,
|
limit,
|
||||||
cc: cc,
|
cc,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Substitute in any fixed values and fail if they're out of range.
|
// Substitute in any fixed values and fail if they're out of range.
|
||||||
simplify_limit(q)
|
simplify_limit(q)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use clauses::{
|
pub use crate::clauses::ConjoiningClauses;
|
||||||
ConjoiningClauses,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use types::{
|
pub use crate::types::{
|
||||||
Column,
|
Column, ColumnAlternation, ColumnConstraint, ColumnConstraintOrAlternation, ColumnIntersection,
|
||||||
ColumnAlternation,
|
ColumnName, ComputedTable, DatomsColumn, DatomsTable, FulltextColumn, OrderBy, QualifiedAlias,
|
||||||
ColumnConstraint,
|
QueryValue, SourceAlias, TableAlias, VariableColumn,
|
||||||
ColumnConstraintOrAlternation,
|
|
||||||
ColumnIntersection,
|
|
||||||
ColumnName,
|
|
||||||
ComputedTable,
|
|
||||||
DatomsColumn,
|
|
||||||
DatomsTable,
|
|
||||||
FulltextColumn,
|
|
||||||
OrderBy,
|
|
||||||
QualifiedAlias,
|
|
||||||
QueryValue,
|
|
||||||
SourceAlias,
|
|
||||||
TableAlias,
|
|
||||||
VariableColumn,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
impl FindQuery {
|
impl FindQuery {
|
||||||
pub fn simple(spec: FindSpec, where_clauses: Vec<WhereClause>) -> FindQuery {
|
pub fn simple(spec: FindSpec, where_clauses: Vec<WhereClause>) -> FindQuery {
|
||||||
FindQuery {
|
FindQuery {
|
||||||
|
@ -345,7 +364,7 @@ impl FindQuery {
|
||||||
in_vars: BTreeSet::default(),
|
in_vars: BTreeSet::default(),
|
||||||
in_sources: BTreeSet::default(),
|
in_sources: BTreeSet::default(),
|
||||||
limit: Limit::None,
|
limit: Limit::None,
|
||||||
where_clauses: where_clauses,
|
where_clauses,
|
||||||
order: None,
|
order: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -398,5 +417,5 @@ impl FindQuery {
|
||||||
pub fn parse_find_string(string: &str) -> Result<FindQuery> {
|
pub fn parse_find_string(string: &str) -> Result<FindQuery> {
|
||||||
parse_query(string)
|
parse_query(string)
|
||||||
.map_err(|e| e.into())
|
.map_err(|e| e.into())
|
||||||
.and_then(|parsed| FindQuery::from_parsed_query(parsed))
|
.and_then(FindQuery::from_parsed_query)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,29 +9,13 @@
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::fmt::{
|
use std::fmt::{Debug, Formatter};
|
||||||
Debug,
|
|
||||||
Formatter,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mentat_core::{
|
use core_traits::{Entid, TypedValue, ValueType, ValueTypeSet};
|
||||||
Entid,
|
|
||||||
TypedValue,
|
|
||||||
ValueRc,
|
|
||||||
ValueType,
|
|
||||||
ValueTypeSet,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mentat_query::{
|
use mentat_core::ValueRc;
|
||||||
Direction,
|
|
||||||
FindSpec,
|
use edn::query::{Direction, FindSpec, Keyword, Limit, Order, SrcVar, Variable, WhereClause};
|
||||||
Keyword,
|
|
||||||
Limit,
|
|
||||||
Order,
|
|
||||||
SrcVar,
|
|
||||||
Variable,
|
|
||||||
WhereClause,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// This enum models the fixed set of default tables we have -- two
|
/// This enum models the fixed set of default tables we have -- two
|
||||||
/// tables and two views -- and computed tables defined in the enclosing CC.
|
/// tables and two views -- and computed tables defined in the enclosing CC.
|
||||||
|
@ -48,11 +32,11 @@ pub enum DatomsTable {
|
||||||
/// A source of rows that isn't a named table -- typically a subquery or union.
|
/// A source of rows that isn't a named table -- typically a subquery or union.
|
||||||
#[derive(PartialEq, Eq, Debug)]
|
#[derive(PartialEq, Eq, Debug)]
|
||||||
pub enum ComputedTable {
|
pub enum ComputedTable {
|
||||||
Subquery(::clauses::ConjoiningClauses),
|
Subquery(Box<crate::clauses::ConjoiningClauses>),
|
||||||
Union {
|
Union {
|
||||||
projection: BTreeSet<Variable>,
|
projection: BTreeSet<Variable>,
|
||||||
type_extraction: BTreeSet<Variable>,
|
type_extraction: BTreeSet<Variable>,
|
||||||
arms: Vec<::clauses::ConjoiningClauses>,
|
arms: Vec<crate::clauses::ConjoiningClauses>,
|
||||||
},
|
},
|
||||||
NamedValues {
|
NamedValues {
|
||||||
names: Vec<Variable>,
|
names: Vec<Variable>,
|
||||||
|
@ -169,8 +153,8 @@ impl ColumnName for DatomsColumn {
|
||||||
impl ColumnName for VariableColumn {
|
impl ColumnName for VariableColumn {
|
||||||
fn column_name(&self) -> String {
|
fn column_name(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
&VariableColumn::Variable(ref v) => v.to_string(),
|
VariableColumn::Variable(ref v) => v.to_string(),
|
||||||
&VariableColumn::VariableTypeTag(ref v) => format!("{}_value_type_tag", v.as_str()),
|
VariableColumn::VariableTypeTag(ref v) => format!("{}_value_type_tag", v.as_str()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -179,8 +163,8 @@ impl Debug for VariableColumn {
|
||||||
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
// These should agree with VariableColumn::column_name.
|
// These should agree with VariableColumn::column_name.
|
||||||
&VariableColumn::Variable(ref v) => write!(f, "{}", v.as_str()),
|
VariableColumn::Variable(ref v) => write!(f, "{}", v.as_str()),
|
||||||
&VariableColumn::VariableTypeTag(ref v) => write!(f, "{}_value_type_tag", v.as_str()),
|
VariableColumn::VariableTypeTag(ref v) => write!(f, "{}_value_type_tag", v.as_str()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -194,10 +178,10 @@ impl Debug for DatomsColumn {
|
||||||
impl Debug for Column {
|
impl Debug for Column {
|
||||||
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
&Column::Fixed(ref c) => c.fmt(f),
|
Column::Fixed(ref c) => c.fmt(f),
|
||||||
&Column::Fulltext(ref c) => c.fmt(f),
|
Column::Fulltext(ref c) => c.fmt(f),
|
||||||
&Column::Variable(ref v) => v.fmt(f),
|
Column::Variable(ref v) => v.fmt(f),
|
||||||
&Column::Transactions(ref t) => t.fmt(f),
|
Column::Transactions(ref t) => t.fmt(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -292,7 +276,8 @@ impl QualifiedAlias {
|
||||||
Column::Fulltext(_) => None,
|
Column::Fulltext(_) => None,
|
||||||
Column::Variable(_) => None,
|
Column::Variable(_) => None,
|
||||||
Column::Transactions(ref c) => c.associated_type_tag_column().map(Column::Transactions),
|
Column::Transactions(ref c) => c.associated_type_tag_column().map(Column::Transactions),
|
||||||
}.map(|d| QualifiedAlias(self.0.clone(), d))
|
}
|
||||||
|
.map(|d| QualifiedAlias(self.0.clone(), d))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -313,19 +298,10 @@ impl Debug for QueryValue {
|
||||||
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
|
||||||
use self::QueryValue::*;
|
use self::QueryValue::*;
|
||||||
match self {
|
match self {
|
||||||
&Column(ref qa) => {
|
Column(ref qa) => write!(f, "{:?}", qa),
|
||||||
write!(f, "{:?}", qa)
|
Entid(ref entid) => write!(f, "entity({:?})", entid),
|
||||||
},
|
TypedValue(ref typed_value) => write!(f, "value({:?})", typed_value),
|
||||||
&Entid(ref entid) => {
|
PrimitiveLong(value) => write!(f, "primitive({:?})", value),
|
||||||
write!(f, "entity({:?})", entid)
|
|
||||||
},
|
|
||||||
&TypedValue(ref typed_value) => {
|
|
||||||
write!(f, "value({:?})", typed_value)
|
|
||||||
},
|
|
||||||
&PrimitiveLong(value) => {
|
|
||||||
write!(f, "primitive({:?})", value)
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -399,24 +375,15 @@ impl Inequality {
|
||||||
}
|
}
|
||||||
|
|
||||||
// The built-in inequality operators apply to Long, Double, and Instant.
|
// The built-in inequality operators apply to Long, Double, and Instant.
|
||||||
pub fn supported_types(&self) -> ValueTypeSet {
|
pub fn supported_types(self) -> ValueTypeSet {
|
||||||
use self::Inequality::*;
|
use self::Inequality::*;
|
||||||
match self {
|
match self {
|
||||||
&LessThan |
|
LessThan | LessThanOrEquals | GreaterThan | GreaterThanOrEquals | NotEquals => {
|
||||||
&LessThanOrEquals |
|
|
||||||
&GreaterThan |
|
|
||||||
&GreaterThanOrEquals |
|
|
||||||
&NotEquals => {
|
|
||||||
let mut ts = ValueTypeSet::of_numeric_types();
|
let mut ts = ValueTypeSet::of_numeric_types();
|
||||||
ts.insert(ValueType::Instant);
|
ts.insert(ValueType::Instant);
|
||||||
ts
|
ts
|
||||||
},
|
}
|
||||||
&Unpermute |
|
Unpermute | Differ | TxAfter | TxBefore => ValueTypeSet::of_one(ValueType::Ref),
|
||||||
&Differ |
|
|
||||||
&TxAfter |
|
|
||||||
&TxBefore => {
|
|
||||||
ValueTypeSet::of_one(ValueType::Ref)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -425,17 +392,17 @@ impl Debug for Inequality {
|
||||||
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
|
||||||
use self::Inequality::*;
|
use self::Inequality::*;
|
||||||
f.write_str(match self {
|
f.write_str(match self {
|
||||||
&LessThan => "<",
|
LessThan => "<",
|
||||||
&LessThanOrEquals => "<=",
|
LessThanOrEquals => "<=",
|
||||||
&GreaterThan => ">",
|
GreaterThan => ">",
|
||||||
&GreaterThanOrEquals => ">=",
|
GreaterThanOrEquals => ">=",
|
||||||
&NotEquals => "!=", // Datalog uses !=. SQL uses <>.
|
NotEquals => "!=", // Datalog uses !=. SQL uses <>.
|
||||||
|
|
||||||
&Unpermute => "<",
|
Unpermute => "<",
|
||||||
&Differ => "<>",
|
Differ => "<>",
|
||||||
|
|
||||||
&TxAfter => ">",
|
TxAfter => ">",
|
||||||
&TxBefore => "<",
|
TxBefore => "<",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -567,51 +534,78 @@ impl Debug for ColumnConstraint {
|
||||||
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
|
||||||
use self::ColumnConstraint::*;
|
use self::ColumnConstraint::*;
|
||||||
match self {
|
match self {
|
||||||
&Equals(ref qa1, ref thing) => {
|
Equals(ref qa1, ref thing) => write!(f, "{:?} = {:?}", qa1, thing),
|
||||||
write!(f, "{:?} = {:?}", qa1, thing)
|
|
||||||
},
|
|
||||||
|
|
||||||
&Inequality { operator, ref left, ref right } => {
|
Inequality {
|
||||||
write!(f, "{:?} {:?} {:?}", left, operator, right)
|
operator,
|
||||||
},
|
ref left,
|
||||||
|
ref right,
|
||||||
|
} => write!(f, "{:?} {:?} {:?}", left, operator, right),
|
||||||
|
|
||||||
&Matches(ref qa, ref thing) => {
|
Matches(ref qa, ref thing) => write!(f, "{:?} MATCHES {:?}", qa, thing),
|
||||||
write!(f, "{:?} MATCHES {:?}", qa, thing)
|
|
||||||
},
|
|
||||||
|
|
||||||
&HasTypes { ref value, ref value_types, check_value } => {
|
HasTypes {
|
||||||
|
ref value,
|
||||||
|
ref value_types,
|
||||||
|
check_value,
|
||||||
|
} => {
|
||||||
// This is cludgey, but it's debug code.
|
// This is cludgey, but it's debug code.
|
||||||
write!(f, "(")?;
|
write!(f, "(")?;
|
||||||
for value_type in value_types.iter() {
|
for value_type in value_types.iter() {
|
||||||
write!(f, "({:?}.value_type_tag = {:?}", value, value_type)?;
|
write!(f, "({:?}.value_type_tag = {:?}", value, value_type)?;
|
||||||
if check_value && value_type == ValueType::Double || value_type == ValueType::Long {
|
if *check_value && value_type == ValueType::Double
|
||||||
write!(f, " AND typeof({:?}) = '{:?}')", value,
|
|| value_type == ValueType::Long
|
||||||
if value_type == ValueType::Double { "real" } else { "integer" })?;
|
{
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
" AND typeof({:?}) = '{:?}')",
|
||||||
|
value,
|
||||||
|
if value_type == ValueType::Double {
|
||||||
|
"real"
|
||||||
|
} else {
|
||||||
|
"integer"
|
||||||
|
}
|
||||||
|
)?;
|
||||||
} else {
|
} else {
|
||||||
write!(f, ")")?;
|
write!(f, ")")?;
|
||||||
}
|
}
|
||||||
write!(f, " OR ")?;
|
write!(f, " OR ")?;
|
||||||
}
|
}
|
||||||
write!(f, "1)")
|
write!(f, "1)")
|
||||||
},
|
}
|
||||||
&NotExists(ref ct) => {
|
NotExists(ref ct) => write!(f, "NOT EXISTS {:?}", ct),
|
||||||
write!(f, "NOT EXISTS {:?}", ct)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone)]
|
#[derive(PartialEq, Clone)]
|
||||||
pub enum EmptyBecause {
|
pub enum EmptyBecause {
|
||||||
CachedAttributeHasNoValues { entity: Entid, attr: Entid },
|
CachedAttributeHasNoValues {
|
||||||
CachedAttributeHasNoEntity { value: TypedValue, attr: Entid },
|
entity: Entid,
|
||||||
ConflictingBindings { var: Variable, existing: TypedValue, desired: TypedValue },
|
attr: Entid,
|
||||||
|
},
|
||||||
|
CachedAttributeHasNoEntity {
|
||||||
|
value: TypedValue,
|
||||||
|
attr: Entid,
|
||||||
|
},
|
||||||
|
ConflictingBindings {
|
||||||
|
var: Variable,
|
||||||
|
existing: TypedValue,
|
||||||
|
desired: TypedValue,
|
||||||
|
},
|
||||||
|
|
||||||
// A variable is known to be of two conflicting sets of types.
|
// A variable is known to be of two conflicting sets of types.
|
||||||
TypeMismatch { var: Variable, existing: ValueTypeSet, desired: ValueTypeSet },
|
TypeMismatch {
|
||||||
|
var: Variable,
|
||||||
|
existing: ValueTypeSet,
|
||||||
|
desired: ValueTypeSet,
|
||||||
|
},
|
||||||
|
|
||||||
// The same, but for non-variables.
|
// The same, but for non-variables.
|
||||||
KnownTypeMismatch { left: ValueTypeSet, right: ValueTypeSet },
|
KnownTypeMismatch {
|
||||||
|
left: ValueTypeSet,
|
||||||
|
right: ValueTypeSet,
|
||||||
|
},
|
||||||
NoValidTypes(Variable),
|
NoValidTypes(Variable),
|
||||||
NonAttributeArgument,
|
NonAttributeArgument,
|
||||||
NonInstantArgument,
|
NonInstantArgument,
|
||||||
|
@ -631,69 +625,63 @@ impl Debug for EmptyBecause {
|
||||||
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
|
||||||
use self::EmptyBecause::*;
|
use self::EmptyBecause::*;
|
||||||
match self {
|
match self {
|
||||||
&CachedAttributeHasNoEntity { ref value, ref attr } => {
|
CachedAttributeHasNoEntity {
|
||||||
write!(f, "(?e, {}, {:?}, _) not present in store", attr, value)
|
ref value,
|
||||||
},
|
ref attr,
|
||||||
&CachedAttributeHasNoValues { ref entity, ref attr } => {
|
} => write!(f, "(?e, {}, {:?}, _) not present in store", attr, value),
|
||||||
write!(f, "({}, {}, ?v, _) not present in store", entity, attr)
|
CachedAttributeHasNoValues {
|
||||||
},
|
ref entity,
|
||||||
&ConflictingBindings { ref var, ref existing, ref desired } => {
|
ref attr,
|
||||||
write!(f, "Var {:?} can't be {:?} because it's already bound to {:?}",
|
} => write!(f, "({}, {}, ?v, _) not present in store", entity, attr),
|
||||||
var, desired, existing)
|
ConflictingBindings {
|
||||||
},
|
ref var,
|
||||||
&TypeMismatch { ref var, ref existing, ref desired } => {
|
ref existing,
|
||||||
write!(f, "Type mismatch: {:?} can't be {:?}, because it's already {:?}",
|
ref desired,
|
||||||
var, desired, existing)
|
} => write!(
|
||||||
},
|
f,
|
||||||
&KnownTypeMismatch { ref left, ref right } => {
|
"Var {:?} can't be {:?} because it's already bound to {:?}",
|
||||||
write!(f, "Type mismatch: {:?} can't be compared to {:?}",
|
var, desired, existing
|
||||||
left, right)
|
),
|
||||||
},
|
TypeMismatch {
|
||||||
&NoValidTypes(ref var) => {
|
ref var,
|
||||||
write!(f, "Type mismatch: {:?} has no valid types", var)
|
ref existing,
|
||||||
},
|
ref desired,
|
||||||
&NonAttributeArgument => {
|
} => write!(
|
||||||
write!(f, "Non-attribute argument in attribute place")
|
f,
|
||||||
},
|
"Type mismatch: {:?} can't be {:?}, because it's already {:?}",
|
||||||
&NonInstantArgument => {
|
var, desired, existing
|
||||||
write!(f, "Non-instant argument in instant place")
|
),
|
||||||
},
|
KnownTypeMismatch {
|
||||||
&NonEntityArgument => {
|
ref left,
|
||||||
write!(f, "Non-entity argument in entity place")
|
ref right,
|
||||||
},
|
} => write!(
|
||||||
&NonNumericArgument => {
|
f,
|
||||||
write!(f, "Non-numeric argument in numeric place")
|
"Type mismatch: {:?} can't be compared to {:?}",
|
||||||
},
|
left, right
|
||||||
&NonStringFulltextValue => {
|
),
|
||||||
write!(f, "Non-string argument for fulltext attribute")
|
NoValidTypes(ref var) => write!(f, "Type mismatch: {:?} has no valid types", var),
|
||||||
},
|
NonAttributeArgument => write!(f, "Non-attribute argument in attribute place"),
|
||||||
&UnresolvedIdent(ref kw) => {
|
NonInstantArgument => write!(f, "Non-instant argument in instant place"),
|
||||||
write!(f, "Couldn't resolve keyword {}", kw)
|
NonEntityArgument => write!(f, "Non-entity argument in entity place"),
|
||||||
},
|
NonNumericArgument => write!(f, "Non-numeric argument in numeric place"),
|
||||||
&InvalidAttributeIdent(ref kw) => {
|
NonStringFulltextValue => write!(f, "Non-string argument for fulltext attribute"),
|
||||||
write!(f, "{} does not name an attribute", kw)
|
UnresolvedIdent(ref kw) => write!(f, "Couldn't resolve keyword {}", kw),
|
||||||
},
|
InvalidAttributeIdent(ref kw) => write!(f, "{} does not name an attribute", kw),
|
||||||
&InvalidAttributeEntid(entid) => {
|
InvalidAttributeEntid(entid) => write!(f, "{} is not an attribute", entid),
|
||||||
write!(f, "{} is not an attribute", entid)
|
NonFulltextAttribute(entid) => write!(f, "{} is not a fulltext attribute", entid),
|
||||||
},
|
InvalidBinding(ref column, ref tv) => {
|
||||||
&NonFulltextAttribute(entid) => {
|
|
||||||
write!(f, "{} is not a fulltext attribute", entid)
|
|
||||||
},
|
|
||||||
&InvalidBinding(ref column, ref tv) => {
|
|
||||||
write!(f, "{:?} cannot name column {:?}", tv, column)
|
write!(f, "{:?} cannot name column {:?}", tv, column)
|
||||||
},
|
}
|
||||||
&ValueTypeMismatch(value_type, ref typed_value) => {
|
ValueTypeMismatch(value_type, ref typed_value) => write!(
|
||||||
write!(f, "Type mismatch: {:?} doesn't match attribute type {:?}",
|
f,
|
||||||
typed_value, value_type)
|
"Type mismatch: {:?} doesn't match attribute type {:?}",
|
||||||
},
|
typed_value, value_type
|
||||||
&AttributeLookupFailed => {
|
),
|
||||||
write!(f, "Attribute lookup failed")
|
AttributeLookupFailed => write!(f, "Attribute lookup failed"),
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// A `FindQuery` represents a valid query to the query algebrizer.
|
/// A `FindQuery` represents a valid query to the query algebrizer.
|
||||||
///
|
///
|
||||||
/// We split `FindQuery` from `ParsedQuery` because it's not easy to generalize over containers
|
/// We split `FindQuery` from `ParsedQuery` because it's not easy to generalize over containers
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue