Compare commits
769 commits
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 | ||
|
5b4f50ce1b | ||
|
3cd61a0c93 | ||
|
65e9822ad6 | ||
|
4325d6c0c3 | ||
|
e06bfd1b7d | ||
|
a7d2057bc6 | ||
|
2978ad91c0 | ||
|
190e05e360 | ||
|
d23f2b373a | ||
|
6856462f1b | ||
|
536d40ad84 | ||
|
4ec780c87a | ||
|
3ca5255cde | ||
|
0974108a52 | ||
|
5a29efa336 | ||
|
9a47d8905f | ||
|
e6066769ca | ||
|
fba378ee39 | ||
|
faef4e9ee8 | ||
|
a8cc9cb70d | ||
|
76d7df5548 | ||
|
7e31ca15bc | ||
|
67a14ca756 | ||
|
0955c784b7 | ||
|
9e6505a930 | ||
|
89d8ac50a8 | ||
|
2540404b00 | ||
|
22dad5d6ca | ||
|
69c9f512a0 | ||
|
6290cc9db2 | ||
|
38a92229d7 | ||
|
675a865896 | ||
|
e9cddd63e4 | ||
|
9291b2a0b0 | ||
|
bff24c60b7 | ||
|
61e6b85e6a | ||
|
82610f17f8 | ||
|
c0ddc2ca70 | ||
|
7d2fe8c625 | ||
|
0549bbd604 | ||
|
dcc0770ca4 | ||
|
ad2b646700 | ||
|
f65512158b | ||
|
07c5d733d6 | ||
|
46f7db36c9 | ||
|
2cb7d441dc | ||
|
e362ca6213 | ||
|
2ab481f83e | ||
|
1c0602fa00 | ||
|
3744982cd9 | ||
|
b9f3681728 | ||
|
d49f702512 | ||
|
99deb87b9f | ||
|
eb1df31ac4 | ||
|
06056a8468 | ||
|
1cb1847aa6 | ||
|
76507623ac | ||
|
106d6fae11 | ||
|
02a163a10f | ||
|
87f850a44e | ||
|
d82c7f8ef2 | ||
|
8725bad18c | ||
|
da599c3a78 | ||
|
8af5288a60 | ||
|
5fe4f12d32 | ||
|
ae427849d5 | ||
|
d31ec28aa8 | ||
|
ac1b0b15fe | ||
|
b2249f189d | ||
|
af005a7669 | ||
|
d6569a6a22 | ||
|
0e4991fa26 | ||
|
72a9b302f9 | ||
|
f335253d4c | ||
|
605c3d938c | ||
|
7232e6ef33 | ||
|
b323448630 | ||
|
08b83abe21 | ||
|
c5180656cc | ||
|
7f76d53612 | ||
|
4ea9c78c50 | ||
|
c41d728d1d | ||
|
c19337c8bf | ||
|
8e2d795778 | ||
|
60a57ea493 | ||
|
3760f84da8 | ||
|
4e46adeba1 | ||
|
31de5be64f | ||
|
0adfa6aae6 | ||
|
800f404a23 | ||
|
4e01929334 | ||
|
836fdb3a35 | ||
|
c075434f84 | ||
|
061967f268 | ||
|
326fe881a0 | ||
|
ce3ce1ccbf | ||
|
fb7d2357de | ||
|
aae50f40ac | ||
|
4282b2d332 | ||
|
91fa34e462 | ||
|
88c6a4b05c | ||
|
87fb505c56 | ||
|
54d592df29 | ||
|
99a73ccb03 | ||
|
8e918949fb | ||
|
f041dfe509 | ||
|
8cc0e5a64e | ||
|
6a1a265894 | ||
|
c0d4568970 | ||
|
3d5ae797b2 | ||
|
cfed968514 | ||
|
e68cc4016c | ||
|
d4166cc67c | ||
|
47441f56dc | ||
|
09f1d633b5 | ||
|
a8073056f2 | ||
|
a4a8892309 | ||
|
1d8d94f887 | ||
|
ad9a1394a3 | ||
|
f1fc9f1846 | ||
|
3cc8b4fd24 | ||
|
47a0f40cce | ||
|
729fe59578 | ||
|
d8d18a1731 | ||
|
2a025916fe | ||
|
93b7d25446 | ||
|
250e35b726 | ||
|
b0421c61b4 | ||
|
01db9232b4 | ||
|
e4447927c7 | ||
|
6121da3592 | ||
|
2c0f755632 | ||
|
35467c1b24 | ||
|
8add073001 | ||
|
b4b558e196 | ||
|
e0cd9b6b20 | ||
|
38c1a93712 | ||
|
ed5427253b | ||
|
b2e98f44f6 | ||
|
46c2a0801f | ||
|
e5e37178af | ||
|
3cba87c74b | ||
|
013629dec6 | ||
|
60cb5d2432 | ||
|
4fde4fe0a6 | ||
|
3dc68bcd38 | ||
|
c8f74fa41b | ||
|
37a6f7be28 | ||
|
9a4bd0de4f | ||
|
7a8c9d90c2 | ||
|
cbffe5e545 | ||
|
e437944d94 | ||
|
4c4af46315 | ||
|
e1e7cbaa44 | ||
|
e21156a754 | ||
|
90465ae74a | ||
|
1b66818ac9 | ||
|
9513012aa5 | ||
|
2b82ffb2e5 | ||
|
7960b4ccd2 | ||
|
d4a635f4e7 | ||
|
32ed56685e | ||
|
f979044ba1 | ||
|
a2e13f624c | ||
|
1818e0b98e | ||
|
a74a2deffc | ||
|
0c31fc7875 | ||
|
c8da4be38f | ||
|
e532614908 | ||
|
36eca0bfb0 | ||
|
1f1818448a | ||
|
8ca657ec03 | ||
|
1509d16c3e | ||
|
909b2a8be5 | ||
|
39f1d61175 | ||
|
3f8464e8ed | ||
|
a5cda7c3e9 | ||
|
27dde378e0 | ||
|
175958e754 | ||
|
19ddf9c384 | ||
|
fa7dd2ceab | ||
|
9741435026 | ||
|
7382e3297d | ||
|
b3e27d86a9 | ||
|
a57f7aff99 | ||
|
8607ecb745 | ||
|
4d8e179a59 | ||
|
558906df4f | ||
|
6c54e1d370 | ||
|
9cc5cbf288 | ||
|
ca451a7c9c | ||
|
66b892572c | ||
|
9f30fe6295 | ||
|
ab957948b4 | ||
|
d4365fa4cd | ||
|
ecc4a7a35a | ||
|
c2e5052877 | ||
|
9f3d2c08b2 | ||
|
8d60f2b3d1 | ||
|
dfa0d3e321 | ||
|
f71b2b207e | ||
|
16a66517e4 | ||
|
994a3e65e2 | ||
|
df58de52f4 | ||
|
ea52e214af | ||
|
833ff92436 | ||
|
46835885e4 | ||
|
3e615becd8 | ||
|
1817ce7c0b | ||
|
a0c70a7cd9 | ||
|
f42ae35b70 | ||
|
bead9752bd | ||
|
0ca61017a1 | ||
|
4a0b67ab50 | ||
|
9b23cf3945 | ||
|
30bf827d16 | ||
|
d46535a7c2 | ||
|
36d455150d | ||
|
5e50d2a9b4 | ||
|
39cf26aa76 | ||
|
c53da08a00 | ||
|
23ebc3a5fc | ||
|
20d1b45293 | ||
|
54bd883c65 | ||
|
e33fe71c47 | ||
|
df3cdb5db6 | ||
|
ae91603bd0 | ||
|
93e5dff9c8 | ||
|
c84db82575 | ||
|
48ffa20d4c | ||
|
9655c4b85d | ||
|
01d9b83a9b | ||
|
0dcb7df1c7 | ||
|
dec86bb2c5 | ||
|
23e11fabe6 | ||
|
2ac7a1b1de | ||
|
84f29676e8 | ||
|
d11810dca7 | ||
|
a6341f6fd6 | ||
|
715d434945 | ||
|
d848d954cf | ||
|
e9ed103723 | ||
|
e817f67470 | ||
|
66e6fef75e | ||
|
37a7c9ea48 | ||
|
2614f498be | ||
|
3bf7459315 | ||
|
98502eb68f | ||
|
ef9f2d9c51 | ||
|
6ed5413cd4 | ||
|
812f10b3e4 | ||
|
3d28949add | ||
|
4acc6d0658 | ||
|
6797a606b5 | ||
|
224570fb45 | ||
|
50a9e0c21f | ||
|
ab5b67ecf7 | ||
|
8cc7f0b72e | ||
|
1fcc3d7e1b | ||
|
9740cafdbd | ||
|
023fd9b70b | ||
|
ebb77d59bc | ||
|
579829d091 | ||
|
95e95d735e | ||
|
1589104841 | ||
|
c61bc79b99 | ||
|
e8ec59e464 | ||
|
b7fb44a5a6 | ||
|
a3b8fd3022 | ||
|
75bcb76dd5 | ||
|
1f0c4e3107 | ||
|
2fc4cb5a2d | ||
|
95b9c7f7f5 | ||
|
1b72f5bbb6 | ||
|
03fee722e9 | ||
|
df90c366af | ||
|
55588209c2 | ||
|
c600152d78 | ||
|
c15973f269 | ||
|
d1ad3c47f7 | ||
|
eaf3e7fc4b | ||
|
ea0e9d4c7b | ||
|
aa5f569df5 | ||
|
20aa11dcbd | ||
|
dd39f6df5b | ||
|
3f264e9eb2 | ||
|
565a0e9ff9 | ||
|
17c59bbff6 | ||
|
f7a3fd5b17 | ||
|
54bdd382fb | ||
|
c2ec1a6bdf | ||
|
03c0930285 | ||
|
5d5e85bcba | ||
|
8ec24f01f6 | ||
|
e1e549440f | ||
|
79fa0994b3 | ||
|
d04d22a6a6 | ||
|
b9cbf92205 | ||
|
c6e933c396 | ||
|
d30ad428e8 | ||
|
4a886aae17 | ||
|
9ac2b8c680 | ||
|
899e5d0971 | ||
|
13e27c83e2 | ||
|
4d2eb7222e | ||
|
2f38f1e73e | ||
|
002c918c96 | ||
|
70c5bcfa99 | ||
|
63574af7ac | ||
|
06bb8e99a7 | ||
|
9fe31d443d | ||
|
08534a1a3a | ||
|
a10c6fc67a | ||
|
dbbbd220f9 | ||
|
9a12ced317 | ||
|
5c5818069f | ||
|
c165972684 | ||
|
59a710f80f | ||
|
eb220528bf | ||
|
d88823e7c4 | ||
|
0be78cf956 | ||
|
4b0881a957 | ||
|
05129cefbb | ||
|
2650fe163d | ||
|
a4fc04ea86 | ||
|
634b7a816b | ||
|
2c52346999 | ||
|
ed04083ceb | ||
|
7fc0848cb0 | ||
|
a88375fc15 | ||
|
8a8fcedd1c | ||
|
409a2ea78f | ||
|
d1ac752de6 | ||
|
4fa57942d3 | ||
|
953f9f7734 | ||
|
c95ec13ffe | ||
|
3d4615fb8c | ||
|
1dc8a3eaa0 | ||
|
059b9d1182 | ||
|
523d5ea5f1 | ||
|
daca8def57 | ||
|
bd389d2f0d | ||
|
e64ee5864e | ||
|
19fc7cddf1 | ||
|
bc63744aba | ||
|
cd860ae68d | ||
|
bffefe7e6b | ||
|
aa14a71019 | ||
|
ff0147e89c | ||
|
60c082b61e | ||
|
dfc846e483 | ||
|
651308f721 | ||
|
a9a82ea1a7 | ||
|
01af45ab3f | ||
|
8ddbc834ae | ||
|
5718ce0155 | ||
|
5cd53aff44 | ||
|
99b7e89116 | ||
|
35d73d5541 | ||
|
64acc6a7ee | ||
|
d8f761993d | ||
|
758ab8b476 | ||
|
bca8b7e322 | ||
|
d8075aa07d | ||
|
08d2c613a4 | ||
|
7948788936 | ||
|
79ccd818f3 | ||
|
98ac559894 | ||
|
33fa1261b8 | ||
|
b9f9b4ff58 | ||
|
e984e02529 | ||
|
1636134a72 | ||
|
e280811243 | ||
|
2b61944f09 | ||
|
b693385495 | ||
|
a07efc0a9e | ||
|
72977f52e4 | ||
|
0639c94468 | ||
|
9df18e4286 | ||
|
b117e2c463 | ||
|
5369f03464 | ||
|
a5023c70cb | ||
|
8ae8466cf9 | ||
|
92cdd72500 | ||
|
2b2b5cf696 | ||
|
74f188df9b | ||
|
9e5c735460 | ||
|
72eeedec74 | ||
|
ce3c4f0dca | ||
|
01ca0ae5c1 | ||
|
997df0b776 | ||
|
460fdac252 | ||
|
439f3a2283 | ||
|
d2e6b767c6 | ||
|
95a5326e23 | ||
|
8e6f37e709 | ||
|
b24db01744 | ||
|
8adb6d97fd | ||
|
0d15381e11 | ||
|
4b874deae1 | ||
|
88df7b3b33 | ||
|
5e971f3b22 | ||
|
cb4ba9e68f | ||
|
7024978517 | ||
|
97749833d0 | ||
|
d83c8620cd | ||
|
1c4e30a906 | ||
|
f5aa6b2c2c | ||
|
d8d36140a9 | ||
|
11a9a30d35 | ||
|
fe307f8b7a | ||
|
3d66cb5d0f | ||
|
2129514e86 | ||
|
55291b4d30 | ||
|
15b4195a6e | ||
|
8beea55e39 | ||
|
1801db2a77 | ||
|
70e5759b5f | ||
|
dc6a7a4128 | ||
|
fddc57d548 | ||
|
6109a63249 | ||
|
914902cf9e | ||
|
30804e033a | ||
|
39440895ab | ||
|
77f7fab525 | ||
|
bf38105fef | ||
|
b5867e9131 | ||
|
ce3a9bdf87 | ||
|
8935d6a8a5 | ||
|
1961815acd | ||
|
7bcf311db9 | ||
|
e898df8842 | ||
|
85f3b79f75 | ||
|
70b112801c | ||
|
f86b24001f | ||
|
ecf56395b9 | ||
|
d7f323d15d | ||
|
48312e1ff0 | ||
|
b2f22952c1 | ||
|
dcd9bcb1ce | ||
|
5e3cdd1fc2 | ||
|
91c75f26c8 | ||
|
76f51015d9 | ||
|
b0120aa446 | ||
|
14972fa6d7 | ||
|
40bca2df6d | ||
|
60b2e6f885 | ||
|
7fc2a22d68 | ||
|
bf707acbc3 | ||
|
0d3b8e4b29 | ||
|
1b26e23d02 | ||
|
7476d0c0c8 | ||
|
e18a0e9fdc | ||
|
6fa907d2df | ||
|
a10f68fdb7 | ||
|
42ae26ab46 | ||
|
9ecd02ef95 | ||
|
a9cd9b1e87 | ||
|
f890995202 | ||
|
bc2b2ec4c8 | ||
|
6f67f8563b | ||
|
a59f9583ac | ||
|
ec2bbb8e83 | ||
|
896d7f8f88 | ||
|
d9b699b588 | ||
|
89949fb451 | ||
|
5af7082165 | ||
|
f36a78e61e | ||
|
42f03f55a2 | ||
|
b165a0b2ad | ||
|
16e9740d8a | ||
|
bfb62302cb | ||
|
a87e5a3ec7 | ||
|
2e303f4837 | ||
|
4e81733eed | ||
|
4f5c94891a | ||
|
6ce5d526a3 | ||
|
f591c90738 | ||
|
1deed24f42 | ||
|
49f91b05b0 | ||
|
cb40a0f581 | ||
|
8e2359d3ab | ||
|
b56b7c2a3f | ||
|
fc73bfce75 | ||
|
1aa33423dc | ||
|
1122960462 | ||
|
9be487ca7d | ||
|
4366f6d61f | ||
|
42580539b8 | ||
|
7db74953d6 | ||
|
21f7bdf493 | ||
|
688a644bd9 | ||
|
9fcf9f3318 | ||
|
c111d4daff | ||
|
afafcd64a0 | ||
|
c585715224 | ||
|
4d83aafa2a | ||
|
a627f532f0 | ||
|
419db388da | ||
|
00c99196a2 | ||
|
0b20d7691b | ||
|
c038c11017 | ||
|
9ee0ac8e00 | ||
|
72da5722ae | ||
|
611fbe2eef | ||
|
5b770a54cd | ||
|
cd5f0d642c | ||
|
707ce36236 | ||
|
5d74f1ee94 | ||
|
816a85f0a3 | ||
|
9a5ece8c89 | ||
|
2ecda0a2bd | ||
|
a685d6c541 | ||
|
cc56cec11a | ||
|
00048d1955 | ||
|
fcdf759399 | ||
|
f3f353661f | ||
|
4e9e8ed837 | ||
|
4b2c7870c0 | ||
|
17bc85fe27 | ||
|
25474980b1 | ||
|
8f68f68378 | ||
|
85da91a0ab | ||
|
93053a4297 | ||
|
ba1896b684 | ||
|
a9929249eb | ||
|
592dec7241 | ||
|
0b3387f8b9 | ||
|
f1a55c9f12 | ||
|
932a42866c | ||
|
00bb3e6d4a | ||
|
0a21b5dca4 | ||
|
359d356dd9 | ||
|
0b87978830 | ||
|
d116fd7bff | ||
|
18279fdd3c | ||
|
c6fa14c0c8 | ||
|
506c83c160 | ||
|
81af295948 | ||
|
2592506288 | ||
|
b77d124152 | ||
|
ab041291fb | ||
|
247035cc9b | ||
|
71a30fe69f | ||
|
b11b9b909c | ||
|
a152e60040 | ||
|
c4735119c4 | ||
|
370742890c | ||
|
71960de636 | ||
|
cd9517e5fd | ||
|
6d10774fc8 | ||
|
daddfd3e0f | ||
|
476f04e27b | ||
|
22ebcd65f3 | ||
|
a54cd9958c | ||
|
b9c439bd00 | ||
|
a665926fe6 | ||
|
84f468ce41 | ||
|
3af0d479aa | ||
|
7a4c75ba44 | ||
|
7f3347981c | ||
|
76b5a5e43b | ||
|
8f9c532d8d | ||
|
981dc6ade9 | ||
|
8a52015422 | ||
|
fa3c99f550 | ||
|
4700eace15 | ||
|
9b8257a725 | ||
|
38e8c49223 | ||
|
5ac47fd6ff | ||
|
f7c97e776c | ||
|
4bebb3cbe4 | ||
|
973c32ff77 | ||
|
789eb59c9a | ||
|
f8682a65fa | ||
|
cbd278dd7e | ||
|
44d50c9005 | ||
|
73f179c887 |
2410 changed files with 338644 additions and 11634 deletions
27
.babelrc
27
.babelrc
|
@ -1,27 +0,0 @@
|
|||
{
|
||||
"env": {
|
||||
"production": {
|
||||
"presets": ["react", "react-optimize"]
|
||||
},
|
||||
"development": {
|
||||
"presets": ["react"]
|
||||
},
|
||||
"test": {
|
||||
"presets": ["react"]
|
||||
}
|
||||
},
|
||||
"only": [
|
||||
"test/js/**"
|
||||
],
|
||||
"plugins": [
|
||||
"transform-es2015-destructuring",
|
||||
"transform-es2015-parameters",
|
||||
"transform-es2015-modules-commonjs",
|
||||
"transform-async-to-generator",
|
||||
"transform-object-rest-spread",
|
||||
"transform-class-properties",
|
||||
"transform-runtime"
|
||||
],
|
||||
"sourceMaps": "inline",
|
||||
"retainLines": true
|
||||
}
|
3
.cargo/config
Normal file
3
.cargo/config
Normal file
|
@ -0,0 +1,3 @@
|
|||
[alias]
|
||||
cli = ["run", "--release", "-p", "mentat_cli"]
|
||||
debugcli = ["run", "-p", "mentat_cli"]
|
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
|
51
.gitignore
vendored
51
.gitignore
vendored
|
@ -1,9 +1,13 @@
|
|||
*.class
|
||||
*.DS_Store
|
||||
*.jar
|
||||
*jar
|
||||
*~
|
||||
**/*.rs.bk
|
||||
.s*
|
||||
.*.sw*
|
||||
*.rs.bak
|
||||
*.bak
|
||||
.hg/
|
||||
.hgignore
|
||||
.lein-deps-sum
|
||||
|
@ -11,13 +15,16 @@
|
|||
.lein-plugins/
|
||||
.lein-repl-history
|
||||
.nrepl-port
|
||||
.bundle/
|
||||
docs/vendor/
|
||||
/.lein-*
|
||||
/.nrepl-port
|
||||
Cargo.lock
|
||||
/checkouts/
|
||||
/classes/
|
||||
/node_modules/
|
||||
/out/
|
||||
/target/
|
||||
/target
|
||||
pom.xml
|
||||
pom.xml.asc
|
||||
/.cljs_node_repl/
|
||||
|
@ -45,3 +52,45 @@ pom.xml.asc
|
|||
/release-node/datomish/
|
||||
/release-node/goog/
|
||||
/release-node/honeysql/
|
||||
/edn/target/
|
||||
/fixtures/*.db-shm
|
||||
/fixtures/*.db-wal
|
||||
/query-parser/out/
|
||||
|
||||
## Build generated
|
||||
/sdks/swift/Mentat/build/
|
||||
/sdks/android/**/build
|
||||
DerivedData
|
||||
build.xcarchive
|
||||
|
||||
## Various settings
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
/sdks/swift/Mentat/*.xcodeproj/project.xcworkspace/xcuserdata
|
||||
|
||||
## Other
|
||||
*.xccheckout
|
||||
*.moved-aside
|
||||
*.xcuserstate
|
||||
*.xcscmblueprint
|
||||
|
||||
## Obj-C/Swift specific
|
||||
*.hmap
|
||||
*.ipa
|
||||
/sdks/swift/Mentat/External-Dependencies
|
||||
|
||||
# Android & IntelliJ
|
||||
**/*.iml
|
||||
**/.idea
|
||||
/sdks/android/**/local.properties
|
||||
|
||||
# Documentation
|
||||
docs/_site
|
||||
docs/.sass-cache
|
||||
docs/.jekyll-metadata
|
||||
|
|
1
.ignore
Normal file
1
.ignore
Normal file
|
@ -0,0 +1 @@
|
|||
docs/
|
80
.taskcluster.yml
Normal file
80
.taskcluster.yml
Normal file
|
@ -0,0 +1,80 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
version: 0
|
||||
allowPullRequests: public
|
||||
tasks:
|
||||
####################################################################################################
|
||||
# Task: Pull requests
|
||||
####################################################################################################
|
||||
- provisionerId: '{{ taskcluster.docker.provisionerId }}'
|
||||
workerType: '{{ taskcluster.docker.workerType }}'
|
||||
extra:
|
||||
github:
|
||||
env: true
|
||||
events:
|
||||
- pull_request.opened
|
||||
- pull_request.edited
|
||||
- pull_request.synchronize
|
||||
- pull_request.reopened
|
||||
- push
|
||||
scopes:
|
||||
- "queue:create-task:aws-provisioner-v1/github-worker"
|
||||
- "queue:scheduler-id:taskcluster-github"
|
||||
payload:
|
||||
maxRunTime: 3600
|
||||
deadline: "{{ '2 hours' | $fromNow }}"
|
||||
image: 'mozillamobile/android-components:1.4'
|
||||
command:
|
||||
- /bin/bash
|
||||
- '--login'
|
||||
- '-cx'
|
||||
- >-
|
||||
export TERM=dumb
|
||||
&& git fetch {{ event.head.repo.url }} {{ event.head.repo.branch }}
|
||||
&& git config advice.detachedHead false
|
||||
&& git checkout {{event.head.sha}}
|
||||
&& python automation/taskcluster/decision_task_pull_request.py
|
||||
features:
|
||||
taskclusterProxy: true
|
||||
metadata:
|
||||
name: Mentat Android SDK - Pull Request
|
||||
description: Building and testing the Mentat Android SDK - triggered by a pull request.
|
||||
owner: '{{ event.head.user.email }}'
|
||||
source: '{{ event.head.repo.url }}'
|
||||
####################################################################################################
|
||||
# Task: Release
|
||||
####################################################################################################
|
||||
- provisionerId: '{{ taskcluster.docker.provisionerId }}'
|
||||
workerType: '{{ taskcluster.docker.workerType }}'
|
||||
extra:
|
||||
github:
|
||||
events:
|
||||
- release
|
||||
scopes:
|
||||
- "secrets:get:project/mentat/publish"
|
||||
payload:
|
||||
maxRunTime: 3600
|
||||
deadline: "{{ '2 hours' | $fromNow }}"
|
||||
image: 'mozillamobile/mentat:1.2'
|
||||
command:
|
||||
- /bin/bash
|
||||
- '--login'
|
||||
- '-cx'
|
||||
- >-
|
||||
export TERM=dumb
|
||||
&& git fetch origin --tags
|
||||
&& git config advice.detachedHead false
|
||||
&& git checkout {{ event.version }}
|
||||
&& python automation/taskcluster/release/fetch-bintray-api-key.py
|
||||
&& cd sdks/android/Mentat
|
||||
&& ./gradlew --no-daemon clean library:assembleRelease
|
||||
&& VCS_TAG=`git show-ref {{ event.version }}` ./gradlew bintrayUpload --debug -PvcsTag="$VCS_TAG"
|
||||
features:
|
||||
taskclusterProxy: true
|
||||
metadata:
|
||||
name: Mentat Android SDK - Release ({{ event.version }})
|
||||
description: Building and publishing release versions.
|
||||
owner: '{{ event.head.user.email }}'
|
||||
source: '{{ event.head.repo.url }}'
|
90
.travis.yml
90
.travis.yml
|
@ -1,14 +1,78 @@
|
|||
language: clojure
|
||||
lein: lein2
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
lein: 2.7.0
|
||||
clojure: 1.8.0
|
||||
language: rust
|
||||
env:
|
||||
- TRAVIS_NODE_VERSION="6.2.2"
|
||||
install:
|
||||
- nvm install $TRAVIS_NODE_VERSION
|
||||
- nvm use $TRAVIS_NODE_VERSION
|
||||
- lein deps
|
||||
- npm install
|
||||
script: lein cljsbuild once release-node && lein with-profile node install && lein test
|
||||
- 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.
|
||||
# (The version in Travis's default Ubuntu Trusty is much too old).
|
||||
os: osx
|
||||
before_install:
|
||||
- brew install sqlcipher
|
||||
rust:
|
||||
- 1.43.0
|
||||
- 1.44.0
|
||||
- 1.45.0
|
||||
- 1.46.0
|
||||
- 1.47.0
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rust: nightly
|
||||
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:
|
||||
- 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 --features edn/serde_support --verbose --all
|
||||
# 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
|
||||
# this all-together, see https://github.com/rust-lang/cargo/issues/5364 for more information). To
|
||||
# work around this, we run tests individually for sub-crates that rely on `rusqlite`.
|
||||
- |
|
||||
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 ./$crate/Cargo.toml --verbose --no-default-features --features sqlcipher
|
||||
done
|
||||
|
|
25
.vscode/launch.json
vendored
Normal file
25
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "attach",
|
||||
"name": "Attach to CLI (debug)",
|
||||
"program": "${workspaceFolder}/target/debug/mentat_cli"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "CLI",
|
||||
"stdio": "*",
|
||||
"program": "${workspaceFolder}/target/debug/mentat_cli",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"terminal": "integrated"
|
||||
},
|
||||
]
|
||||
}
|
30
.vscode/settings.json
vendored
Normal file
30
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Place your settings in this file to overwrite default and user settings.
|
||||
{
|
||||
// Newline at EOF.
|
||||
"files.insertFinalNewline": true,
|
||||
|
||||
// Configure glob patterns for excluding files and folders.
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
"**/.hg": true,
|
||||
"**/CVS": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/.*.sw*": true, // Vim swap files.
|
||||
"**/*~": true, // Vim backup files.
|
||||
".cljs_*_repl": true,
|
||||
".gitignore": true,
|
||||
".lein*": true,
|
||||
".sw*": true,
|
||||
"target": true, // Top-level build output.
|
||||
"*/target": true // Sub-crate build output.
|
||||
},
|
||||
"rust.customTestConfigurations": [
|
||||
{
|
||||
"title": "All Crates",
|
||||
"args": [
|
||||
"--all"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
58
.vscode/tasks.json
vendored
Normal file
58
.vscode/tasks.json
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "shell",
|
||||
"label": "Test all",
|
||||
"command": "cargo",
|
||||
"args": [
|
||||
"test",
|
||||
"--all",
|
||||
],
|
||||
"problemMatcher": [
|
||||
"$rustc"
|
||||
],
|
||||
"group": "test"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"label": "Run CLI",
|
||||
"command": "cargo",
|
||||
"args": [
|
||||
"debugcli",
|
||||
],
|
||||
"problemMatcher": [
|
||||
"$rustc"
|
||||
],
|
||||
"group": "test"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"label": "Build CLI",
|
||||
"command": "cargo",
|
||||
"args": [
|
||||
"build",
|
||||
"-p",
|
||||
"mentat_cli",
|
||||
],
|
||||
"problemMatcher": [
|
||||
"$rustc"
|
||||
],
|
||||
"group": "build"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"label": "Build Mentat",
|
||||
"command": "cargo",
|
||||
"args": [
|
||||
"build",
|
||||
],
|
||||
"problemMatcher": [
|
||||
"$rustc"
|
||||
],
|
||||
"group": "build"
|
||||
}
|
||||
]
|
||||
}
|
39
CHANGELOG.md
Normal file
39
CHANGELOG.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
# 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)
|
||||
|
||||
* sdks/android compiled against:
|
||||
* Kotlin standard library 1.2.41
|
||||
|
||||
* **sdks/android**: `Mentat()` constructor replaced with `open` factory method.
|
||||
|
||||
* [Commits](https://github.com/mozilla/mentat/compare/v0.10.0...v0.11.0)
|
||||
|
||||
# 0.10 (2018-07-26)
|
||||
|
||||
* sdks/android compiled against:
|
||||
* Kotlin standard library 1.2.41
|
||||
|
||||
* **API changes**:
|
||||
* `store_open{_encrypted}` now accepts an error parameter; corresponding constructors changed to be factory functions.
|
||||
|
||||
* [Commits](https://github.com/mozilla/mentat/compare/v0.9.0...v0.10.0)
|
||||
|
||||
# 0.9 (2018-07-25)
|
||||
|
||||
* sdks/android compiled against:
|
||||
* Kotlin standard library 1.2.41
|
||||
|
||||
* **API changes**:
|
||||
* Mentat partitions now enforce their integrity, denying entids that aren't already known.
|
||||
|
||||
* **sdks/android**: First version published to nalexander's personal bintray repository.
|
||||
* Various bugfixes and refactorings (see commits below for details)
|
||||
* [Commits](https://github.com/mozilla/mentat/compare/v0.8.1...v0.9.0)
|
118
Cargo.toml
Normal file
118
Cargo.toml
Normal file
|
@ -0,0 +1,118 @@
|
|||
[package]
|
||||
edition = "2021"
|
||||
authors = [
|
||||
"Richard Newman <rnewman@twinql.com>",
|
||||
"Nicholas Alexander <nalexander@mozilla.com>",
|
||||
"Victor Porof <vporof@mozilla.com>",
|
||||
"Jordan Santell <jsantell@mozilla.com>",
|
||||
"Joe Walker <jwalker@mozilla.com>",
|
||||
"Emily Toop <etoop@mozilla.com>",
|
||||
"Grisha Kruglov <grigory@kruglov.ca>",
|
||||
"Kit Cambridge <kit@yakshaving.ninja>",
|
||||
"Edouard Oger <eoger@fastmail.com>",
|
||||
"Thom Chiovoloni <tchiovoloni@mozilla.com>",
|
||||
"Gregory Burd <greg@burd.me>",
|
||||
]
|
||||
name = "mentat"
|
||||
version = "0.14.0"
|
||||
build = "build/version.rs"
|
||||
|
||||
[features]
|
||||
default = ["bundled_sqlite3", "syncable"]
|
||||
bundled_sqlite3 = ["rusqlite/bundled"]
|
||||
sqlcipher = ["rusqlite/sqlcipher", "mentat_db/sqlcipher"]
|
||||
syncable = ["mentat_tolstoy", "tolstoy_traits", "mentat_db/syncable"]
|
||||
|
||||
[workspace]
|
||||
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]
|
||||
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]
|
||||
chrono = "~0.4"
|
||||
failure = "~0.1"
|
||||
lazy_static = "~1.4"
|
||||
time = "0.3.1"
|
||||
log = "~0.4"
|
||||
uuid = { version = "~1", features = ["v4", "serde"] }
|
||||
|
||||
[dependencies.rusqlite]
|
||||
version = "~0.29"
|
||||
features = ["limits", "bundled"]
|
||||
|
||||
[dependencies.edn]
|
||||
path = "edn"
|
||||
|
||||
[dependencies.core_traits]
|
||||
path = "core-traits"
|
||||
|
||||
[dependencies.mentat_core]
|
||||
path = "core"
|
||||
|
||||
[dependencies.mentat_sql]
|
||||
path = "sql"
|
||||
|
||||
[dependencies.mentat_db]
|
||||
path = "db"
|
||||
|
||||
[dependencies.db_traits]
|
||||
path = "db-traits"
|
||||
|
||||
[dependencies.mentat_query_algebrizer]
|
||||
path = "query-algebrizer"
|
||||
|
||||
[dependencies.query_algebrizer_traits]
|
||||
path = "query-algebrizer-traits"
|
||||
|
||||
[dependencies.mentat_query_projector]
|
||||
path = "query-projector"
|
||||
|
||||
[dependencies.query_projector_traits]
|
||||
path = "query-projector-traits"
|
||||
|
||||
[dependencies.mentat_query_pull]
|
||||
path = "query-pull"
|
||||
|
||||
[dependencies.query_pull_traits]
|
||||
path = "query-pull-traits"
|
||||
|
||||
[dependencies.mentat_query_sql]
|
||||
path = "query-sql"
|
||||
|
||||
[dependencies.sql_traits]
|
||||
path = "sql-traits"
|
||||
|
||||
[dependencies.public_traits]
|
||||
path = "public-traits"
|
||||
|
||||
[dependencies.mentat_transaction]
|
||||
path = "transaction"
|
||||
|
||||
[dependencies.mentat_tolstoy]
|
||||
path = "tolstoy"
|
||||
optional = true
|
||||
|
||||
[dependencies.tolstoy_traits]
|
||||
path = "tolstoy-traits"
|
||||
optional = true
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
debug = false
|
||||
lto = true
|
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/
|
317
README.md
317
README.md
|
@ -1,38 +1,93 @@
|
|||
# Datomish
|
||||
# Project Mentat
|
||||
[![Build Status](https://travis-ci.org/qpdb/mentat.svg?branch=master)](https://travis-ci.org/qpdb/mentat)
|
||||
|
||||
Datomish is a persistent, embedded knowledge base. It's written in ClojureScript, and 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).
|
||||
|
||||
Datomish compiles into a single JavaScript file, and is usable both in Node (on top of `promise_sqlite`) and in Firefox (on top of `Sqlite.jsm`). It also works in pure Clojure on the JVM on top of `jdbc-sqlite`.
|
||||
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.
|
||||
|
||||
There's an example Firefox restartless add-on in the [`addon`](https://github.com/mozilla/datomish/tree/master/addon) directory; build instructions are below.
|
||||
*Thank you*.
|
||||
|
||||
[Documentation](https://docs.rs/mentat)
|
||||
|
||||
---
|
||||
|
||||
## Motivation
|
||||
|
||||
Datomish is intended to be a flexible relational (not key-value, not document-oriented) store that doesn't leak its storage schema to users, and doesn't make it hard to grow its domain schema and run arbitrary queries.
|
||||
Mentat is a flexible relational (not key-value, not document-oriented) store that makes it easy to describe, grow, and reuse your domain schema.
|
||||
|
||||
Our short-term goal is to build a system that, as the basis for a User Agent Service, can support multiple [Tofino](https://github.com/mozilla/tofino) UX experiments without having a storage engineer do significant data migration, schema work, or revving of special-purpose endpoints.
|
||||
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 allow both the data store itself and embedding applications to use better architectures, meeting performance goals in a way that allows future evolution.
|
||||
## Data storage is hard
|
||||
|
||||
We've observed that data storage is a particular area of difficulty for software development teams:
|
||||
|
||||
- It's hard to define storage schemas well. A developer must:
|
||||
- Model their domain entities and relationships.
|
||||
- Encode that model _efficiently_ and _correctly_ using the features available in the database.
|
||||
- Plan for future extensions and performance tuning.
|
||||
|
||||
In a SQL database, the same schema definition defines everything from high-level domain relationships through to numeric field sizes in the same smear of keywords. It's difficult for someone unfamiliar with the domain to determine from such a schema what's a domain fact and what's an implementation concession — are all part numbers always 16 characters long, or are we trying to save space? — or, indeed, whether a missing constraint is deliberate or a bug.
|
||||
|
||||
The developer must think about foreign key constraints, compound uniqueness, and nullability. They must consider indexing, synchronizing, and stable identifiers. Most developers simply don't do enough work in SQL to get all of these things right. Storage thus becomes the specialty of a few individuals.
|
||||
|
||||
Which one of these is correct?
|
||||
|
||||
```edn
|
||||
{:db/id :person/email
|
||||
:db/valueType :db.type/string
|
||||
:db/cardinality :db.cardinality/many ; People can have multiple email addresses.
|
||||
:db/unique :db.unique/identity ; For our purposes, each email identifies one person.
|
||||
:db/index true} ; We want fast lookups by email.
|
||||
{:db/id :person/friend
|
||||
:db/valueType :db.type/ref
|
||||
:db/cardinality :db.cardinality/many} ; People can have many friends.
|
||||
```
|
||||
```sql
|
||||
CREATE TABLE people (
|
||||
id INTEGER PRIMARY KEY, -- Bug: because of the primary key, each person can have no more than 1 email.
|
||||
email VARCHAR(64), -- Bug?: no NOT NULL, so a person can have no email.
|
||||
-- Bug: nobody will ever have a long email address, right?
|
||||
);
|
||||
CREATE TABLE friendships (
|
||||
FOREIGN KEY person REFERENCES people(id), -- Bug?: no indexing, so lookups by friend or person will be slow.
|
||||
FOREIGN KEY friend REFERENCES people(id), -- Bug: no compound uniqueness constraint, so we can have dupe friendships.
|
||||
);
|
||||
```
|
||||
|
||||
They both have limitations — the Mentat schema allows only for an open world (it's possible to declare friendships with people whose email isn't known), and requires validation code to enforce email string correctness — but we think that even such a tiny SQL example is harder to understand and obscures important domain decisions.
|
||||
|
||||
- Queries are intimately tied to structural storage choices. That not only hides the declarative domain-level meaning of the query — it's hard to tell what a query is trying to do when it's a 100-line mess of subqueries and `LEFT OUTER JOIN`s — but it also means a simple structural schema change requires auditing _every query_ for correctness.
|
||||
|
||||
- Developers often capture less event-shaped than they perhaps should, simply because their initial requirements don't warrant it. It's quite common to later want to [know when a fact was recorded](https://bugzilla.mozilla.org/show_bug.cgi?id=1341939), or _in which order_ two facts were recorded (particularly for migrations), or on which device an event took place… or even that a fact was _ever_ recorded and then deleted.
|
||||
|
||||
- Common queries are hard. Storing values only once, upserts, complicated joins, and group-wise maxima are all difficult for non-expert developers to get right.
|
||||
|
||||
- It's hard to evolve storage schemas. Writing a robust SQL schema migration is hard, particularly if a bad migration has ever escaped into the wild! Teams learn to fear and avoid schema changes, and eventually they ship a table called `metadata`, with three `TEXT` columns, so they never have to write a migration again. That decision pushes storage complexity into application code. (Or they start storing unversioned JSON blobs in the database…)
|
||||
|
||||
- It's hard to share storage with another component, let alone share _data_ with another component. Conway's Law applies: your software system will often grow to have one database per team.
|
||||
|
||||
- It's hard to build efficient storage and querying architectures. Materialized views require knowledge of triggers, or the implementation of bottleneck APIs. _Ad hoc_ caches are often wrong, are almost never formally designed (do you want a write-back, write-through, or write-around cache? Do you know the difference?), and often aren't reusable. The average developer, faced with a SQL database, has little choice but to build a simple table that tries to meet every need.
|
||||
|
||||
|
||||
## Comparison to DataScript
|
||||
|
||||
DataScript asks the question: "What if creating a database would be as cheap as creating a Hashmap?"
|
||||
DataScript asks the question: "What if creating a database were as cheap as creating a Hashmap?"
|
||||
|
||||
Datomish 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 Datomish'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/).)
|
||||
|
||||
Another possible question would be: "What if we could bake some of the concepts of CQRS and event sourcing into a persistent relational store, such that the transaction log itself were of value to queries?"
|
||||
Another possible question would be: "What if we could bake some of the concepts of [CQRS and event sourcing](http://www.baeldung.com/cqrs-event-sourced-architecture-resources) into a persistent relational store, such that the transaction log itself were of value to queries?"
|
||||
|
||||
Some thought has been given to how databases as values — long-term references to a snapshot of the store at an instant in time — could work in this model. It's not impossible; it simply has different performance characteristics.
|
||||
|
||||
Just like DataScript, Datomish speaks Datalog for querying and takes additions and retractions as input to a transaction. Unlike DataScript, Datomish's API is asynchronous.
|
||||
Just like DataScript, Mentat speaks Datalog for querying and takes additions and retractions as input to a transaction.
|
||||
|
||||
Unlike DataScript, Datomish exposes free-text indexing, thanks to SQLite.
|
||||
Unlike DataScript, Mentat exposes free-text indexing, thanks to SQLite/FTS.
|
||||
|
||||
|
||||
## Comparison to Datomic
|
||||
|
@ -41,159 +96,145 @@ 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.
|
||||
|
||||
Datomish is designed for embedding, initially in an 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
|
||||
|
||||
SQLite is a traditional SQL database in most respects: schemas conflate semantic, structural, and datatype concerns; the main interface with the database is human-first textual queries; sparse and graph-structured data are 'unnatural', if not always inefficient; experimenting with and evolving data models are error-prone and complicated activities; and so on.
|
||||
SQLite is a traditional SQL database in most respects: schemas conflate semantic, structural, and datatype concerns, as described above; the main interface with the database is human-first textual queries; sparse and graph-structured data are 'unnatural', if not always inefficient; experimenting with and evolving data models are error-prone and complicated activities; and so on.
|
||||
|
||||
Datomish aims to offer many of the advantages of SQLite — single-file use, embeddability, and good performance — while building a more relaxed and expressive data model on top.
|
||||
Mentat aims to offer many of the advantages of SQLite — single-file use, embeddability, and good performance — while building a more relaxed, reusable, and expressive data model on top.
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
Please note that this project is released with a Contributor Code of Conduct.
|
||||
By participating in this project you agree to abide by its terms.
|
||||
|
||||
See [CONTRIBUTING.md](/CONTRIBUTING.md) for further notes.
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for further notes.
|
||||
|
||||
This project is very new, so we'll probably revise these guidelines. Please
|
||||
comment on an issue before putting significant effort in if you'd like to
|
||||
contribute.
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
## Building
|
||||
|
||||
Datomish is currently licensed under the Apache License v2.0. See the `LICENSE` file for details.
|
||||
You first need to clone the project. To build and test the project, we are using [Cargo](https://crates.io/install).
|
||||
|
||||
Datomish includes some code from DataScript, kindly relicensed by Nikita Prokopov.
|
||||
To build all of the crates in the project use:
|
||||
|
||||
````
|
||||
cargo build
|
||||
````
|
||||
|
||||
To run tests use:
|
||||
|
||||
````
|
||||
# Run tests for everything.
|
||||
cargo test --all
|
||||
|
||||
# Run tests for just the query-algebrizer folder (specify the crate, not the folder),
|
||||
# printing debug output.
|
||||
cargo test -p mentat_query_algebrizer -- --nocapture
|
||||
````
|
||||
|
||||
For most `cargo` commands you can pass the `-p` argument to run the command just on that package. So, `cargo build -p mentat_query_algebrizer` will build just the "query-algebrizer" folder.
|
||||
|
||||
## What are all of these crates?
|
||||
|
||||
We use multiple sub-crates for Mentat for four reasons:
|
||||
|
||||
1. To improve incremental build times.
|
||||
2. To encourage encapsulation; writing `extern crate` feels worse than just `use mod`.
|
||||
3. To simplify the creation of targets that don't use certain features: _e.g._, a build with no syncing, or with no query system.
|
||||
4. To allow for reuse (_e.g._, the EDN parser is essentially a separate library).
|
||||
|
||||
So what are they?
|
||||
|
||||
### Building blocks
|
||||
|
||||
#### `edn`
|
||||
|
||||
Our EDN parser. It uses `rust-peg` to parse [EDN](https://github.com/edn-format/edn), which is Clojure/Datomic's richer alternative to JSON. `edn`'s dependencies are all either for representing rich values (`chrono`, `uuid`, `ordered-float`) or for parsing (`serde`, `peg`).
|
||||
|
||||
In addition, this crate turns a stream of EDN values into a representation suitable to be transacted.
|
||||
|
||||
#### `mentat_core`
|
||||
|
||||
This is the lowest-level Mentat crate. It collects together the following things:
|
||||
|
||||
- Fundamental domain-specific data structures like `ValueType` and `TypedValue`.
|
||||
- Fundamental SQL-related linkages like `SQLValueType`. These encode the mapping between Mentat's types and values and their representation in our SQLite format.
|
||||
- Conversion to and from EDN types (_e.g._, `edn::Keyword` to `TypedValue::Keyword`).
|
||||
- Common utilities (some in the `util` module, and others that should be moved there or broken out) like `Either`, `InternSet`, and `RcCounter`.
|
||||
- Reusable lazy namespaced keywords (_e.g._, `DB_TYPE_DOUBLE`) that are used by `mentat_db` and EDN serialization of core structs.
|
||||
|
||||
### Types
|
||||
|
||||
#### `mentat_query`
|
||||
|
||||
This crate defines the structs and enums that are the output of the query parser and used by the translator and algebrizer. `SrcVar`, `NonIntegerConstant`, `FnArg`… these all live here.
|
||||
|
||||
#### `mentat_query_sql`
|
||||
|
||||
Similarly, this crate defines an abstract representation of a SQL query as understood by Mentat. This bridges between Mentat's types (_e.g._, `TypedValue`) and SQL concepts (`ColumnOrExpression`, `GroupBy`). It's produced by the algebrizer and consumed by the translator.
|
||||
|
||||
### Query processing
|
||||
|
||||
#### `mentat_query_algebrizer`
|
||||
|
||||
This is the biggest piece of the query engine. It takes a parsed query, which at this point is _independent of a database_, and combines it with the current state of the schema and data. This involves translating keywords into attributes, abstract values into concrete values with a known type, and producing an `AlgebraicQuery`, which is a representation of how a query's Datalog semantics can be satisfied as SQL table joins and constraints over Mentat's SQL schema. An algebrized query is tightly coupled with both the disk schema and the vocabulary present in the store when the work is done.
|
||||
|
||||
#### `mentat_query_projector`
|
||||
|
||||
A Datalog query _projects_ some of the variables in the query into data structures in the output. This crate takes an algebrized query and a projection list and figures out how to get values out of the running SQL query and into the right format for the consumer.
|
||||
|
||||
#### `mentat_query_translator`
|
||||
|
||||
This crate works with all of the above to turn the output of the algebrizer and projector into the data structures defined in `mentat_query_sql`.
|
||||
|
||||
#### `mentat_sql`
|
||||
|
||||
This simple crate turns those data structures into SQL text and bindings that can later be executed by `rusqlite`.
|
||||
|
||||
### The data layer: `mentat_db`
|
||||
|
||||
This is a big one: it implements the core storage logic on top of SQLite. This crate is responsible for bootstrapping new databases, transacting new data, maintaining the attribute cache, and building and updating in-memory representations of the storage schema.
|
||||
|
||||
### The main crate
|
||||
|
||||
The top-level main crate of Mentat assembles these component crates into something useful. It wraps up a connection to a database file and the associated metadata into a `Store`, and encapsulates an in-progress transaction (`InProgress`). It provides modules for programmatically writing (`entity_builder.rs`) and managing vocabulary (`vocabulary.rs`).
|
||||
|
||||
### 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; 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
|
||||
|
||||
This is under `tools/cli`. It's essentially an external consumer of the main `mentat` crate. This code is ugly, but it mostly works.
|
||||
|
||||
---
|
||||
|
||||
## SQLite dependencies
|
||||
|
||||
Datomish uses partial indices, which are available in SQLite 3.8.0 and higher.
|
||||
Mentat uses partial indices, which are available in SQLite 3.8.0 and higher. It relies on correlation between aggregate and non-aggregate columns in the output, which was added in SQLite 3.7.11.
|
||||
|
||||
It also uses FTS4, which is [a compile time option](http://www.sqlite.org/fts3.html#section_2).
|
||||
|
||||
By default, Mentat specifies the `"bundled"` feature for `rusqlite`, which uses a relatively recent
|
||||
version of SQLite. If you want to link against the system version of SQLite, omit `"bundled_sqlite3"`
|
||||
from Mentat's features.
|
||||
|
||||
## Prep
|
||||
|
||||
You'll need [Leiningen](http://leiningen.org).
|
||||
|
||||
```
|
||||
# If you use nvm.
|
||||
nvm use 6
|
||||
|
||||
lein deps
|
||||
npm install
|
||||
|
||||
# If you want a decent REPL.
|
||||
brew install rlwrap
|
||||
```toml
|
||||
[dependencies.mentat]
|
||||
version = "0.6"
|
||||
# System sqlite is known to be new.
|
||||
default-features = false
|
||||
```
|
||||
|
||||
## Running a REPL
|
||||
---
|
||||
|
||||
### Starting a ClojureScript REPL from the terminal
|
||||
## License
|
||||
|
||||
```
|
||||
rlwrap lein run -m clojure.main repl.clj
|
||||
```
|
||||
|
||||
### Connecting to a ClojureScript environment from Vim
|
||||
|
||||
You'll need `vim-fireplace`. Install using Pathogen.
|
||||
|
||||
First, start a Clojure REPL with an nREPL server. Then load our ClojureScript REPL and dependencies. Finally, connect to it from Vim.
|
||||
|
||||
```
|
||||
$ lein repl
|
||||
nREPL server started on port 62385 on host 127.0.0.1 - nrepl://127.0.0.1:62385
|
||||
REPL-y 0.3.7, nREPL 0.2.10
|
||||
Clojure 1.8.0
|
||||
Java HotSpot(TM) 64-Bit Server VM 1.8.0_60-b27
|
||||
Docs: (doc function-name-here)
|
||||
(find-doc "part-of-name-here")
|
||||
Source: (source function-name-here)
|
||||
Javadoc: (javadoc java-object-or-class-here)
|
||||
Exit: Control+D or (exit) or (quit)
|
||||
Results: Stored in vars *1, *2, *3, an exception in *e
|
||||
|
||||
user=> (load-file "repl.clj")
|
||||
Reading analysis cache for jar:file:/Users/rnewman/.m2/repository/org/clojure/clojurescript/1.9.89/clojurescript-1.9.89.jar!/cljs/core.cljs
|
||||
Compiling out/cljs/nodejs.cljs
|
||||
Compiling src/datomish/sqlite.cljs
|
||||
Compiling src/datomish/core.cljs
|
||||
ClojureScript Node.js REPL server listening on 57134
|
||||
Watch compilation log available at: out/watch.log
|
||||
To quit, type: :cljs/quit
|
||||
cljs.user=>
|
||||
```
|
||||
|
||||
in Vim, in the working directory:
|
||||
|
||||
```
|
||||
:Piggieback (cljs.repl.node/repl-env)
|
||||
```
|
||||
|
||||
Now you can use `:Eval`, `cqc`, and friends to evaluate code. Fireplace should connect automatically, but if it doesn't:
|
||||
|
||||
```
|
||||
:Connect nrepl://localhost:62385
|
||||
```
|
||||
|
||||
## To run tests in ClojureScript
|
||||
|
||||
Run `lein doo node test once`, or `lein doo node test auto` to re-run on file changes.
|
||||
|
||||
## To run tests in Clojure
|
||||
|
||||
Run `lein test`.
|
||||
|
||||
## To run smoketests with the built release library in a Node environment
|
||||
|
||||
```
|
||||
# Build.
|
||||
lein cljsbuild once release-node
|
||||
npm run test
|
||||
```
|
||||
|
||||
## To build for a Firefox add-on
|
||||
|
||||
```
|
||||
lein cljsbuild once release-browser
|
||||
```
|
||||
|
||||
### To build and run the example Firefox add-on:
|
||||
|
||||
First build as above, so that `datomish.js` exists.
|
||||
|
||||
Then:
|
||||
|
||||
```
|
||||
cd addon
|
||||
./build.sh
|
||||
cd release
|
||||
./run.sh
|
||||
```
|
||||
|
||||
## Preparing an NPM release
|
||||
|
||||
The intention is that the `target/release-node/` directory is roughly the shape of an npm-ready JavaScript package.
|
||||
|
||||
To generate a require/import-ready `target/release-node/datomish.js`, run
|
||||
```
|
||||
lein cljsbuild once release-node
|
||||
```
|
||||
To verify that importing into Node.js succeeds, run
|
||||
```
|
||||
npm run test
|
||||
```
|
||||
|
||||
## To locally install for ClojureScript use
|
||||
|
||||
```
|
||||
lein with-profile node install
|
||||
```
|
||||
|
||||
Many thanks to ([David Nolen](https://github.com/swannodette)) and ([Nikita Prokopov](https://github.com/tonsky)) for demonstrating how to package ClojureScript for distribution via npm.
|
||||
Project Mentat is currently licensed under the Apache License v2.0. See the `LICENSE` file for details.
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"presets": ["es2015"],
|
||||
"plugins": [
|
||||
"transform-async-to-generator"
|
||||
]
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
Icon file is "Line Graph" by Cris Dobbins, from The Noun Project.
|
||||
|
||||
https://thenounproject.com/term/line-graph/145324/
|
|
@ -1,3 +0,0 @@
|
|||
cp ../target/release-browser/datomish.js release/
|
||||
node_modules/.bin/webpack -p
|
||||
cat src/wrapper.prefix built/index.js > release/index.js
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"name": "datomish-example",
|
||||
"version": "1.0.0",
|
||||
"description": "A test add-on for Datomish and Firefox.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MPL-2.0",
|
||||
"devDependencies": {
|
||||
"babel": "^6.5.2",
|
||||
"babel-cli": "^6.14.0",
|
||||
"babel-core": "^6.14.0",
|
||||
"babel-loader": "^6.2.5",
|
||||
"babel-plugin-transform-async-to-generator": "^6.8.0",
|
||||
"babel-preset-es2015": "^6.14.0",
|
||||
"webpack": "^1.13.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-polyfill": "^6.13.0"
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
#Datomish Test
|
||||
An example add-on that loads Datomish on top of Sqlite.jsm.
|
Binary file not shown.
Before Width: | Height: | Size: 1.1 KiB |
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"title": "Datomish Test",
|
||||
"name": "datomish-test",
|
||||
"version": "0.0.1",
|
||||
"description": "An example add-on that loads Datomish on top of Sqlite.jsm.",
|
||||
"main": "index.js",
|
||||
"author": "Richard Newman <rnewman@mozilla.com>",
|
||||
"engines": {
|
||||
"firefox": ">=48.0a1"
|
||||
},
|
||||
"license": "MPL-2.0",
|
||||
"keywords": [
|
||||
"jetpack"
|
||||
]
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
jpm run -b /Applications/FirefoxNightly.app/
|
|
@ -1,92 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
var self = require("sdk/self");
|
||||
var buttons = require('sdk/ui/button/action');
|
||||
var tabs = require('sdk/tabs');
|
||||
|
||||
var datomish = require("datomish.js");
|
||||
|
||||
var schema = {
|
||||
"name": "pages",
|
||||
"attributes": [
|
||||
{"name": "page/url",
|
||||
"type": "string",
|
||||
"cardinality": "one",
|
||||
"unique": "identity",
|
||||
"doc": "A page's URL."},
|
||||
{"name": "page/title",
|
||||
"type": "string",
|
||||
"cardinality": "one",
|
||||
"fulltext": true,
|
||||
"doc": "A page's title."},
|
||||
{"name": "page/content",
|
||||
"type": "string",
|
||||
"cardinality": "one", // Simple for now.
|
||||
"fulltext": true,
|
||||
"doc": "A snapshot of the page's content. Should be plain text."},
|
||||
]
|
||||
};
|
||||
|
||||
async function initDB(path) {
|
||||
let db = await datomish.open(path);
|
||||
await db.ensureSchema(schema);
|
||||
return db;
|
||||
}
|
||||
|
||||
async function findURLs(db) {
|
||||
let query = `[:find ?page ?url ?title :in $ :where [?page :page/url ?url][(get-else $ ?page :page/title "") ?title]]`;
|
||||
let options = new Object();
|
||||
options["limit"] = 10;
|
||||
return datomish.q(db.db(), query, options);
|
||||
}
|
||||
|
||||
async function findPagesMatching(db, string) {
|
||||
let query =
|
||||
`[:find ?url ?title
|
||||
:in $ ?str
|
||||
:where
|
||||
[(fulltext $ :any ?str) [[?page]]]
|
||||
[?page :page/url ?url]
|
||||
[(get-else $ ?page :page/title "") ?title]]`;
|
||||
return datomish.q(db.db(), query, {"limit": 10, "inputs": {"str": string}});
|
||||
}
|
||||
|
||||
async function savePage(db, url, title, content) {
|
||||
let datom = {"db/id": 55, "page/url": url};
|
||||
if (title) {
|
||||
datom["page/title"] = title;
|
||||
}
|
||||
if (content) {
|
||||
datom["page/content"] = content;
|
||||
}
|
||||
let txResult = await db.transact([datom]);
|
||||
return txResult;
|
||||
}
|
||||
|
||||
async function handleClick(state) {
|
||||
let db = await datomish.open("/tmp/testing.db");
|
||||
await db.ensureSchema(schema);
|
||||
|
||||
let txResult = await savePage(db, tabs.activeTab.url, tabs.activeTab.title, "Content goes here");
|
||||
|
||||
console.log("Transaction returned " + JSON.stringify(txResult));
|
||||
console.log("Transaction instant: " + txResult.txInstant);
|
||||
|
||||
let results = await findURLs(db);
|
||||
results = results.map(r => r[1]);
|
||||
|
||||
console.log("Query results: " + JSON.stringify(results));
|
||||
|
||||
let pages = await findPagesMatching(db, "goes");
|
||||
|
||||
console.log("Pages: " + JSON.stringify(pages));
|
||||
await db.close();
|
||||
}
|
||||
|
||||
var button = buttons.ActionButton({
|
||||
id: "datomish-save",
|
||||
label: "Save Page",
|
||||
icon: "./datomish-48.png",
|
||||
onClick: handleClick
|
||||
});
|
|
@ -1,4 +0,0 @@
|
|||
// Monkeypatch.
|
||||
var { setTimeout } = require("sdk/timers");
|
||||
this.setTimeout = setTimeout;
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
|
||||
module.exports = {
|
||||
entry: ['babel-polyfill', './src/index.js'],
|
||||
output: {
|
||||
filename: 'built/index.js'
|
||||
},
|
||||
target: 'webworker',
|
||||
externals: {
|
||||
'datomish.js': 'commonjs datomish.js',
|
||||
'sdk/self': 'commonjs sdk/self',
|
||||
'sdk/ui/button/action': 'commonjs sdk/ui/button/action',
|
||||
'sdk/tabs': 'commonjs sdk/tabs'
|
||||
},
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /\.js?$/,
|
||||
exclude: /(node_modules)|(wrapper.prefix)/,
|
||||
loader: 'babel'
|
||||
}]
|
||||
}
|
||||
}
|
36
automation/docker/Dockerfile
Normal file
36
automation/docker/Dockerfile
Normal file
|
@ -0,0 +1,36 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
FROM mozillamobile/rust-component:buildtools-27.0.3-ndk-r17b-ndk-version-26-rust-stable-rust-beta
|
||||
|
||||
MAINTAINER Nick Alexander "nalexander@mozilla.com"
|
||||
|
||||
#----------------------------------------------------------------------------------------------------------------------
|
||||
#-- Project -----------------------------------------------------------------------------------------------------------
|
||||
#----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
ENV PROJECT_REPOSITORY "https://github.com/mozilla/mentat.git"
|
||||
|
||||
RUN git clone $PROJECT_REPOSITORY
|
||||
|
||||
WORKDIR /build/mentat
|
||||
|
||||
# Temporary.
|
||||
RUN git fetch origin master && git checkout origin/generic-automation-images && git show-ref HEAD
|
||||
|
||||
# Populate dependencies.
|
||||
RUN ./sdks/android/Mentat/gradlew --no-daemon -p sdks/android/Mentat tasks
|
||||
|
||||
# Build Rust.
|
||||
RUN ./sdks/android/Mentat/gradlew --no-daemon -p sdks/android/Mentat cargoBuild
|
||||
|
||||
# Actually build. In the future, we might also lint (to cache additional dependencies).
|
||||
RUN ./sdks/android/Mentat/gradlew --no-daemon -p sdks/android/Mentat assemble test
|
||||
|
||||
#----------------------------------------------------------------------------------------------------------------------
|
||||
# -- Cleanup ----------------------------------------------------------------------------------------------------------
|
||||
#----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
# Drop built Rust artifacts.
|
||||
RUN cargo clean
|
81
automation/docker/generic/Dockerfile
Normal file
81
automation/docker/generic/Dockerfile
Normal file
|
@ -0,0 +1,81 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
FROM mozillamobile/android-components:1.4
|
||||
|
||||
MAINTAINER Nick Alexander "nalexander@mozilla.com"
|
||||
|
||||
#----------------------------------------------------------------------------------------------------------------------
|
||||
#-- Configuration -----------------------------------------------------------------------------------------------------
|
||||
#----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
ENV ANDROID_NDK_VERSION "r17b"
|
||||
|
||||
#----------------------------------------------------------------------------------------------------------------------
|
||||
#-- System ------------------------------------------------------------------------------------------------------------
|
||||
#----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
RUN apt-get update -qq
|
||||
|
||||
#----------------------------------------------------------------------------------------------------------------------
|
||||
#-- Android NDK (Android SDK comes from base `android-components` image) ----------------------------------------------
|
||||
#----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
RUN mkdir -p /build
|
||||
WORKDIR /build
|
||||
|
||||
ENV ANDROID_NDK_HOME /build/android-ndk
|
||||
|
||||
RUN curl -L https://dl.google.com/android/repository/android-ndk-${ANDROID_NDK_VERSION}-linux-x86_64.zip > ndk.zip \
|
||||
&& unzip ndk.zip -d /build \
|
||||
&& rm ndk.zip \
|
||||
&& mv /build/android-ndk-${ANDROID_NDK_VERSION} ${ANDROID_NDK_HOME}
|
||||
|
||||
ENV ANDROID_NDK_TOOLCHAIN_DIR /build/android-ndk-toolchain
|
||||
ENV ANDROID_NDK_API_VERSION 26
|
||||
|
||||
RUN set -eux; \
|
||||
python "$ANDROID_NDK_HOME/build/tools/make_standalone_toolchain.py" --arch="arm" --api="$ANDROID_NDK_API_VERSION" --install-dir="$ANDROID_NDK_TOOLCHAIN_DIR/arm-$ANDROID_NDK_API_VERSION" --force; \
|
||||
python "$ANDROID_NDK_HOME/build/tools/make_standalone_toolchain.py" --arch="arm64" --api="$ANDROID_NDK_API_VERSION" --install-dir="$ANDROID_NDK_TOOLCHAIN_DIR/arm64-$ANDROID_NDK_API_VERSION" --force; \
|
||||
python "$ANDROID_NDK_HOME/build/tools/make_standalone_toolchain.py" --arch="x86" --api="$ANDROID_NDK_API_VERSION" --install-dir="$ANDROID_NDK_TOOLCHAIN_DIR/x86-$ANDROID_NDK_API_VERSION" --force
|
||||
|
||||
#----------------------------------------------------------------------------------------------------------------------
|
||||
#-- Rust (cribbed from https://github.com/rust-lang-nursery/docker-rust/blob/ced83778ec6fea7f63091a484946f95eac0ee611/1.27.1/stretch/Dockerfile)
|
||||
#-- Rust is after the Android NDK since Rust rolls forward more frequently. Both stable and beta for advanced consumers.
|
||||
#----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
ENV RUSTUP_HOME=/usr/local/rustup \
|
||||
CARGO_HOME=/usr/local/cargo \
|
||||
PATH=/usr/local/cargo/bin:$PATH \
|
||||
RUST_VERSION=1.27.1
|
||||
|
||||
RUN set -eux; \
|
||||
rustArch='x86_64-unknown-linux-gnu'; rustupSha256='4d382e77fd6760282912d2d9beec5e260ec919efd3cb9bdb64fe1207e84b9d91'; \
|
||||
url="https://static.rust-lang.org/rustup/archive/1.12.0/${rustArch}/rustup-init"; \
|
||||
wget "$url"; \
|
||||
echo "${rustupSha256} *rustup-init" | sha256sum -c -; \
|
||||
chmod +x rustup-init; \
|
||||
./rustup-init -y --no-modify-path --default-toolchain $RUST_VERSION; \
|
||||
rm rustup-init; \
|
||||
chmod -R a+w $RUSTUP_HOME $CARGO_HOME; \
|
||||
rustup --version; \
|
||||
cargo --version; \
|
||||
rustc --version; \
|
||||
rustup target add i686-linux-android; \
|
||||
rustup target add armv7-linux-androideabi; \
|
||||
rustup target add aarch64-linux-android
|
||||
|
||||
RUN set -eux; \
|
||||
rustup install beta; \
|
||||
rustup target add --toolchain beta i686-linux-android; \
|
||||
rustup target add --toolchain beta armv7-linux-androideabi; \
|
||||
rustup target add --toolchain beta aarch64-linux-android
|
||||
|
||||
#----------------------------------------------------------------------------------------------------------------------
|
||||
# -- Cleanup ----------------------------------------------------------------------------------------------------------
|
||||
#----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
RUN apt-get clean
|
122
automation/taskcluster/decision_task_pull_request.py
Normal file
122
automation/taskcluster/decision_task_pull_request.py
Normal file
|
@ -0,0 +1,122 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import taskcluster
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
"""
|
||||
Decision task for pull requests
|
||||
"""
|
||||
|
||||
TASK_ID = os.environ.get('TASK_ID')
|
||||
REPO_URL = os.environ.get('GITHUB_HEAD_REPO_URL')
|
||||
BRANCH = os.environ.get('GITHUB_HEAD_BRANCH')
|
||||
COMMIT = os.environ.get('GITHUB_HEAD_SHA')
|
||||
|
||||
def fetch_module_names():
|
||||
process = subprocess.Popen(["./gradlew", "--no-daemon", "printModules"], stdout=subprocess.PIPE,
|
||||
cwd=os.path.join(os.getcwd(), "sdks", "android", "Mentat"))
|
||||
(output, err) = process.communicate()
|
||||
exit_code = process.wait()
|
||||
|
||||
if exit_code is not 0:
|
||||
print "Gradle command returned error:", exit_code
|
||||
|
||||
return re.findall('module: (.*)', output, re.M)
|
||||
|
||||
|
||||
def schedule_task(queue, taskId, task):
|
||||
print "TASK", taskId
|
||||
print json.dumps(task, indent=4, separators=(',', ': '))
|
||||
|
||||
result = queue.createTask(taskId, task)
|
||||
print "RESULT", taskId
|
||||
print json.dumps(result, indent=4, separators=(',', ': '))
|
||||
|
||||
|
||||
def create_task(name, description, command):
|
||||
created = datetime.datetime.now()
|
||||
expires = taskcluster.fromNow('1 year')
|
||||
deadline = taskcluster.fromNow('1 day')
|
||||
|
||||
return {
|
||||
"workerType": 'github-worker',
|
||||
"taskGroupId": TASK_ID,
|
||||
"expires": taskcluster.stringDate(expires),
|
||||
"retries": 5,
|
||||
"created": taskcluster.stringDate(created),
|
||||
"tags": {},
|
||||
"priority": "lowest",
|
||||
"schedulerId": "taskcluster-github",
|
||||
"deadline": taskcluster.stringDate(deadline),
|
||||
"dependencies": [ TASK_ID ],
|
||||
"routes": [],
|
||||
"scopes": [],
|
||||
"requires": "all-completed",
|
||||
"payload": {
|
||||
"features": {},
|
||||
"maxRunTime": 7200,
|
||||
"image": "mozillamobile/mentat:1.2",
|
||||
"command": [
|
||||
"/bin/bash",
|
||||
"--login",
|
||||
"-cx",
|
||||
"export TERM=dumb && git fetch %s %s && git config advice.detachedHead false && git checkout %s && cd sdks/android/Mentat && ./gradlew --no-daemon clean %s" % (REPO_URL, BRANCH, COMMIT, command)
|
||||
],
|
||||
"artifacts": {},
|
||||
"deadline": taskcluster.stringDate(deadline)
|
||||
},
|
||||
"provisionerId": "aws-provisioner-v1",
|
||||
"metadata": {
|
||||
"name": name,
|
||||
"description": description,
|
||||
"owner": "nalexander@mozilla.com",
|
||||
"source": "https://github.com/mozilla/mentat"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def create_module_task(module):
|
||||
return create_task(
|
||||
name='Mentat Android SDK - Module ' + module,
|
||||
description='Building and testing module ' + module,
|
||||
command=" ".join(map(lambda x: module + ":" + x, ['assemble', 'test', 'lint'])))
|
||||
|
||||
|
||||
# def create_detekt_task():
|
||||
# return create_task(
|
||||
# name='Android Components - detekt',
|
||||
# description='Running detekt over all modules',
|
||||
# command='detektCheck')
|
||||
|
||||
|
||||
# def create_ktlint_task():
|
||||
# return create_task(
|
||||
# name='Android Components - ktlint',
|
||||
# description='Running ktlint over all modules',
|
||||
# command='ktlint')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
queue = taskcluster.Queue({ 'baseUrl': 'http://taskcluster/queue/v1' })
|
||||
|
||||
modules = fetch_module_names()
|
||||
|
||||
if len(modules) == 0:
|
||||
print "Could not get module names from gradle"
|
||||
sys.exit(2)
|
||||
|
||||
for module in modules:
|
||||
task = create_module_task(module)
|
||||
task_id = taskcluster.slugId()
|
||||
schedule_task(queue, task_id, task)
|
||||
|
||||
# schedule_task(queue, taskcluster.slugId(), create_detekt_task())
|
||||
# schedule_task(queue, taskcluster.slugId(), create_ktlint_task())
|
28
automation/taskcluster/release/fetch-bintray-api-key.py
Normal file
28
automation/taskcluster/release/fetch-bintray-api-key.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import os
|
||||
import taskcluster
|
||||
|
||||
SECRET_NAME = 'project/mentat/publish'
|
||||
TASKCLUSTER_BASE_URL = 'http://taskcluster/secrets/v1'
|
||||
|
||||
def fetch_publish_secrets(secret_name):
|
||||
"""Fetch and return secrets from taskcluster's secret service"""
|
||||
secrets = taskcluster.Secrets({'baseUrl': TASKCLUSTER_BASE_URL})
|
||||
return secrets.get(secret_name)
|
||||
|
||||
def main():
|
||||
"""Fetch the bintray user and api key from taskcluster's secret service
|
||||
and save it to local.properties in the project root directory.
|
||||
"""
|
||||
data = fetch_publish_secrets(SECRET_NAME)
|
||||
|
||||
properties_file_path = os.path.join(os.path.dirname(__file__), '../../../sdks/android/Mentat/local.properties')
|
||||
with open(properties_file_path, 'w') as properties_file:
|
||||
properties_file.write("bintray.user=%s\n" % data['secret']['bintray_user'])
|
||||
properties_file.write("bintray.apikey=%s\n" % data['secret']['bintray_apikey'])
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
32
build/version.rs
Normal file
32
build/version.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
// 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 rustc_version::{version, Version};
|
||||
use std::io::{self, Write};
|
||||
use std::process::exit;
|
||||
|
||||
/// MIN_VERSION should be changed when there's a new minimum version of rustc required
|
||||
/// to build the project.
|
||||
static MIN_VERSION: &str = "1.69.0";
|
||||
|
||||
fn main() {
|
||||
let ver = version().unwrap();
|
||||
let min = Version::parse(MIN_VERSION).unwrap();
|
||||
if ver < min {
|
||||
writeln!(
|
||||
&mut io::stderr(),
|
||||
"Mentat requires rustc {} or higher, you were using version {}.",
|
||||
MIN_VERSION,
|
||||
ver
|
||||
)
|
||||
.unwrap();
|
||||
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
181
core-traits/value_type_set.rs
Normal file
181
core-traits/value_type_set.rs
Normal file
|
@ -0,0 +1,181 @@
|
|||
// 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.
|
||||
|
||||
use enum_set::EnumSet;
|
||||
|
||||
use crate::ValueType;
|
||||
|
||||
trait EnumSetExtensions<T: ::enum_set::CLike + Clone> {
|
||||
/// Return a set containing both `x` and `y`.
|
||||
fn of_both(x: T, y: T) -> EnumSet<T>;
|
||||
|
||||
/// Return a clone of `self` with `y` added.
|
||||
fn with(&self, y: T) -> EnumSet<T>;
|
||||
}
|
||||
|
||||
impl<T: ::enum_set::CLike + Clone> EnumSetExtensions<T> for EnumSet<T> {
|
||||
/// Return a set containing both `x` and `y`.
|
||||
fn of_both(x: T, y: T) -> Self {
|
||||
let mut o = EnumSet::new();
|
||||
o.insert(x);
|
||||
o.insert(y);
|
||||
o
|
||||
}
|
||||
|
||||
/// Return a clone of `self` with `y` added.
|
||||
fn with(&self, y: T) -> EnumSet<T> {
|
||||
let mut o = self.clone();
|
||||
o.insert(y);
|
||||
o
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct ValueTypeSet(pub EnumSet<ValueType>);
|
||||
|
||||
impl Default for ValueTypeSet {
|
||||
fn default() -> ValueTypeSet {
|
||||
ValueTypeSet::any()
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueTypeSet {
|
||||
pub fn any() -> ValueTypeSet {
|
||||
ValueTypeSet(ValueType::all_enums())
|
||||
}
|
||||
|
||||
pub fn none() -> ValueTypeSet {
|
||||
ValueTypeSet(EnumSet::new())
|
||||
}
|
||||
|
||||
/// Return a set containing only `t`.
|
||||
pub fn of_one(t: ValueType) -> ValueTypeSet {
|
||||
let mut s = EnumSet::new();
|
||||
s.insert(t);
|
||||
ValueTypeSet(s)
|
||||
}
|
||||
|
||||
/// Return a set containing `Double` and `Long`.
|
||||
pub fn of_numeric_types() -> ValueTypeSet {
|
||||
ValueTypeSet(EnumSet::of_both(ValueType::Double, ValueType::Long))
|
||||
}
|
||||
|
||||
/// Return a set containing `Double`, `Long`, and `Instant`.
|
||||
pub fn of_numeric_and_instant_types() -> ValueTypeSet {
|
||||
let mut s = EnumSet::new();
|
||||
s.insert(ValueType::Double);
|
||||
s.insert(ValueType::Long);
|
||||
s.insert(ValueType::Instant);
|
||||
ValueTypeSet(s)
|
||||
}
|
||||
|
||||
/// Return a set containing `Ref` and `Keyword`.
|
||||
pub fn of_keywords() -> ValueTypeSet {
|
||||
ValueTypeSet(EnumSet::of_both(ValueType::Ref, ValueType::Keyword))
|
||||
}
|
||||
|
||||
/// Return a set containing `Ref` and `Long`.
|
||||
pub fn of_longs() -> ValueTypeSet {
|
||||
ValueTypeSet(EnumSet::of_both(ValueType::Ref, ValueType::Long))
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueTypeSet {
|
||||
pub fn insert(&mut self, vt: ValueType) -> bool {
|
||||
self.0.insert(vt)
|
||||
}
|
||||
|
||||
pub fn len(self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
/// Returns a set containing all the types in this set and `other`.
|
||||
pub fn union(self, other: ValueTypeSet) -> ValueTypeSet {
|
||||
ValueTypeSet(self.0.union(other.0))
|
||||
}
|
||||
|
||||
pub fn intersection(self, other: ValueTypeSet) -> ValueTypeSet {
|
||||
ValueTypeSet(self.0.intersection(other.0))
|
||||
}
|
||||
|
||||
/// Returns the set difference between `self` and `other`, which is the
|
||||
/// set of items in `self` that are not in `other`.
|
||||
pub fn difference(self, other: ValueTypeSet) -> ValueTypeSet {
|
||||
ValueTypeSet(self.0 - other.0)
|
||||
}
|
||||
|
||||
/// Return an arbitrary type that's part of this set.
|
||||
/// For a set containing a single type, this will be that type.
|
||||
pub fn exemplar(self) -> Option<ValueType> {
|
||||
self.0.iter().next()
|
||||
}
|
||||
|
||||
pub fn is_subset(self, other: ValueTypeSet) -> bool {
|
||||
self.0.is_subset(&other.0)
|
||||
}
|
||||
|
||||
/// Returns true if `self` and `other` contain no items in common.
|
||||
pub fn is_disjoint(self, other: ValueTypeSet) -> bool {
|
||||
self.0.is_disjoint(&other.0)
|
||||
}
|
||||
|
||||
pub fn contains(self, vt: ValueType) -> bool {
|
||||
self.0.contains(&vt)
|
||||
}
|
||||
|
||||
pub fn is_empty(self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
pub fn is_unit(self) -> bool {
|
||||
self.0.len() == 1
|
||||
}
|
||||
|
||||
pub fn iter(self) -> ::enum_set::Iter<ValueType> {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ValueType> for ValueTypeSet {
|
||||
fn from(t: ValueType) -> Self {
|
||||
ValueTypeSet::of_one(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueTypeSet {
|
||||
pub fn is_only_numeric(self) -> bool {
|
||||
self.is_subset(ValueTypeSet::of_numeric_types())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for ValueTypeSet {
|
||||
type Item = ValueType;
|
||||
type IntoIter = ::enum_set::Iter<ValueType>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::iter::FromIterator<ValueType> for ValueTypeSet {
|
||||
fn from_iter<I: IntoIterator<Item = ValueType>>(iterator: I) -> Self {
|
||||
let mut ret = Self::none();
|
||||
ret.0.extend(iterator);
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::iter::Extend<ValueType> for ValueTypeSet {
|
||||
fn extend<I: IntoIterator<Item = ValueType>>(&mut self, iter: I) {
|
||||
for element in iter {
|
||||
self.0.insert(element);
|
||||
}
|
||||
}
|
||||
}
|
65
core-traits/values.rs
Normal file
65
core-traits/values.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
// 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 edn::symbols;
|
||||
/// Literal `Value` instances in the the "db" namespace.
|
||||
///
|
||||
/// Used through-out the transactor to match core DB constructs.
|
||||
use edn::types::Value;
|
||||
|
||||
/// Declare a lazy static `ident` of type `Value::Keyword` with the given `namespace` and
|
||||
/// `name`.
|
||||
///
|
||||
/// 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
|
||||
/// will be expanded outside-in. Looking at the `lazy_static!` source suggests that there is no
|
||||
/// harm in repeating that macro, since internally a multi-`static` block will be expanded into
|
||||
/// many single-`static` blocks.
|
||||
///
|
||||
/// TODO: take just ":db.part/db" and define DB_PART_DB using "db.part" and "db".
|
||||
macro_rules! lazy_static_namespaced_keyword_value (
|
||||
($tag:ident, $namespace:expr, $name:expr) => (
|
||||
lazy_static! {
|
||||
pub static ref $tag: Value = {
|
||||
Value::Keyword(symbols::Keyword::namespaced($namespace, $name))
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
lazy_static_namespaced_keyword_value!(DB_ADD, "db", "add");
|
||||
lazy_static_namespaced_keyword_value!(DB_ALTER_ATTRIBUTE, "db.alter", "attribute");
|
||||
lazy_static_namespaced_keyword_value!(DB_CARDINALITY, "db", "cardinality");
|
||||
lazy_static_namespaced_keyword_value!(DB_CARDINALITY_MANY, "db.cardinality", "many");
|
||||
lazy_static_namespaced_keyword_value!(DB_CARDINALITY_ONE, "db.cardinality", "one");
|
||||
lazy_static_namespaced_keyword_value!(DB_FULLTEXT, "db", "fulltext");
|
||||
lazy_static_namespaced_keyword_value!(DB_IDENT, "db", "ident");
|
||||
lazy_static_namespaced_keyword_value!(DB_INDEX, "db", "index");
|
||||
lazy_static_namespaced_keyword_value!(DB_INSTALL_ATTRIBUTE, "db.install", "attribute");
|
||||
lazy_static_namespaced_keyword_value!(DB_IS_COMPONENT, "db", "isComponent");
|
||||
lazy_static_namespaced_keyword_value!(DB_NO_HISTORY, "db", "noHistory");
|
||||
lazy_static_namespaced_keyword_value!(DB_PART_DB, "db.part", "db");
|
||||
lazy_static_namespaced_keyword_value!(DB_RETRACT, "db", "retract");
|
||||
lazy_static_namespaced_keyword_value!(DB_TYPE_BOOLEAN, "db.type", "boolean");
|
||||
lazy_static_namespaced_keyword_value!(DB_TYPE_DOUBLE, "db.type", "double");
|
||||
lazy_static_namespaced_keyword_value!(DB_TYPE_INSTANT, "db.type", "instant");
|
||||
lazy_static_namespaced_keyword_value!(DB_TYPE_KEYWORD, "db.type", "keyword");
|
||||
lazy_static_namespaced_keyword_value!(DB_TYPE_LONG, "db.type", "long");
|
||||
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_URI, "db.type", "uri");
|
||||
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_IDENTITY, "db.unique", "identity");
|
||||
lazy_static_namespaced_keyword_value!(DB_UNIQUE_VALUE, "db.unique", "value");
|
||||
lazy_static_namespaced_keyword_value!(DB_VALUE_TYPE, "db", "valueType");
|
19
core/Cargo.toml
Normal file
19
core/Cargo.toml
Normal file
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "mentat_core"
|
||||
version = "0.0.2"
|
||||
workspace = ".."
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "~0.4", features = ["serde"] }
|
||||
enum-set = "~0.0"
|
||||
failure = "~0.1"
|
||||
indexmap = "~1.9"
|
||||
ordered-float = { version = "~2.8", features = ["serde"] }
|
||||
uuid = { version = "~1", features = ["v4", "serde"] }
|
||||
|
||||
[dependencies.core_traits]
|
||||
path = "../core-traits"
|
||||
|
||||
[dependencies.edn]
|
||||
path = "../edn"
|
||||
features = ["serde_support"]
|
49
core/src/cache.rs
Normal file
49
core/src/cache.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
// 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.
|
||||
|
||||
/// Cache traits.
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use core_traits::{Entid, TypedValue};
|
||||
|
||||
use crate::Schema;
|
||||
|
||||
pub trait CachedAttributes {
|
||||
fn is_attribute_cached_reverse(&self, entid: Entid) -> bool;
|
||||
fn is_attribute_cached_forward(&self, entid: Entid) -> bool;
|
||||
fn has_cached_attributes(&self) -> bool;
|
||||
|
||||
fn get_values_for_entid(
|
||||
&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.
|
||||
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>>;
|
||||
}
|
||||
|
||||
pub trait UpdateableCache<E> {
|
||||
fn update<I>(&mut self, schema: &Schema, retractions: I, assertions: I) -> Result<(), E>
|
||||
where
|
||||
I: Iterator<Item = (Entid, Entid, TypedValue)>;
|
||||
}
|
65
core/src/counter.rs
Normal file
65
core/src/counter.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
// 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::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct RcCounter {
|
||||
c: Rc<Cell<usize>>,
|
||||
}
|
||||
|
||||
/// A simple shared counter.
|
||||
impl RcCounter {
|
||||
pub fn with_initial(value: usize) -> Self {
|
||||
RcCounter {
|
||||
c: Rc::new(Cell::new(value)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
RcCounter {
|
||||
c: Rc::new(Cell::new(0)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the next value in the sequence.
|
||||
///
|
||||
/// ```
|
||||
/// use mentat_core::counter::RcCounter;
|
||||
///
|
||||
/// let c = RcCounter::with_initial(3);
|
||||
/// assert_eq!(c.next(), 3);
|
||||
/// assert_eq!(c.next(), 4);
|
||||
/// let d = c.clone();
|
||||
/// assert_eq!(d.next(), 5);
|
||||
/// assert_eq!(c.next(), 6);
|
||||
/// ```
|
||||
pub fn next(&self) -> usize {
|
||||
let current = self.c.get();
|
||||
self.c.replace(current + 1)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::RcCounter;
|
||||
|
||||
#[test]
|
||||
fn test_rc_counter() {
|
||||
let c = RcCounter::new();
|
||||
assert_eq!(c.next(), 0);
|
||||
assert_eq!(c.next(), 1);
|
||||
|
||||
let d = c.clone();
|
||||
assert_eq!(d.next(), 2);
|
||||
assert_eq!(c.next(), 3);
|
||||
}
|
||||
}
|
345
core/src/lib.rs
Normal file
345
core/src/lib.rs
Normal file
|
@ -0,0 +1,345 @@
|
|||
// 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.
|
||||
|
||||
extern crate chrono;
|
||||
extern crate enum_set;
|
||||
extern crate failure;
|
||||
extern crate indexmap;
|
||||
extern crate ordered_float;
|
||||
extern crate uuid;
|
||||
|
||||
extern crate core_traits;
|
||||
|
||||
extern crate edn;
|
||||
|
||||
use core_traits::{Attribute, Entid, KnownEntid, ValueType};
|
||||
|
||||
mod cache;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub use uuid::Uuid;
|
||||
|
||||
pub use chrono::{
|
||||
DateTime,
|
||||
Timelike, // For truncation.
|
||||
};
|
||||
|
||||
pub use edn::parse::parse_query;
|
||||
pub use edn::{Cloned, FromMicros, FromRc, Keyword, ToMicros, Utc, ValueRc};
|
||||
|
||||
pub use crate::cache::{CachedAttributes, UpdateableCache};
|
||||
|
||||
mod sql_types;
|
||||
mod tx_report;
|
||||
/// Core types defining a Mentat knowledge base.
|
||||
mod types;
|
||||
|
||||
pub use crate::tx_report::TxReport;
|
||||
|
||||
pub use crate::types::ValueTypeTag;
|
||||
|
||||
pub use crate::sql_types::{SQLTypeAffinity, SQLValueType, SQLValueTypeSet};
|
||||
|
||||
/// Map `Keyword` idents (`:db/ident`) to positive integer entids (`1`).
|
||||
pub type IdentMap = BTreeMap<Keyword, Entid>;
|
||||
|
||||
/// Map positive integer entids (`1`) to `Keyword` idents (`:db/ident`).
|
||||
pub type EntidMap = BTreeMap<Entid, Keyword>;
|
||||
|
||||
/// Map attribute entids to `Attribute` instances.
|
||||
pub type AttributeMap = BTreeMap<Entid, Attribute>;
|
||||
|
||||
/// Represents a Mentat schema.
|
||||
///
|
||||
/// Maintains the mapping between string idents and positive integer entids; and exposes the schema
|
||||
/// flags associated to a given entid (equivalently, ident).
|
||||
///
|
||||
/// TODO: consider a single bi-directional map instead of separate ident->entid and entid->ident
|
||||
/// maps.
|
||||
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||
pub struct Schema {
|
||||
/// Map entid->ident.
|
||||
///
|
||||
/// Invariant: is the inverse map of `ident_map`.
|
||||
pub entid_map: EntidMap,
|
||||
|
||||
/// Map ident->entid.
|
||||
///
|
||||
/// Invariant: is the inverse map of `entid_map`.
|
||||
pub ident_map: IdentMap,
|
||||
|
||||
/// Map entid->attribute flags.
|
||||
///
|
||||
/// Invariant: key-set is the same as the key-set of `entid_map` (equivalently, the value-set of
|
||||
/// `ident_map`).
|
||||
pub attribute_map: AttributeMap,
|
||||
|
||||
/// Maintain a vec of unique attribute IDs for which the corresponding attribute in `attribute_map`
|
||||
/// has `.component == true`.
|
||||
pub component_attributes: Vec<Entid>,
|
||||
}
|
||||
|
||||
pub trait HasSchema {
|
||||
fn entid_for_type(&self, t: ValueType) -> Option<KnownEntid>;
|
||||
|
||||
fn get_ident<T>(&self, x: T) -> Option<&Keyword>
|
||||
where
|
||||
T: Into<Entid>;
|
||||
fn get_entid(&self, x: &Keyword) -> Option<KnownEntid>;
|
||||
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.
|
||||
fn attribute_for_ident(&self, ident: &Keyword) -> Option<(&Attribute, KnownEntid)>;
|
||||
|
||||
/// Return true if the provided entid identifies an attribute in this schema.
|
||||
fn is_attribute<T>(&self, x: T) -> bool
|
||||
where
|
||||
T: Into<Entid>;
|
||||
|
||||
/// Return true if the provided ident identifies an attribute in this schema.
|
||||
fn identifies_attribute(&self, x: &Keyword) -> bool;
|
||||
|
||||
fn component_attributes(&self) -> &[Entid];
|
||||
}
|
||||
|
||||
impl 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(),
|
||||
};
|
||||
s.update_component_attributes();
|
||||
s
|
||||
}
|
||||
|
||||
/// Returns an symbolic representation of the schema suitable for applying across Mentat stores.
|
||||
pub fn to_edn_value(&self) -> edn::Value {
|
||||
edn::Value::Vector(
|
||||
(&self.attribute_map)
|
||||
.iter()
|
||||
.map(|(entid, attribute)| attribute.to_edn_value(self.get_ident(*entid).cloned()))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_raw_entid(&self, x: &Keyword) -> Option<Entid> {
|
||||
self.ident_map.get(x).copied()
|
||||
}
|
||||
|
||||
pub fn update_component_attributes(&mut self) {
|
||||
let mut components: Vec<Entid>;
|
||||
components = self
|
||||
.attribute_map
|
||||
.iter()
|
||||
.filter_map(|(k, v)| if v.component { Some(*k) } else { None })
|
||||
.collect();
|
||||
components.sort_unstable();
|
||||
self.component_attributes = components;
|
||||
}
|
||||
}
|
||||
|
||||
impl HasSchema for Schema {
|
||||
fn entid_for_type(&self, t: ValueType) -> Option<KnownEntid> {
|
||||
// TODO: this can be made more efficient.
|
||||
self.get_entid(&t.into_keyword())
|
||||
}
|
||||
|
||||
fn get_ident<T>(&self, x: T) -> Option<&Keyword>
|
||||
where
|
||||
T: Into<Entid>,
|
||||
{
|
||||
self.entid_map.get(&x.into())
|
||||
}
|
||||
|
||||
fn get_entid(&self, x: &Keyword) -> Option<KnownEntid> {
|
||||
self.get_raw_entid(x).map(KnownEntid)
|
||||
}
|
||||
|
||||
fn attribute_for_entid<T>(&self, x: T) -> Option<&Attribute>
|
||||
where
|
||||
T: Into<Entid>,
|
||||
{
|
||||
self.attribute_map.get(&x.into())
|
||||
}
|
||||
|
||||
fn attribute_for_ident(&self, ident: &Keyword) -> Option<(&Attribute, KnownEntid)> {
|
||||
self.get_raw_entid(&ident).and_then(|entid| {
|
||||
self.attribute_for_entid(entid)
|
||||
.map(|a| (a, KnownEntid(entid)))
|
||||
})
|
||||
}
|
||||
|
||||
/// Return true if the provided entid identifies an attribute in this schema.
|
||||
fn is_attribute<T>(&self, x: T) -> bool
|
||||
where
|
||||
T: Into<Entid>,
|
||||
{
|
||||
self.attribute_map.contains_key(&x.into())
|
||||
}
|
||||
|
||||
/// Return true if the provided ident identifies an attribute in this schema.
|
||||
fn identifies_attribute(&self, x: &Keyword) -> bool {
|
||||
self.get_raw_entid(x)
|
||||
.map(|e| self.is_attribute(e))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn component_attributes(&self) -> &[Entid] {
|
||||
&self.component_attributes
|
||||
}
|
||||
}
|
||||
|
||||
pub mod counter;
|
||||
pub mod util;
|
||||
|
||||
/// A helper macro to sequentially process an iterable sequence,
|
||||
/// evaluating a block between each pair of items.
|
||||
///
|
||||
/// This is used to simply and efficiently produce output like
|
||||
///
|
||||
/// ```sql
|
||||
/// 1, 2, 3
|
||||
/// ```
|
||||
///
|
||||
/// or
|
||||
///
|
||||
/// ```sql
|
||||
/// x = 1 AND y = 2
|
||||
/// ```
|
||||
///
|
||||
/// without producing an intermediate string sequence.
|
||||
#[macro_export]
|
||||
macro_rules! interpose {
|
||||
( $name: pat, $across: expr, $body: block, $inter: block ) => {
|
||||
interpose_iter!($name, $across.iter(), $body, $inter)
|
||||
};
|
||||
}
|
||||
|
||||
/// A helper to bind `name` to values in `across`, running `body` for each value,
|
||||
/// and running `inter` between each value. See `interpose` for examples.
|
||||
#[macro_export]
|
||||
macro_rules! interpose_iter {
|
||||
( $name: pat, $across: expr, $body: block, $inter: block ) => {
|
||||
let mut seq = $across;
|
||||
if let Some($name) = seq.next() {
|
||||
$body;
|
||||
for $name in seq {
|
||||
$inter;
|
||||
$body;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use core_traits::{attribute, TypedValue};
|
||||
|
||||
fn associate_ident(schema: &mut Schema, i: Keyword, e: Entid) {
|
||||
schema.entid_map.insert(e, i.clone());
|
||||
schema.ident_map.insert(i, e);
|
||||
}
|
||||
|
||||
fn add_attribute(schema: &mut Schema, e: Entid, a: Attribute) {
|
||||
schema.attribute_map.insert(e, a);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_datetime_truncation() {
|
||||
let dt: DateTime<Utc> =
|
||||
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();
|
||||
if let TypedValue::Instant(roundtripped) = tv {
|
||||
assert_eq!(roundtripped, expected);
|
||||
} else {
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_as_edn_value() {
|
||||
let mut schema = Schema::default();
|
||||
|
||||
let attr1 = Attribute {
|
||||
index: true,
|
||||
value_type: ValueType::Ref,
|
||||
fulltext: false,
|
||||
unique: None,
|
||||
multival: false,
|
||||
component: false,
|
||||
no_history: true,
|
||||
};
|
||||
associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 97);
|
||||
add_attribute(&mut schema, 97, attr1);
|
||||
|
||||
let attr2 = Attribute {
|
||||
index: false,
|
||||
value_type: ValueType::String,
|
||||
fulltext: true,
|
||||
unique: Some(attribute::Unique::Value),
|
||||
multival: true,
|
||||
component: false,
|
||||
no_history: false,
|
||||
};
|
||||
associate_ident(&mut schema, Keyword::namespaced("foo", "bas"), 98);
|
||||
add_attribute(&mut schema, 98, attr2);
|
||||
|
||||
let attr3 = Attribute {
|
||||
index: false,
|
||||
value_type: ValueType::Boolean,
|
||||
fulltext: false,
|
||||
unique: Some(attribute::Unique::Identity),
|
||||
multival: false,
|
||||
component: true,
|
||||
no_history: false,
|
||||
};
|
||||
|
||||
associate_ident(&mut schema, Keyword::namespaced("foo", "bat"), 99);
|
||||
add_attribute(&mut schema, 99, attr3);
|
||||
|
||||
let value = schema.to_edn_value();
|
||||
|
||||
let expected_output = r#"[ { :db/ident :foo/bar
|
||||
:db/valueType :db.type/ref
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/index true
|
||||
:db/noHistory true },
|
||||
{ :db/ident :foo/bas
|
||||
:db/valueType :db.type/string
|
||||
:db/cardinality :db.cardinality/many
|
||||
:db/unique :db.unique/value
|
||||
:db/fulltext true },
|
||||
{ :db/ident :foo/bat
|
||||
:db/valueType :db.type/boolean
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/unique :db.unique/identity
|
||||
:db/isComponent true }, ]"#;
|
||||
let expected_value = edn::parse::value(&expected_output)
|
||||
.expect("to be able to parse")
|
||||
.without_spans();
|
||||
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 value2 = schema.to_edn_value();
|
||||
assert_eq!(expected_value, value2);
|
||||
}
|
||||
}
|
140
core/src/sql_types.rs
Normal file
140
core/src/sql_types.rs
Normal file
|
@ -0,0 +1,140 @@
|
|||
// 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.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use core_traits::{ValueType, ValueTypeSet};
|
||||
|
||||
use crate::types::ValueTypeTag;
|
||||
|
||||
/// Type safe representation of the possible return values from SQLite's `typeof`
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||
pub enum SQLTypeAffinity {
|
||||
Null, // "null"
|
||||
Integer, // "integer"
|
||||
Real, // "real"
|
||||
Text, // "text"
|
||||
Blob, // "blob"
|
||||
}
|
||||
|
||||
// Put this here rather than in `db` simply because it's widely needed.
|
||||
pub trait SQLValueType {
|
||||
fn value_type_tag(&self) -> ValueTypeTag;
|
||||
fn accommodates_integer(&self, int: i64) -> bool;
|
||||
|
||||
/// Return a pair of the ValueTypeTag for this value type, and the SQLTypeAffinity required
|
||||
/// to distinguish it from any other types that share the same tag.
|
||||
///
|
||||
/// Background: The tag alone is not enough to determine the type of a value, since multiple
|
||||
/// ValueTypes may share the same tag (for example, ValueType::Long and ValueType::Double).
|
||||
/// However, each ValueType can be determined by checking both the tag and the type's affinity.
|
||||
fn sql_representation(&self) -> (ValueTypeTag, Option<SQLTypeAffinity>);
|
||||
}
|
||||
|
||||
impl SQLValueType for ValueType {
|
||||
fn sql_representation(&self) -> (ValueTypeTag, Option<SQLTypeAffinity>) {
|
||||
match *self {
|
||||
ValueType::Ref => (0, None),
|
||||
ValueType::Boolean => (1, None),
|
||||
ValueType::Instant => (4, None),
|
||||
|
||||
// SQLite distinguishes integral from decimal types, allowing long and double to share a tag.
|
||||
ValueType::Long => (5, Some(SQLTypeAffinity::Integer)),
|
||||
ValueType::Double => (5, Some(SQLTypeAffinity::Real)),
|
||||
ValueType::String => (10, None),
|
||||
ValueType::Uuid => (11, None),
|
||||
ValueType::Keyword => (13, None),
|
||||
ValueType::Bytes => (15, Some(SQLTypeAffinity::Blob)),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn value_type_tag(&self) -> ValueTypeTag {
|
||||
self.sql_representation().0
|
||||
}
|
||||
|
||||
/// Returns true if the provided integer is in the SQLite value space of this type. For
|
||||
/// example, `1` is how we encode `true`.
|
||||
fn accommodates_integer(&self, int: i64) -> bool {
|
||||
use crate::ValueType::*;
|
||||
match *self {
|
||||
Instant => false, // Always use #inst.
|
||||
Long | Double => true,
|
||||
Ref => int >= 0,
|
||||
Boolean => (int == 0) || (int == 1),
|
||||
ValueType::String => false,
|
||||
Keyword => false,
|
||||
Uuid => false,
|
||||
Bytes => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// We have an enum of types, `ValueType`. It can be collected into a set, `ValueTypeSet`. Each type
|
||||
/// is associated with a type tag, which is how a type is represented in, e.g., SQL storage. Types
|
||||
/// can share type tags, because backing SQL storage is able to differentiate between some types
|
||||
/// (e.g., longs and doubles), and so distinct tags aren't necessary. That association is defined by
|
||||
/// `SQLValueType`. That trait similarly extends to `ValueTypeSet`, which maps a collection of types
|
||||
/// into a collection of tags.
|
||||
pub trait SQLValueTypeSet {
|
||||
fn value_type_tags(&self) -> BTreeSet<ValueTypeTag>;
|
||||
fn has_unique_type_tag(&self) -> bool;
|
||||
fn unique_type_tag(&self) -> Option<ValueTypeTag>;
|
||||
}
|
||||
|
||||
impl SQLValueTypeSet for ValueTypeSet {
|
||||
// This is inefficient, but it'll do for now.
|
||||
fn value_type_tags(&self) -> BTreeSet<ValueTypeTag> {
|
||||
let mut out = BTreeSet::new();
|
||||
for t in self.0.iter() {
|
||||
out.insert(t.value_type_tag());
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn unique_type_tag(&self) -> Option<ValueTypeTag> {
|
||||
if self.is_unit() || self.has_unique_type_tag() {
|
||||
self.exemplar().map(|t| t.value_type_tag())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn has_unique_type_tag(&self) -> bool {
|
||||
if self.is_unit() {
|
||||
return true;
|
||||
}
|
||||
|
||||
let mut acc = BTreeSet::new();
|
||||
for t in self.0.iter() {
|
||||
if acc.insert(t.value_type_tag()) && acc.len() > 1 {
|
||||
// We inserted a second or subsequent value.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
!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));
|
||||
}
|
||||
}
|
34
core/src/tx_report.rs
Normal file
34
core/src/tx_report.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
// 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.
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use core_traits::Entid;
|
||||
|
||||
use crate::{DateTime, Utc};
|
||||
|
||||
/// A transaction report summarizes an applied transaction.
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||
pub struct TxReport {
|
||||
/// The transaction ID of the transaction.
|
||||
pub tx_id: Entid,
|
||||
|
||||
/// The timestamp when the transaction began to be committed.
|
||||
pub tx_instant: DateTime<Utc>,
|
||||
|
||||
/// A map from string literal tempid to resolved or allocated entid.
|
||||
///
|
||||
/// Every string literal tempid presented to the transactor either resolves via upsert to an
|
||||
/// existing entid, or is allocated a new entid. (It is possible for multiple distinct string
|
||||
/// literal tempids to all unify to a single freshly allocated entid.)
|
||||
pub tempids: BTreeMap<String, Entid>,
|
||||
}
|
11
core/src/types.rs
Normal file
11
core/src/types.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
// 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.
|
||||
|
||||
pub type ValueTypeTag = i32;
|
90
core/src/util.rs
Normal file
90
core/src/util.rs
Normal file
|
@ -0,0 +1,90 @@
|
|||
// 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.
|
||||
|
||||
/// Side-effect chaining on `Result`.
|
||||
pub trait ResultEffect<T> {
|
||||
/// Invoke `f` if `self` is `Ok`, returning `self`.
|
||||
fn when_ok<F: FnOnce()>(self, f: F) -> Self;
|
||||
|
||||
/// Invoke `f` if `self` is `Err`, returning `self`.
|
||||
fn when_err<F: FnOnce()>(self, f: F) -> Self;
|
||||
}
|
||||
|
||||
impl<T, E> ResultEffect<T> for Result<T, E> {
|
||||
fn when_ok<F: FnOnce()>(self, f: F) -> Self {
|
||||
if self.is_ok() {
|
||||
f();
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
fn when_err<F: FnOnce()>(self, f: F) -> Self {
|
||||
if self.is_err() {
|
||||
f();
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Side-effect chaining on `Option`.
|
||||
pub trait OptionEffect<T> {
|
||||
/// Invoke `f` if `self` is `None`, returning `self`.
|
||||
fn when_none<F: FnOnce()>(self, f: F) -> Self;
|
||||
|
||||
/// Invoke `f` if `self` is `Some`, returning `self`.
|
||||
fn when_some<F: FnOnce()>(self, f: F) -> Self;
|
||||
}
|
||||
|
||||
impl<T> OptionEffect<T> for Option<T> {
|
||||
fn when_none<F: FnOnce()>(self, f: F) -> Self {
|
||||
if self.is_none() {
|
||||
f();
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
fn when_some<F: FnOnce()>(self, f: F) -> Self {
|
||||
if self.is_some() {
|
||||
f();
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||
pub enum Either<L, R> {
|
||||
Left(L),
|
||||
Right(R),
|
||||
}
|
||||
|
||||
// Cribbed from https://github.com/bluss/either/blob/f793721f3fdeb694f009e731b23a2858286bc0d6/src/lib.rs#L219-L259.
|
||||
impl<L, R> Either<L, R> {
|
||||
pub fn map_left<F, M>(self, f: F) -> Either<M, R>
|
||||
where
|
||||
F: FnOnce(L) -> M,
|
||||
{
|
||||
use self::Either::*;
|
||||
match self {
|
||||
Left(l) => Left(f(l)),
|
||||
Right(r) => Right(r),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_right<F, S>(self, f: F) -> Either<L, S>
|
||||
where
|
||||
F: FnOnce(R) -> S,
|
||||
{
|
||||
use self::Either::*;
|
||||
match self {
|
||||
Left(l) => Left(l),
|
||||
Right(r) => Right(f(r)),
|
||||
}
|
||||
}
|
||||
}
|
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"]
|
300
db-traits/errors.rs
Normal file
300
db-traits/errors.rs
Normal file
|
@ -0,0 +1,300 @@
|
|||
// 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 failure::{Backtrace, Context, Fail};
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use rusqlite;
|
||||
|
||||
use edn::entities::TempId;
|
||||
|
||||
use core_traits::{Entid, KnownEntid, TypedValue, ValueType};
|
||||
|
||||
pub type Result<T> = ::std::result::Result<T, DbError>;
|
||||
|
||||
// TODO Error/ErrorKind pair
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum CardinalityConflict {
|
||||
/// A cardinality one attribute has multiple assertions `[e a v1], [e a v2], ...`.
|
||||
CardinalityOneAddConflict {
|
||||
e: Entid,
|
||||
a: Entid,
|
||||
vs: BTreeSet<TypedValue>,
|
||||
},
|
||||
|
||||
/// A datom has been both asserted and retracted, like `[:db/add e a v]` and `[:db/retract e a v]`.
|
||||
AddRetractConflict {
|
||||
e: Entid,
|
||||
a: Entid,
|
||||
vs: BTreeSet<TypedValue>,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO Error/ErrorKind pair
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Fail)]
|
||||
pub enum SchemaConstraintViolation {
|
||||
/// A transaction tried to assert datoms where one tempid upserts to two (or more) distinct
|
||||
/// entids.
|
||||
ConflictingUpserts {
|
||||
/// A map from tempid to the entids it would upsert to.
|
||||
///
|
||||
/// In the future, we might even be able to attribute the upserts to particular (reduced)
|
||||
/// datoms, i.e., to particular `[e a v]` triples that caused the constraint violation.
|
||||
/// Attributing constraint violations to input data is more difficult to the multiple
|
||||
/// rewriting passes the input undergoes.
|
||||
conflicting_upserts: BTreeMap<TempId, BTreeSet<KnownEntid>>,
|
||||
},
|
||||
|
||||
/// A transaction tried to assert a datom or datoms with the wrong value `v` type(s).
|
||||
TypeDisagreements {
|
||||
/// 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>,
|
||||
},
|
||||
|
||||
/// A transaction tried to assert datoms that don't observe the schema's cardinality constraints.
|
||||
CardinalityConflicts { conflicts: Vec<CardinalityConflict> },
|
||||
}
|
||||
|
||||
impl ::std::fmt::Display for SchemaConstraintViolation {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
use self::SchemaConstraintViolation::*;
|
||||
match self {
|
||||
ConflictingUpserts {
|
||||
ref conflicting_upserts,
|
||||
} => {
|
||||
writeln!(f, "conflicting upserts:")?;
|
||||
for (tempid, entids) in conflicting_upserts {
|
||||
writeln!(f, " tempid {:?} upserts to {:?}", tempid, entids)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
TypeDisagreements {
|
||||
ref conflicting_datoms,
|
||||
} => {
|
||||
writeln!(f, "type disagreements:")?;
|
||||
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
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
CardinalityConflicts { ref conflicts } => {
|
||||
writeln!(f, "cardinality conflicts:")?;
|
||||
for conflict in conflicts {
|
||||
writeln!(f, " {:?}", conflict)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)]
|
||||
pub enum InputError {
|
||||
/// Map notation included a bad `:db/id` value.
|
||||
BadDbId,
|
||||
|
||||
/// A value place cannot be interpreted as an entity place (for example, in nested map
|
||||
/// notation).
|
||||
BadEntityPlace,
|
||||
}
|
||||
|
||||
impl ::std::fmt::Display for InputError {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
use self::InputError::*;
|
||||
match self {
|
||||
BadDbId => {
|
||||
writeln!(f, ":db/id in map notation must either not be present or be an entid, an ident, or a tempid")
|
||||
}
|
||||
BadEntityPlace => {
|
||||
writeln!(f, "cannot convert value place into entity place")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DbError {
|
||||
inner: Context<DbErrorKind>,
|
||||
}
|
||||
|
||||
impl ::std::fmt::Display for DbError {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
::std::fmt::Display::fmt(&self.inner, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Fail for DbError {
|
||||
fn cause(&self) -> Option<&dyn Fail> {
|
||||
self.inner.cause()
|
||||
}
|
||||
|
||||
fn backtrace(&self) -> Option<&Backtrace> {
|
||||
self.inner.backtrace()
|
||||
}
|
||||
}
|
||||
|
||||
impl DbError {
|
||||
pub fn kind(&self) -> DbErrorKind {
|
||||
self.inner.get_context().clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DbErrorKind> for DbError {
|
||||
fn from(kind: DbErrorKind) -> Self {
|
||||
DbError {
|
||||
inner: Context::new(kind),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Context<DbErrorKind>> for DbError {
|
||||
fn from(inner: Context<DbErrorKind>) -> Self {
|
||||
DbError { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<rusqlite::Error> for DbError {
|
||||
fn from(error: rusqlite::Error) -> Self {
|
||||
DbError {
|
||||
inner: Context::new(DbErrorKind::RusqliteError(error.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Fail)]
|
||||
pub enum DbErrorKind {
|
||||
/// We're just not done yet. Recognized a feature that is not yet implemented.
|
||||
#[fail(display = "not yet implemented: {}", _0)]
|
||||
NotYetImplemented(String),
|
||||
|
||||
/// 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
|
||||
)]
|
||||
BadValuePair(String, ValueType),
|
||||
|
||||
/// We've got corrupt data in the SQL store: a value and value_type_tag don't line up.
|
||||
/// TODO _1.data_type()
|
||||
#[fail(display = "bad SQL (value_type_tag, value) pair: ({:?}, {:?})", _0, _1)]
|
||||
BadSQLValuePair(rusqlite::types::Value, i32),
|
||||
|
||||
/// 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 ...
|
||||
/// #[fail(display = "bad SQL store user_version: {}", _0)]
|
||||
/// BadSQLiteStoreVersion(i32),
|
||||
/// A bootstrap definition couldn't be parsed or installed. This is a programmer error, not
|
||||
/// a runtime error.
|
||||
#[fail(display = "bad bootstrap definition: {}", _0)]
|
||||
BadBootstrapDefinition(String),
|
||||
|
||||
/// A schema assertion couldn't be parsed.
|
||||
#[fail(display = "bad schema assertion: {}", _0)]
|
||||
BadSchemaAssertion(String),
|
||||
|
||||
/// An ident->entid mapping failed.
|
||||
#[fail(display = "no entid found for ident: {}", _0)]
|
||||
UnrecognizedIdent(String),
|
||||
|
||||
/// An entid->ident mapping failed.
|
||||
#[fail(display = "no ident found for entid: {}", _0)]
|
||||
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)]
|
||||
UnknownAttribute(Entid),
|
||||
|
||||
#[fail(display = "cannot reverse-cache non-unique attribute: {}", _0)]
|
||||
CannotCacheNonUniqueAttributeInReverse(Entid),
|
||||
|
||||
#[fail(display = "schema alteration failed: {}", _0)]
|
||||
SchemaAlterationFailed(String),
|
||||
|
||||
/// A transaction tried to violate a constraint of the schema of the Mentat store.
|
||||
#[fail(display = "schema constraint violation: {}", _0)]
|
||||
SchemaConstraintViolation(SchemaConstraintViolation),
|
||||
|
||||
/// The transaction was malformed in some way (that was not recognized at parse time; for
|
||||
/// example, in a way that is schema-dependent).
|
||||
#[fail(display = "transaction input error: {}", _0)]
|
||||
InputError(InputError),
|
||||
|
||||
#[fail(
|
||||
display = "Cannot transact a fulltext assertion with a typed value that is not :db/valueType :db.type/string"
|
||||
)]
|
||||
WrongTypeValueForFtsAssertion,
|
||||
|
||||
// SQL errors.
|
||||
#[fail(display = "could not update a cache")]
|
||||
CacheUpdateFailed,
|
||||
|
||||
#[fail(display = "Could not set_user_version")]
|
||||
CouldNotSetVersionPragma,
|
||||
|
||||
#[fail(display = "Could not get_user_version")]
|
||||
CouldNotGetVersionPragma,
|
||||
|
||||
#[fail(display = "Could not search!")]
|
||||
CouldNotSearch,
|
||||
|
||||
#[fail(display = "Could not insert transaction: failed to add datoms not already present")]
|
||||
TxInsertFailedToAddMissingDatoms,
|
||||
|
||||
#[fail(display = "Could not insert transaction: failed to retract datoms already present")]
|
||||
TxInsertFailedToRetractDatoms,
|
||||
|
||||
#[fail(display = "Could not update datoms: failed to retract datoms already present")]
|
||||
DatomsUpdateFailedToRetract,
|
||||
|
||||
#[fail(display = "Could not update datoms: failed to add datoms not already present")]
|
||||
DatomsUpdateFailedToAdd,
|
||||
|
||||
#[fail(display = "Failed to create temporary tables")]
|
||||
FailedToCreateTempTables,
|
||||
|
||||
#[fail(display = "Could not insert non-fts one statements into temporary search table!")]
|
||||
NonFtsInsertionIntoTempSearchTableFailed,
|
||||
|
||||
#[fail(display = "Could not insert fts values into fts table!")]
|
||||
FtsInsertionFailed,
|
||||
|
||||
#[fail(display = "Could not insert FTS statements into temporary search table!")]
|
||||
FtsInsertionIntoTempSearchTableFailed,
|
||||
|
||||
#[fail(display = "Could not drop FTS search ids!")]
|
||||
FtsFailedToDropSearchIds,
|
||||
|
||||
#[fail(display = "Could not update partition map")]
|
||||
FailedToUpdatePartitionMap,
|
||||
|
||||
#[fail(display = "Can't operate over mixed timelines")]
|
||||
TimelinesMixed,
|
||||
|
||||
#[fail(display = "Can't move transactions to a non-empty timeline")]
|
||||
TimelinesMoveToNonEmpty,
|
||||
|
||||
#[fail(display = "Supplied an invalid transaction range")]
|
||||
TimelinesInvalidRange,
|
||||
|
||||
// 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: {}", _0)]
|
||||
RusqliteError(String),
|
||||
}
|
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;
|
49
db/Cargo.toml
Normal file
49
db/Cargo.toml
Normal file
|
@ -0,0 +1,49 @@
|
|||
[package]
|
||||
name = "mentat_db"
|
||||
version = "0.0.2"
|
||||
workspace = ".."
|
||||
|
||||
[features]
|
||||
default = []
|
||||
sqlcipher = ["rusqlite/sqlcipher"]
|
||||
syncable = ["serde", "serde_json", "serde_derive"]
|
||||
|
||||
[dependencies]
|
||||
failure = "~0.1"
|
||||
indexmap = "~1.9"
|
||||
itertools = "~0.10"
|
||||
lazy_static = "~1.4"
|
||||
log = "~0.4"
|
||||
ordered-float = "~2.8"
|
||||
time = "~0.3"
|
||||
petgraph = "~0.6"
|
||||
serde = { version = "~1.0", optional = true }
|
||||
serde_json = { version = "~1.0", optional = true }
|
||||
serde_derive = { version = "~1.0", optional = true }
|
||||
|
||||
[dependencies.rusqlite]
|
||||
version = "~0.29"
|
||||
features = ["limits", "bundled"]
|
||||
|
||||
[dependencies.edn]
|
||||
path = "../edn"
|
||||
|
||||
[dependencies.mentat_core]
|
||||
path = "../core"
|
||||
|
||||
[dependencies.core_traits]
|
||||
path = "../core-traits"
|
||||
|
||||
[dependencies.db_traits]
|
||||
path = "../db-traits"
|
||||
|
||||
[dependencies.mentat_sql]
|
||||
path = "../sql"
|
||||
|
||||
# TODO: This should be in dev-dependencies.
|
||||
[dependencies.tabwriter]
|
||||
version = "~1.2"
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.9"
|
||||
#tabwriter = { version = "1.2.1" }
|
3
db/README.md
Normal file
3
db/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
This sub-crate implements the SQLite database layer: installing,
|
||||
managing, and migrating forward the SQL schema underlying the datom
|
||||
store.
|
89
db/src/add_retract_alter_set.rs
Normal file
89
db/src/add_retract_alter_set.rs
Normal file
|
@ -0,0 +1,89 @@
|
|||
// 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::collections::BTreeMap;
|
||||
|
||||
/// Witness assertions and retractions, folding (assertion, retraction) pairs into alterations.
|
||||
/// Assumes that no assertion or retraction will be witnessed more than once.
|
||||
///
|
||||
/// This keeps track of when we see a :db/add, a :db/retract, or both :db/add and :db/retract in
|
||||
/// some order.
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||
pub struct AddRetractAlterSet<K, V> {
|
||||
pub asserted: BTreeMap<K, V>,
|
||||
pub retracted: BTreeMap<K, V>,
|
||||
pub altered: BTreeMap<K, (V, V)>,
|
||||
}
|
||||
|
||||
impl<K, V> Default for AddRetractAlterSet<K, V>
|
||||
where
|
||||
K: Ord,
|
||||
{
|
||||
fn default() -> AddRetractAlterSet<K, V> {
|
||||
AddRetractAlterSet {
|
||||
asserted: BTreeMap::default(),
|
||||
retracted: BTreeMap::default(),
|
||||
altered: BTreeMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> AddRetractAlterSet<K, V>
|
||||
where
|
||||
K: Ord,
|
||||
{
|
||||
pub fn witness(&mut self, key: K, value: V, added: bool) {
|
||||
if added {
|
||||
if let Some(retracted_value) = self.retracted.remove(&key) {
|
||||
self.altered.insert(key, (retracted_value, value));
|
||||
} else {
|
||||
self.asserted.insert(key, value);
|
||||
}
|
||||
} else if let Some(asserted_value) = self.asserted.remove(&key) {
|
||||
self.altered.insert(key, (value, asserted_value));
|
||||
} else {
|
||||
self.retracted.insert(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let mut set: AddRetractAlterSet<i64, char> = AddRetractAlterSet::default();
|
||||
// Assertion.
|
||||
set.witness(1, 'a', true);
|
||||
// Retraction.
|
||||
set.witness(2, 'b', false);
|
||||
// Alteration.
|
||||
set.witness(3, 'c', true);
|
||||
set.witness(3, 'd', false);
|
||||
// Alteration, witnessed in the with the retraction before the assertion.
|
||||
set.witness(4, 'e', false);
|
||||
set.witness(4, 'f', true);
|
||||
|
||||
let mut asserted = BTreeMap::default();
|
||||
asserted.insert(1, 'a');
|
||||
let mut retracted = BTreeMap::default();
|
||||
retracted.insert(2, 'b');
|
||||
let mut altered = BTreeMap::default();
|
||||
altered.insert(3, ('d', 'c'));
|
||||
altered.insert(4, ('e', 'f'));
|
||||
|
||||
assert_eq!(set.asserted, asserted);
|
||||
assert_eq!(set.retracted, retracted);
|
||||
assert_eq!(set.altered, altered);
|
||||
}
|
||||
}
|
382
db/src/bootstrap.rs
Normal file
382
db/src/bootstrap.rs
Normal file
|
@ -0,0 +1,382 @@
|
|||
// 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 crate::db::TypedSQLValue;
|
||||
use crate::entids;
|
||||
use db_traits::errors::{DbErrorKind, Result};
|
||||
use edn;
|
||||
use edn::entities::Entity;
|
||||
use edn::symbols;
|
||||
use edn::types::Value;
|
||||
|
||||
use core_traits::{values, TypedValue};
|
||||
|
||||
use crate::schema::SchemaBuilding;
|
||||
use crate::types::{Partition, PartitionMap};
|
||||
use mentat_core::{IdentMap, Schema};
|
||||
|
||||
/// The first transaction ID applied to the knowledge base.
|
||||
///
|
||||
/// This is the start of the :db.part/tx partition.
|
||||
pub const TX0: i64 = 0x1000_0000;
|
||||
|
||||
/// This is the start of the :db.part/user partition.
|
||||
pub const USER0: i64 = 0x10000;
|
||||
|
||||
// Corresponds to the version of the :db.schema/core vocabulary.
|
||||
pub const CORE_SCHEMA_VERSION: u32 = 1;
|
||||
|
||||
lazy_static! {
|
||||
static ref V1_IDENTS: [(symbols::Keyword, i64); 40] = {
|
||||
[
|
||||
(ns_keyword!("db", "ident"), entids::DB_IDENT),
|
||||
(ns_keyword!("db.part", "db"), entids::DB_PART_DB),
|
||||
(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", "attribute"),
|
||||
entids::DB_INSTALL_ATTRIBUTE,
|
||||
),
|
||||
(ns_keyword!("db", "valueType"), entids::DB_VALUE_TYPE),
|
||||
(ns_keyword!("db", "cardinality"), entids::DB_CARDINALITY),
|
||||
(ns_keyword!("db", "unique"), entids::DB_UNIQUE),
|
||||
(ns_keyword!("db", "isComponent"), entids::DB_IS_COMPONENT),
|
||||
(ns_keyword!("db", "index"), entids::DB_INDEX),
|
||||
(ns_keyword!("db", "fulltext"), entids::DB_FULLTEXT),
|
||||
(ns_keyword!("db", "noHistory"), entids::DB_NO_HISTORY),
|
||||
(ns_keyword!("db", "add"), entids::DB_ADD),
|
||||
(ns_keyword!("db", "retract"), entids::DB_RETRACT),
|
||||
(ns_keyword!("db.part", "user"), entids::DB_PART_USER),
|
||||
(ns_keyword!("db.part", "tx"), entids::DB_PART_TX),
|
||||
(ns_keyword!("db", "excise"), entids::DB_EXCISE),
|
||||
(ns_keyword!("db.excise", "attrs"), entids::DB_EXCISE_ATTRS),
|
||||
(
|
||||
ns_keyword!("db.excise", "beforeT"),
|
||||
entids::DB_EXCISE_BEFORE_T,
|
||||
),
|
||||
(ns_keyword!("db.excise", "before"), entids::DB_EXCISE_BEFORE),
|
||||
(
|
||||
ns_keyword!("db.alter", "attribute"),
|
||||
entids::DB_ALTER_ATTRIBUTE,
|
||||
),
|
||||
(ns_keyword!("db.type", "ref"), entids::DB_TYPE_REF),
|
||||
(ns_keyword!("db.type", "keyword"), entids::DB_TYPE_KEYWORD),
|
||||
(ns_keyword!("db.type", "long"), entids::DB_TYPE_LONG),
|
||||
(ns_keyword!("db.type", "double"), entids::DB_TYPE_DOUBLE),
|
||||
(ns_keyword!("db.type", "string"), entids::DB_TYPE_STRING),
|
||||
(ns_keyword!("db.type", "uuid"), entids::DB_TYPE_UUID),
|
||||
(ns_keyword!("db.type", "uri"), entids::DB_TYPE_URI),
|
||||
(ns_keyword!("db.type", "boolean"), entids::DB_TYPE_BOOLEAN),
|
||||
(ns_keyword!("db.type", "instant"), entids::DB_TYPE_INSTANT),
|
||||
(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.unique", "value"), entids::DB_UNIQUE_VALUE),
|
||||
(
|
||||
ns_keyword!("db.unique", "identity"),
|
||||
entids::DB_UNIQUE_IDENTITY,
|
||||
),
|
||||
(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", "core"), entids::DB_SCHEMA_CORE),
|
||||
]
|
||||
};
|
||||
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", "user"), USER0, TX0 - 1, USER0, true),
|
||||
(
|
||||
ns_keyword!("db.part", "tx"),
|
||||
TX0,
|
||||
i64::max_value(),
|
||||
TX0,
|
||||
false,
|
||||
),
|
||||
]
|
||||
};
|
||||
static ref V1_CORE_SCHEMA: [symbols::Keyword; 16] = {
|
||||
[
|
||||
(ns_keyword!("db", "ident")),
|
||||
(ns_keyword!("db.install", "partition")),
|
||||
(ns_keyword!("db.install", "valueType")),
|
||||
(ns_keyword!("db.install", "attribute")),
|
||||
(ns_keyword!("db", "txInstant")),
|
||||
(ns_keyword!("db", "valueType")),
|
||||
(ns_keyword!("db", "cardinality")),
|
||||
(ns_keyword!("db", "doc")),
|
||||
(ns_keyword!("db", "unique")),
|
||||
(ns_keyword!("db", "isComponent")),
|
||||
(ns_keyword!("db", "index")),
|
||||
(ns_keyword!("db", "fulltext")),
|
||||
(ns_keyword!("db", "noHistory")),
|
||||
(ns_keyword!("db.alter", "attribute")),
|
||||
(ns_keyword!("db.schema", "version")),
|
||||
(ns_keyword!("db.schema", "attribute")),
|
||||
]
|
||||
};
|
||||
static ref V1_SYMBOLIC_SCHEMA: Value = {
|
||||
let s = r#"
|
||||
{:db/ident {:db/valueType :db.type/keyword
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/index true
|
||||
:db/unique :db.unique/identity}
|
||||
:db.install/partition {:db/valueType :db.type/ref
|
||||
:db/cardinality :db.cardinality/many}
|
||||
:db.install/valueType {:db/valueType :db.type/ref
|
||||
:db/cardinality :db.cardinality/many}
|
||||
:db.install/attribute {:db/valueType :db.type/ref
|
||||
:db/cardinality :db.cardinality/many}
|
||||
;; TODO: support user-specified functions in the future.
|
||||
;; :db.install/function {:db/valueType :db.type/ref
|
||||
;; :db/cardinality :db.cardinality/many}
|
||||
:db/txInstant {:db/valueType :db.type/instant
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/index true}
|
||||
:db/valueType {:db/valueType :db.type/ref
|
||||
:db/cardinality :db.cardinality/one}
|
||||
:db/cardinality {:db/valueType :db.type/ref
|
||||
:db/cardinality :db.cardinality/one}
|
||||
:db/doc {:db/valueType :db.type/string
|
||||
:db/cardinality :db.cardinality/one}
|
||||
:db/unique {:db/valueType :db.type/ref
|
||||
:db/cardinality :db.cardinality/one}
|
||||
:db/isComponent {:db/valueType :db.type/boolean
|
||||
:db/cardinality :db.cardinality/one}
|
||||
:db/index {:db/valueType :db.type/boolean
|
||||
:db/cardinality :db.cardinality/one}
|
||||
:db/fulltext {:db/valueType :db.type/boolean
|
||||
:db/cardinality :db.cardinality/one}
|
||||
:db/noHistory {:db/valueType :db.type/boolean
|
||||
:db/cardinality :db.cardinality/one}
|
||||
:db.alter/attribute {:db/valueType :db.type/ref
|
||||
:db/cardinality :db.cardinality/many}
|
||||
:db.schema/version {:db/valueType :db.type/long
|
||||
:db/cardinality :db.cardinality/one}
|
||||
|
||||
;; unique-value because an attribute can only belong to a single
|
||||
;; schema fragment.
|
||||
:db.schema/attribute {:db/valueType :db.type/ref
|
||||
:db/index true
|
||||
:db/unique :db.unique/value
|
||||
:db/cardinality :db.cardinality/many}}"#;
|
||||
edn::parse::value(s)
|
||||
.map(|v| v.without_spans())
|
||||
.map_err(|_| {
|
||||
DbErrorKind::BadBootstrapDefinition("Unable to parse V1_SYMBOLIC_SCHEMA".into())
|
||||
})
|
||||
.unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
/// Convert (ident, entid) pairs into [:db/add IDENT :db/ident IDENT] `Value` instances.
|
||||
fn idents_to_assertions(idents: &[(symbols::Keyword, i64)]) -> Vec<Value> {
|
||||
idents
|
||||
.iter()
|
||||
.map(|&(ref ident, _)| {
|
||||
let value = Value::Keyword(ident.clone());
|
||||
Value::Vector(vec![
|
||||
values::DB_ADD.clone(),
|
||||
value.clone(),
|
||||
values::DB_IDENT.clone(),
|
||||
value,
|
||||
])
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Convert an ident list into [:db/add :db.schema/core :db.schema/attribute IDENT] `Value` instances.
|
||||
fn schema_attrs_to_assertions(version: u32, idents: &[symbols::Keyword]) -> Vec<Value> {
|
||||
let schema_core = Value::Keyword(ns_keyword!("db.schema", "core"));
|
||||
let schema_attr = Value::Keyword(ns_keyword!("db.schema", "attribute"));
|
||||
let schema_version = Value::Keyword(ns_keyword!("db.schema", "version"));
|
||||
idents
|
||||
.iter()
|
||||
.map(|ident| {
|
||||
let value = Value::Keyword(ident.clone());
|
||||
Value::Vector(vec![
|
||||
values::DB_ADD.clone(),
|
||||
schema_core.clone(),
|
||||
schema_attr.clone(),
|
||||
value,
|
||||
])
|
||||
})
|
||||
.chain(::std::iter::once(Value::Vector(vec![
|
||||
values::DB_ADD.clone(),
|
||||
schema_core.clone(),
|
||||
schema_version,
|
||||
Value::Integer(version as i64),
|
||||
])))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Convert {:ident {:key :value ...} ...} to
|
||||
/// vec![(symbols::Keyword(:ident), symbols::Keyword(:key), TypedValue(:value)), ...].
|
||||
///
|
||||
/// Such triples are closer to what the transactor will produce when processing attribute
|
||||
/// assertions.
|
||||
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.
|
||||
let mut triples: Vec<(symbols::Keyword, symbols::Keyword, TypedValue)> = vec![];
|
||||
// TODO: Consider `flat_map` and `map` rather than loop.
|
||||
match *symbolic_schema {
|
||||
Value::Map(ref m) => {
|
||||
for (ident, mp) in m {
|
||||
let ident = match ident {
|
||||
Value::Keyword(ref ident) => ident,
|
||||
_ => bail!(DbErrorKind::BadBootstrapDefinition(format!(
|
||||
"Expected namespaced keyword for ident but got '{:?}'",
|
||||
ident
|
||||
))),
|
||||
};
|
||||
match *mp {
|
||||
Value::Map(ref mpp) => {
|
||||
for (attr, value) in mpp {
|
||||
let attr = match attr {
|
||||
Value::Keyword(ref attr) => attr,
|
||||
_ => bail!(DbErrorKind::BadBootstrapDefinition(format!(
|
||||
"Expected namespaced keyword for attr but got '{:?}'",
|
||||
attr
|
||||
))),
|
||||
};
|
||||
|
||||
// We have symbolic idents but the transactor handles entids. Ad-hoc
|
||||
// convert right here. This is a fundamental limitation on the
|
||||
// bootstrap symbolic schema format; we can't represent "real" keywords
|
||||
// at this time.
|
||||
//
|
||||
// TODO: remove this limitation, perhaps by including a type tag in the
|
||||
// bootstrap symbolic schema, or by representing the initial bootstrap
|
||||
// schema directly as Rust data.
|
||||
let typed_value = match TypedValue::from_edn_value(value) {
|
||||
Some(TypedValue::Keyword(ref k)) => ident_map
|
||||
.get(k)
|
||||
.map(|entid| TypedValue::Ref(*entid))
|
||||
.ok_or_else(|| DbErrorKind::UnrecognizedIdent(k.to_string()))?,
|
||||
Some(v) => v,
|
||||
_ => bail!(DbErrorKind::BadBootstrapDefinition(format!(
|
||||
"Expected Mentat typed value for value but got '{:?}'",
|
||||
value
|
||||
))),
|
||||
};
|
||||
|
||||
triples.push((ident.clone(), attr.clone(), typed_value));
|
||||
}
|
||||
}
|
||||
_ => bail!(DbErrorKind::BadBootstrapDefinition(
|
||||
"Expected {:db/ident {:db/attr value ...} ...}".into()
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => bail!(DbErrorKind::BadBootstrapDefinition("Expected {...}".into())),
|
||||
}
|
||||
Ok(triples)
|
||||
}
|
||||
|
||||
/// Convert {IDENT {:key :value ...} ...} to [[:db/add IDENT :key :value] ...].
|
||||
fn symbolic_schema_to_assertions(symbolic_schema: &Value) -> Result<Vec<Value>> {
|
||||
// Failure here is a coding error, not a runtime error.
|
||||
let mut assertions: Vec<Value> = vec![];
|
||||
match *symbolic_schema {
|
||||
Value::Map(ref m) => {
|
||||
for (ident, mp) in m {
|
||||
match *mp {
|
||||
Value::Map(ref mpp) => {
|
||||
for (attr, value) in mpp {
|
||||
assertions.push(Value::Vector(vec![
|
||||
values::DB_ADD.clone(),
|
||||
ident.clone(),
|
||||
attr.clone(),
|
||||
value.clone(),
|
||||
]));
|
||||
}
|
||||
}
|
||||
_ => bail!(DbErrorKind::BadBootstrapDefinition(
|
||||
"Expected {:db/ident {:db/attr value ...} ...}".into()
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => bail!(DbErrorKind::BadBootstrapDefinition("Expected {...}".into())),
|
||||
}
|
||||
Ok(assertions)
|
||||
}
|
||||
|
||||
pub(crate) fn bootstrap_partition_map() -> PartitionMap {
|
||||
V1_PARTS
|
||||
.iter()
|
||||
.map(|&(ref part, start, end, index, allow_excision)| {
|
||||
(
|
||||
part.to_string(),
|
||||
Partition::new(start, end, index, allow_excision),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn bootstrap_ident_map() -> IdentMap {
|
||||
V1_IDENTS
|
||||
.iter()
|
||||
.map(|&(ref ident, entid)| (ident.clone(), entid))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn bootstrap_schema() -> Schema {
|
||||
let ident_map = bootstrap_ident_map();
|
||||
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()
|
||||
}
|
||||
|
||||
pub(crate) fn bootstrap_entities() -> Vec<Entity<edn::ValueAndSpan>> {
|
||||
let bootstrap_assertions: Value = Value::Vector(
|
||||
[
|
||||
symbolic_schema_to_assertions(&V1_SYMBOLIC_SCHEMA).expect("symbolic schema"),
|
||||
idents_to_assertions(&V1_IDENTS[..]),
|
||||
schema_attrs_to_assertions(CORE_SCHEMA_VERSION, V1_CORE_SCHEMA.as_ref()),
|
||||
]
|
||||
.concat(),
|
||||
);
|
||||
|
||||
// Failure here is a coding error (since the inputs are fixed), not a runtime error.
|
||||
// TODO: represent these bootstrap entity data errors rather than just panicing.
|
||||
edn::parse::entities(&bootstrap_assertions.to_string()).expect("bootstrap assertions")
|
||||
}
|
2017
db/src/cache.rs
Normal file
2017
db/src/cache.rs
Normal file
File diff suppressed because it is too large
Load diff
3398
db/src/db.rs
Normal file
3398
db/src/db.rs
Normal file
File diff suppressed because it is too large
Load diff
544
db/src/debug.rs
Normal file
544
db/src/debug.rs
Normal file
|
@ -0,0 +1,544 @@
|
|||
// 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)]
|
||||
#![allow(unused_macros)]
|
||||
|
||||
/// Low-level functions for testing.
|
||||
|
||||
// Macro to parse a `Borrow<str>` to an `edn::Value` and assert the given `edn::Value` `matches`
|
||||
// against it.
|
||||
//
|
||||
// This is a macro only to give nice line numbers when tests fail.
|
||||
#[macro_export]
|
||||
macro_rules! assert_matches {
|
||||
( $input: expr, $expected: expr ) => {{
|
||||
// Failure to parse the expected pattern is a coding error, so we unwrap.
|
||||
let pattern_value = edn::parse::value($expected.borrow())
|
||||
.expect(format!("to be able to parse expected {}", $expected).as_str())
|
||||
.without_spans();
|
||||
let input_value = $input.to_edn();
|
||||
assert!(
|
||||
input_value.matches(&pattern_value),
|
||||
"Expected value:\n{}\nto match pattern:\n{}\n",
|
||||
input_value.to_pretty(120).unwrap(),
|
||||
pattern_value.to_pretty(120).unwrap()
|
||||
);
|
||||
}};
|
||||
}
|
||||
|
||||
// Transact $input against the given $conn, expecting success or a `Result<TxReport, String>`.
|
||||
//
|
||||
// This unwraps safely and makes asserting errors pleasant.
|
||||
#[macro_export]
|
||||
macro_rules! assert_transact {
|
||||
( $conn: expr, $input: expr, $expected: expr ) => {{
|
||||
trace!("assert_transact: {}", $input);
|
||||
let result = $conn.transact($input).map_err(|e| e.to_string());
|
||||
assert_eq!(result, $expected.map_err(|e| e.to_string()));
|
||||
}};
|
||||
( $conn: expr, $input: expr ) => {{
|
||||
trace!("assert_transact: {}", $input);
|
||||
let result = $conn.transact($input);
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Expected Ok(_), got `{}`",
|
||||
result.unwrap_err()
|
||||
);
|
||||
result.unwrap()
|
||||
}};
|
||||
}
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::BTreeMap;
|
||||
use std::io::Write;
|
||||
|
||||
use itertools::Itertools;
|
||||
use rusqlite;
|
||||
use rusqlite::types::ToSql;
|
||||
use rusqlite::TransactionBehavior;
|
||||
use tabwriter::TabWriter;
|
||||
|
||||
use crate::bootstrap;
|
||||
use crate::db::*;
|
||||
use crate::db::{read_attribute_map, read_ident_map};
|
||||
use crate::entids;
|
||||
use db_traits::errors::Result;
|
||||
use edn;
|
||||
|
||||
use core_traits::{Entid, TypedValue, ValueType};
|
||||
|
||||
use crate::internal_types::TermWithTempIds;
|
||||
use crate::schema::SchemaBuilding;
|
||||
use crate::tx::{transact, transact_terms};
|
||||
use crate::types::*;
|
||||
use crate::watcher::NullWatcher;
|
||||
use edn::entities::{EntidOrIdent, TempId};
|
||||
use edn::InternSet;
|
||||
use mentat_core::{HasSchema, SQLValueType, TxReport};
|
||||
|
||||
/// Represents a *datom* (assertion) in the store.
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||
pub struct Datom {
|
||||
// TODO: generalize this.
|
||||
pub e: EntidOrIdent,
|
||||
pub a: EntidOrIdent,
|
||||
pub v: edn::Value,
|
||||
pub tx: i64,
|
||||
pub added: Option<bool>,
|
||||
}
|
||||
|
||||
/// Represents a set of datoms (assertions) in the store.
|
||||
///
|
||||
/// To make comparision easier, we deterministically order. The ordering is the ascending tuple
|
||||
/// ordering determined by `(e, a, (value_type_tag, v), tx)`, where `value_type_tag` is an internal
|
||||
/// value that is not exposed but is deterministic.
|
||||
pub struct Datoms(pub Vec<Datom>);
|
||||
|
||||
/// Represents an ordered sequence of transactions in the store.
|
||||
///
|
||||
/// To make comparision easier, we deterministically order. The ordering is the ascending tuple
|
||||
/// ordering determined by `(e, a, (value_type_tag, v), tx, added)`, where `value_type_tag` is an
|
||||
/// internal value that is not exposed but is deterministic, and `added` is ordered such that
|
||||
/// retracted assertions appear before added assertions.
|
||||
pub struct Transactions(pub Vec<Datoms>);
|
||||
|
||||
/// Represents the fulltext values in the store.
|
||||
pub struct FulltextValues(pub Vec<(i64, String)>);
|
||||
|
||||
impl Datom {
|
||||
pub fn to_edn(&self) -> edn::Value {
|
||||
let f = |entid: &EntidOrIdent| -> edn::Value {
|
||||
match *entid {
|
||||
EntidOrIdent::Entid(ref y) => edn::Value::Integer(*y),
|
||||
EntidOrIdent::Ident(ref y) => edn::Value::Keyword(y.clone()),
|
||||
}
|
||||
};
|
||||
|
||||
let mut v = vec![f(&self.e), f(&self.a), self.v.clone()];
|
||||
if let Some(added) = self.added {
|
||||
v.push(edn::Value::Integer(self.tx));
|
||||
v.push(edn::Value::Boolean(added));
|
||||
}
|
||||
|
||||
edn::Value::Vector(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl Datoms {
|
||||
pub fn to_edn(&self) -> edn::Value {
|
||||
edn::Value::Vector((&self.0).iter().map(|x| x.to_edn()).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl Transactions {
|
||||
pub fn to_edn(&self) -> edn::Value {
|
||||
edn::Value::Vector((&self.0).iter().map(|x| x.to_edn()).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl FulltextValues {
|
||||
pub fn to_edn(&self) -> edn::Value {
|
||||
edn::Value::Vector(
|
||||
(&self.0)
|
||||
.iter()
|
||||
.map(|&(x, ref y)| {
|
||||
edn::Value::Vector(vec![edn::Value::Integer(x), edn::Value::Text(y.clone())])
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Turn TypedValue::Ref into TypedValue::Keyword when it is possible.
|
||||
trait ToIdent {
|
||||
fn map_ident(self, schema: &Schema) -> Self;
|
||||
}
|
||||
|
||||
impl ToIdent for TypedValue {
|
||||
fn map_ident(self, schema: &Schema) -> Self {
|
||||
if let TypedValue::Ref(e) = self {
|
||||
schema
|
||||
.get_ident(e)
|
||||
.cloned()
|
||||
.map(|i| i.into())
|
||||
.unwrap_or(TypedValue::Ref(e))
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a numeric entid to an ident `Entid` if possible, otherwise a numeric `Entid`.
|
||||
pub fn to_entid(schema: &Schema, entid: i64) -> EntidOrIdent {
|
||||
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`.
|
||||
// pub fn to_ident(schema: &Schema, entid: i64) -> Entid {
|
||||
// schema.get_ident(entid).map_or(Entid::Entid(entid), |ident| Entid::Ident(ident.clone()))
|
||||
// }
|
||||
|
||||
/// Return the set of datoms in the store, ordered by (e, a, v, tx), but not including any datoms of
|
||||
/// the form [... :db/txInstant ...].
|
||||
pub fn datoms<S: Borrow<Schema>>(conn: &rusqlite::Connection, schema: &S) -> Result<Datoms> {
|
||||
datoms_after(conn, schema, bootstrap::TX0 - 1)
|
||||
}
|
||||
|
||||
/// Return the set of datoms in the store with transaction ID strictly greater than the given `tx`,
|
||||
/// ordered by (e, a, v, tx).
|
||||
///
|
||||
/// 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> {
|
||||
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 r: Result<Vec<_>> = stmt
|
||||
.query_and_then(&[&tx], |row| {
|
||||
let e: i64 = row.get(0)?;
|
||||
let a: i64 = row.get(1)?;
|
||||
|
||||
if a == entids::DB_TX_INSTANT {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let v: rusqlite::types::Value = row.get(2)?;
|
||||
let value_type_tag: i32 = row.get(3)?;
|
||||
|
||||
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 typed_value =
|
||||
TypedValue::from_sql_value_pair(v, value_type_tag)?.map_ident(borrowed_schema);
|
||||
let (value, _) = typed_value.to_edn_value_pair();
|
||||
|
||||
let tx: i64 = row.get(4)?;
|
||||
|
||||
Ok(Some(Datom {
|
||||
e: EntidOrIdent::Entid(e),
|
||||
a: to_entid(borrowed_schema, a),
|
||||
v: value,
|
||||
tx,
|
||||
added: None,
|
||||
}))
|
||||
})?
|
||||
.collect();
|
||||
|
||||
Ok(Datoms(r?.into_iter().filter_map(|x| x).collect()))
|
||||
}
|
||||
|
||||
/// Return the sequence of transactions in the store with transaction ID strictly greater than the
|
||||
/// given `tx`, ordered by (tx, e, a, v).
|
||||
///
|
||||
/// 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> {
|
||||
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 r: Result<Vec<_>> = stmt
|
||||
.query_and_then(&[&tx], |row| {
|
||||
let e: i64 = row.get(0)?;
|
||||
let a: i64 = row.get(1)?;
|
||||
|
||||
let v: rusqlite::types::Value = row.get(2)?;
|
||||
let value_type_tag: i32 = row.get(3)?;
|
||||
|
||||
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 typed_value =
|
||||
TypedValue::from_sql_value_pair(v, value_type_tag)?.map_ident(borrowed_schema);
|
||||
let (value, _) = typed_value.to_edn_value_pair();
|
||||
|
||||
let tx: i64 = row.get(4)?;
|
||||
let added: bool = row.get(5)?;
|
||||
|
||||
Ok(Datom {
|
||||
e: EntidOrIdent::Entid(e),
|
||||
a: to_entid(borrowed_schema, a),
|
||||
v: value,
|
||||
tx,
|
||||
added: Some(added),
|
||||
})
|
||||
})?
|
||||
.collect();
|
||||
|
||||
// Group by tx.
|
||||
let r: Vec<Datoms> = r?
|
||||
.into_iter()
|
||||
.group_by(|x| x.tx)
|
||||
.into_iter()
|
||||
.map(|(_key, group)| Datoms(group.collect()))
|
||||
.collect();
|
||||
Ok(Transactions(r))
|
||||
}
|
||||
|
||||
/// Return the set of fulltext values in the store, ordered by rowid.
|
||||
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 r: Result<Vec<_>> = stmt
|
||||
.query_and_then([], |row| {
|
||||
let rowid: i64 = row.get(0)?;
|
||||
let text: String = row.get(1)?;
|
||||
Ok((rowid, text))
|
||||
})?
|
||||
.collect();
|
||||
|
||||
r.map(FulltextValues)
|
||||
}
|
||||
|
||||
/// Execute the given `sql` query with the given `params` and format the results as a
|
||||
/// tab-and-newline formatted string suitable for debug printing.
|
||||
///
|
||||
/// 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.
|
||||
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 tw = TabWriter::new(Vec::new()).padding(2);
|
||||
writeln!(&mut tw, "{}", sql).unwrap();
|
||||
|
||||
for column_name in stmt.column_names() {
|
||||
write!(&mut tw, "{}\t", column_name).unwrap();
|
||||
}
|
||||
writeln!(&mut tw).unwrap();
|
||||
|
||||
let r: Result<Vec<_>> = stmt
|
||||
.query_and_then(params, |row| {
|
||||
for i in 0..row.as_ref().column_count() {
|
||||
let value: rusqlite::types::Value = row.get(i)?;
|
||||
write!(&mut tw, "{:?}\t", value).unwrap();
|
||||
}
|
||||
writeln!(&mut tw).unwrap();
|
||||
Ok(())
|
||||
})?
|
||||
.collect();
|
||||
r?;
|
||||
|
||||
let dump = String::from_utf8(tw.into_inner().unwrap()).unwrap();
|
||||
Ok(dump)
|
||||
}
|
||||
|
||||
// A connection that doesn't try to be clever about possibly sharing its `Schema`. Compare to
|
||||
// `mentat::Conn`.
|
||||
pub struct TestConn {
|
||||
pub sqlite: rusqlite::Connection,
|
||||
pub partition_map: PartitionMap,
|
||||
pub schema: Schema,
|
||||
}
|
||||
|
||||
impl TestConn {
|
||||
fn assert_materialized_views(&self) {
|
||||
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_schema = Schema::from_ident_map_and_attribute_map(
|
||||
materialized_ident_map,
|
||||
materialized_attribute_map,
|
||||
)
|
||||
.expect("schema");
|
||||
assert_eq!(materialized_schema, self.schema);
|
||||
}
|
||||
|
||||
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.
|
||||
let entities = edn::parse::entities(transaction.borrow()).unwrap_or_else(|_| {
|
||||
panic!("to be able to parse {} into entities", transaction.borrow())
|
||||
});
|
||||
|
||||
let details = {
|
||||
// The block scopes the borrow of self.sqlite.
|
||||
// We're about to write, so go straight ahead and get an IMMEDIATE transaction.
|
||||
let tx = self
|
||||
.sqlite
|
||||
.transaction_with_behavior(TransactionBehavior::Immediate)?;
|
||||
// Applying the transaction can fail, so we don't unwrap.
|
||||
let details = transact(
|
||||
&tx,
|
||||
self.partition_map.clone(),
|
||||
&self.schema,
|
||||
&self.schema,
|
||||
NullWatcher(),
|
||||
entities,
|
||||
)?;
|
||||
tx.commit()?;
|
||||
details
|
||||
};
|
||||
|
||||
let (report, next_partition_map, next_schema, _watcher) = details;
|
||||
self.partition_map = next_partition_map;
|
||||
if let Some(next_schema) = next_schema {
|
||||
self.schema = next_schema;
|
||||
}
|
||||
|
||||
// Verify that we've updated the materialized views during transacting.
|
||||
self.assert_materialized_views();
|
||||
|
||||
Ok(report)
|
||||
}
|
||||
|
||||
pub fn transact_simple_terms<I>(
|
||||
&mut self,
|
||||
terms: I,
|
||||
tempid_set: InternSet<TempId>,
|
||||
) -> Result<TxReport>
|
||||
where
|
||||
I: IntoIterator<Item = TermWithTempIds>,
|
||||
{
|
||||
let details = {
|
||||
// The block scopes the borrow of self.sqlite.
|
||||
// We're about to write, so go straight ahead and get an IMMEDIATE transaction.
|
||||
let tx = self
|
||||
.sqlite
|
||||
.transaction_with_behavior(TransactionBehavior::Immediate)?;
|
||||
// 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,
|
||||
)?;
|
||||
tx.commit()?;
|
||||
details
|
||||
};
|
||||
|
||||
let (report, next_partition_map, next_schema, _watcher) = details;
|
||||
self.partition_map = next_partition_map;
|
||||
if let Some(next_schema) = next_schema {
|
||||
self.schema = next_schema;
|
||||
}
|
||||
|
||||
// Verify that we've updated the materialized views during transacting.
|
||||
self.assert_materialized_views();
|
||||
|
||||
Ok(report)
|
||||
}
|
||||
|
||||
pub fn last_tx_id(&self) -> Entid {
|
||||
self.partition_map
|
||||
.get(&":db.part/tx".to_string())
|
||||
.unwrap()
|
||||
.next_entid()
|
||||
- 1
|
||||
}
|
||||
|
||||
pub fn last_transaction(&self) -> Datoms {
|
||||
transactions_after(&self.sqlite, &self.schema, self.last_tx_id() - 1)
|
||||
.expect("last_transaction")
|
||||
.0
|
||||
.pop()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn transactions(&self) -> Transactions {
|
||||
transactions_after(&self.sqlite, &self.schema, bootstrap::TX0).expect("transactions")
|
||||
}
|
||||
|
||||
pub fn datoms(&self) -> Datoms {
|
||||
datoms_after(&self.sqlite, &self.schema, bootstrap::TX0).expect("datoms")
|
||||
}
|
||||
|
||||
pub fn fulltext_values(&self) -> FulltextValues {
|
||||
fulltext_values(&self.sqlite).expect("fulltext_values")
|
||||
}
|
||||
|
||||
pub fn with_sqlite(mut conn: rusqlite::Connection) -> TestConn {
|
||||
let db = ensure_current_version(&mut conn).unwrap();
|
||||
|
||||
// Does not include :db/txInstant.
|
||||
let datoms = datoms_after(&conn, &db.schema, 0).unwrap();
|
||||
assert_eq!(datoms.0.len(), 94);
|
||||
|
||||
// Includes :db/txInstant.
|
||||
let transactions = transactions_after(&conn, &db.schema, 0).unwrap();
|
||||
assert_eq!(transactions.0.len(), 1);
|
||||
assert_eq!(transactions.0[0].0.len(), 95);
|
||||
|
||||
let mut parts = db.partition_map;
|
||||
|
||||
// Add a fake partition to allow tests to do things like
|
||||
// [:db/add 111 :foo/bar 222]
|
||||
{
|
||||
let fake_partition = Partition::new(100, 2000, 1000, true);
|
||||
parts.insert(":db.part/fake".into(), fake_partition);
|
||||
}
|
||||
|
||||
let test_conn = TestConn {
|
||||
sqlite: conn,
|
||||
partition_map: parts,
|
||||
schema: db.schema,
|
||||
};
|
||||
|
||||
// Verify that we've created the materialized views during bootstrapping.
|
||||
test_conn.assert_materialized_views();
|
||||
|
||||
test_conn
|
||||
}
|
||||
|
||||
pub fn sanitized_partition_map(&mut self) {
|
||||
self.partition_map.remove(":db.part/fake");
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TestConn {
|
||||
fn default() -> TestConn {
|
||||
TestConn::with_sqlite(new_connection("").expect("Couldn't open in-memory db"))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TempIds(edn::Value);
|
||||
|
||||
impl TempIds {
|
||||
pub fn to_edn(&self) -> edn::Value {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tempids(report: &TxReport) -> TempIds {
|
||||
let mut map: BTreeMap<edn::Value, edn::Value> = BTreeMap::default();
|
||||
for (tempid, &entid) in report.tempids.iter() {
|
||||
map.insert(edn::Value::Text(tempid.clone()), edn::Value::Integer(entid));
|
||||
}
|
||||
TempIds(edn::Value::Map(map))
|
||||
}
|
123
db/src/entids.rs
Normal file
123
db/src/entids.rs
Normal file
|
@ -0,0 +1,123 @@
|
|||
// 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)]
|
||||
|
||||
/// Literal `Entid` values in the the "db" namespace.
|
||||
///
|
||||
/// Used through-out the transactor to match core DB constructs.
|
||||
use core_traits::Entid;
|
||||
|
||||
// Added in SQL schema v1.
|
||||
pub const DB_IDENT: Entid = 1;
|
||||
pub const DB_PART_DB: Entid = 2;
|
||||
pub const DB_TX_INSTANT: Entid = 3;
|
||||
pub const DB_INSTALL_PARTITION: Entid = 4;
|
||||
pub const DB_INSTALL_VALUE_TYPE: Entid = 5;
|
||||
pub const DB_INSTALL_ATTRIBUTE: Entid = 6;
|
||||
pub const DB_VALUE_TYPE: Entid = 7;
|
||||
pub const DB_CARDINALITY: Entid = 8;
|
||||
pub const DB_UNIQUE: Entid = 9;
|
||||
pub const DB_IS_COMPONENT: Entid = 10;
|
||||
pub const DB_INDEX: Entid = 11;
|
||||
pub const DB_FULLTEXT: Entid = 12;
|
||||
pub const DB_NO_HISTORY: Entid = 13;
|
||||
pub const DB_ADD: Entid = 14;
|
||||
pub const DB_RETRACT: Entid = 15;
|
||||
pub const DB_PART_USER: Entid = 16;
|
||||
pub const DB_PART_TX: Entid = 17;
|
||||
pub const DB_EXCISE: Entid = 18;
|
||||
pub const DB_EXCISE_ATTRS: Entid = 19;
|
||||
pub const DB_EXCISE_BEFORE_T: Entid = 20;
|
||||
pub const DB_EXCISE_BEFORE: Entid = 21;
|
||||
pub const DB_ALTER_ATTRIBUTE: Entid = 22;
|
||||
pub const DB_TYPE_REF: Entid = 23;
|
||||
pub const DB_TYPE_KEYWORD: Entid = 24;
|
||||
pub const DB_TYPE_LONG: Entid = 25;
|
||||
pub const DB_TYPE_DOUBLE: Entid = 26;
|
||||
pub const DB_TYPE_STRING: Entid = 27;
|
||||
pub const DB_TYPE_UUID: Entid = 28;
|
||||
pub const DB_TYPE_URI: Entid = 29;
|
||||
pub const DB_TYPE_BOOLEAN: Entid = 30;
|
||||
pub const DB_TYPE_INSTANT: Entid = 31;
|
||||
pub const DB_TYPE_BYTES: Entid = 32;
|
||||
pub const DB_CARDINALITY_ONE: Entid = 33;
|
||||
pub const DB_CARDINALITY_MANY: Entid = 34;
|
||||
pub const DB_UNIQUE_VALUE: Entid = 35;
|
||||
pub const DB_UNIQUE_IDENTITY: Entid = 36;
|
||||
pub const DB_DOC: Entid = 37;
|
||||
pub const DB_SCHEMA_VERSION: Entid = 38;
|
||||
pub const DB_SCHEMA_ATTRIBUTE: Entid = 39;
|
||||
pub const DB_SCHEMA_CORE: Entid = 40;
|
||||
|
||||
/// Return `false` if the given attribute will not change the metadata: recognized idents, schema,
|
||||
/// partitions in the partition map.
|
||||
pub fn might_update_metadata(attribute: Entid) -> bool {
|
||||
if attribute >= DB_DOC {
|
||||
return false;
|
||||
}
|
||||
matches!(
|
||||
attribute,
|
||||
// Idents.
|
||||
DB_IDENT |
|
||||
// Schema.
|
||||
DB_CARDINALITY |
|
||||
DB_FULLTEXT |
|
||||
DB_INDEX |
|
||||
DB_IS_COMPONENT |
|
||||
DB_UNIQUE |
|
||||
DB_VALUE_TYPE
|
||||
)
|
||||
}
|
||||
|
||||
/// Return 'false' if the given attribute might be used to describe a schema attribute.
|
||||
pub fn is_a_schema_attribute(attribute: Entid) -> bool {
|
||||
matches!(
|
||||
attribute,
|
||||
DB_IDENT
|
||||
| DB_CARDINALITY
|
||||
| DB_FULLTEXT
|
||||
| DB_INDEX
|
||||
| DB_IS_COMPONENT
|
||||
| DB_UNIQUE
|
||||
| DB_VALUE_TYPE
|
||||
)
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// Attributes that are "ident related". These might change the "idents" materialized view.
|
||||
pub static ref IDENTS_SQL_LIST: String = {
|
||||
format!("({})",
|
||||
DB_IDENT)
|
||||
};
|
||||
|
||||
/// Attributes that are "schema related". These might change the "schema" materialized view.
|
||||
pub static ref SCHEMA_SQL_LIST: String = {
|
||||
format!("({}, {}, {}, {}, {}, {})",
|
||||
DB_CARDINALITY,
|
||||
DB_FULLTEXT,
|
||||
DB_INDEX,
|
||||
DB_IS_COMPONENT,
|
||||
DB_UNIQUE,
|
||||
DB_VALUE_TYPE)
|
||||
};
|
||||
|
||||
/// Attributes that are "metadata" related. These might change one of the materialized views.
|
||||
pub static ref METADATA_SQL_LIST: String = {
|
||||
format!("({}, {}, {}, {}, {}, {}, {})",
|
||||
DB_CARDINALITY,
|
||||
DB_FULLTEXT,
|
||||
DB_IDENT,
|
||||
DB_INDEX,
|
||||
DB_IS_COMPONENT,
|
||||
DB_UNIQUE,
|
||||
DB_VALUE_TYPE)
|
||||
};
|
||||
}
|
221
db/src/internal_types.rs
Normal file
221
db/src/internal_types.rs
Normal file
|
@ -0,0 +1,221 @@
|
|||
// 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)]
|
||||
|
||||
//! Types used only within the transactor. These should not be exposed outside of this crate.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||
|
||||
use core_traits::{Attribute, Entid, KnownEntid, TypedValue, ValueType};
|
||||
|
||||
use mentat_core::util::Either;
|
||||
|
||||
use edn;
|
||||
use edn::entities;
|
||||
use edn::entities::{EntityPlace, OpType, TempId, TxFunction};
|
||||
use edn::{SpannedValue, ValueAndSpan, ValueRc};
|
||||
|
||||
use crate::schema::SchemaTypeChecking;
|
||||
use crate::types::{AVMap, AVPair, Schema, TransactableValue};
|
||||
use db_traits::errors;
|
||||
use db_traits::errors::{DbErrorKind, Result};
|
||||
|
||||
impl TransactableValue for ValueAndSpan {
|
||||
fn into_typed_value(self, schema: &Schema, value_type: ValueType) -> Result<TypedValue> {
|
||||
schema.to_typed_value(&self, value_type)
|
||||
}
|
||||
|
||||
fn into_entity_place(self) -> Result<EntityPlace<Self>> {
|
||||
use self::SpannedValue::*;
|
||||
match self.inner {
|
||||
Integer(v) => Ok(EntityPlace::Entid(entities::EntidOrIdent::Entid(v))),
|
||||
Keyword(v) => {
|
||||
if v.is_namespaced() {
|
||||
Ok(EntityPlace::Entid(entities::EntidOrIdent::Ident(v)))
|
||||
} else {
|
||||
// We only allow namespaced idents.
|
||||
bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace))
|
||||
}
|
||||
}
|
||||
Text(v) => Ok(EntityPlace::TempId(TempId::External(v).into())),
|
||||
List(ls) => {
|
||||
let mut it = ls.iter();
|
||||
match (it.next().map(|x| &x.inner), it.next(), it.next(), it.next()) {
|
||||
// Like "(transaction-id)".
|
||||
(Some(&PlainSymbol(ref op)), None, None, None) => {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace)),
|
||||
}
|
||||
}
|
||||
Nil | Boolean(_) | Instant(_) | BigInteger(_) | Float(_) | Uuid(_) | PlainSymbol(_)
|
||||
| NamespacedSymbol(_) | Vector(_) | Set(_) | Map(_) | Bytes(_) => {
|
||||
bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_tempid(&self) -> Option<TempId> {
|
||||
self.inner.as_text().cloned().map(TempId::External)
|
||||
}
|
||||
}
|
||||
|
||||
impl TransactableValue for TypedValue {
|
||||
fn into_typed_value(self, _schema: &Schema, value_type: ValueType) -> Result<TypedValue> {
|
||||
if self.value_type() != value_type {
|
||||
bail!(DbErrorKind::BadValuePair(format!("{:?}", self), value_type));
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn into_entity_place(self) -> Result<EntityPlace<Self>> {
|
||||
match self {
|
||||
TypedValue::Ref(x) => Ok(EntityPlace::Entid(entities::EntidOrIdent::Entid(x))),
|
||||
TypedValue::Keyword(x) => Ok(EntityPlace::Entid(entities::EntidOrIdent::Ident(
|
||||
(*x).clone(),
|
||||
))),
|
||||
TypedValue::String(x) => Ok(EntityPlace::TempId(TempId::External((*x).clone()).into())),
|
||||
TypedValue::Boolean(_)
|
||||
| TypedValue::Long(_)
|
||||
| TypedValue::Double(_)
|
||||
| TypedValue::Instant(_)
|
||||
| TypedValue::Uuid(_)
|
||||
| TypedValue::Bytes(_) => {
|
||||
bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_tempid(&self) -> Option<TempId> {
|
||||
match self {
|
||||
TypedValue::String(ref s) => Some(TempId::External((**s).clone())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||
pub enum Term<E, V> {
|
||||
AddOrRetract(OpType, E, Entid, V),
|
||||
}
|
||||
|
||||
use self::Either::*;
|
||||
|
||||
pub type KnownEntidOr<T> = Either<KnownEntid, T>;
|
||||
pub type TypedValueOr<T> = Either<TypedValue, T>;
|
||||
|
||||
pub type TempIdHandle = ValueRc<TempId>;
|
||||
pub type TempIdMap = HashMap<TempIdHandle, KnownEntid>;
|
||||
|
||||
pub type LookupRef = ValueRc<AVPair>;
|
||||
|
||||
/// Internal representation of an entid on its way to resolution. We either have the simple case (a
|
||||
/// numeric entid), a lookup-ref that still needs to be resolved (an atomized [a v] pair), or a temp
|
||||
/// ID that needs to be upserted or allocated (an atomized tempid).
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||
pub enum LookupRefOrTempId {
|
||||
LookupRef(LookupRef),
|
||||
TempId(TempIdHandle),
|
||||
}
|
||||
|
||||
pub type TermWithTempIdsAndLookupRefs =
|
||||
Term<KnownEntidOr<LookupRefOrTempId>, TypedValueOr<LookupRefOrTempId>>;
|
||||
pub type TermWithTempIds = Term<KnownEntidOr<TempIdHandle>, TypedValueOr<TempIdHandle>>;
|
||||
pub type TermWithoutTempIds = Term<KnownEntid, TypedValue>;
|
||||
pub type Population = Vec<TermWithTempIds>;
|
||||
|
||||
impl TermWithTempIds {
|
||||
// These have no tempids by definition, and just need to be unwrapped. This operation might
|
||||
// also be called "lowering" or "level lowering", but the concept of "unwrapping" is common in
|
||||
// Rust and seems appropriate here.
|
||||
pub(crate) fn unwrap(self) -> TermWithoutTempIds {
|
||||
match self {
|
||||
Term::AddOrRetract(op, Left(n), a, Left(v)) => Term::AddOrRetract(op, n, a, v),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TermWithoutTempIds {
|
||||
pub(crate) fn rewrap<A, B>(self) -> Term<KnownEntidOr<A>, TypedValueOr<B>> {
|
||||
match self {
|
||||
Term::AddOrRetract(op, n, a, v) => Term::AddOrRetract(op, Left(n), a, Left(v)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a `KnownEntidOr` or a `TypedValueOr`, replace any internal `LookupRef` with the entid from
|
||||
/// the given map. Fail if any `LookupRef` cannot be replaced.
|
||||
///
|
||||
/// `lift` allows to specify how the entid found is mapped into the output type. (This could
|
||||
/// also be an `Into` or `From` requirement.)
|
||||
///
|
||||
/// 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
|
||||
/// 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,
|
||||
{
|
||||
match desired_or {
|
||||
Left(desired) => Ok(Left(desired)), // N.b., must unwrap here -- the ::Left types are different!
|
||||
Right(other) => {
|
||||
match other {
|
||||
LookupRefOrTempId::TempId(t) => Ok(Right(t)),
|
||||
LookupRefOrTempId::LookupRef(av) => lookup_map
|
||||
.get(&*av)
|
||||
.map(|x| lift(*x))
|
||||
.map(Left)
|
||||
// XXX TODO: fix this error kind!
|
||||
.ok_or_else(|| {
|
||||
DbErrorKind::UnrecognizedIdent(format!(
|
||||
"couldn't lookup [a v]: {:?}",
|
||||
(*av).clone()
|
||||
))
|
||||
.into()
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub(crate) struct AddAndRetract {
|
||||
pub(crate) add: BTreeSet<TypedValue>,
|
||||
pub(crate) retract: BTreeSet<TypedValue>,
|
||||
}
|
||||
|
||||
// A trie-like structure mapping a -> e -> v that prefix compresses and makes uniqueness constraint
|
||||
// checking more efficient. BTree* for deterministic errors.
|
||||
pub(crate) type AEVTrie<'schema> =
|
||||
BTreeMap<(Entid, &'schema Attribute), BTreeMap<Entid, AddAndRetract>>;
|
121
db/src/lib.rs
Normal file
121
db/src/lib.rs
Normal file
|
@ -0,0 +1,121 @@
|
|||
// 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.
|
||||
|
||||
extern crate failure;
|
||||
extern crate indexmap;
|
||||
extern crate itertools;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
#[cfg(feature = "syncable")]
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
extern crate petgraph;
|
||||
extern crate rusqlite;
|
||||
extern crate tabwriter;
|
||||
extern crate time;
|
||||
|
||||
#[macro_use]
|
||||
extern crate edn;
|
||||
#[macro_use]
|
||||
extern crate mentat_core;
|
||||
extern crate db_traits;
|
||||
#[macro_use]
|
||||
extern crate core_traits;
|
||||
extern crate mentat_sql;
|
||||
|
||||
use std::iter::repeat;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use db_traits::errors::{DbErrorKind, Result};
|
||||
|
||||
#[macro_use]
|
||||
pub mod debug;
|
||||
|
||||
mod add_retract_alter_set;
|
||||
mod bootstrap;
|
||||
pub mod cache;
|
||||
pub mod db;
|
||||
pub mod entids;
|
||||
pub mod internal_types; // pub because we need them for building entities programmatically.
|
||||
mod metadata;
|
||||
mod schema;
|
||||
pub mod timelines;
|
||||
mod tx;
|
||||
mod tx_checking;
|
||||
pub mod tx_observer;
|
||||
pub mod types;
|
||||
mod upsert_resolution;
|
||||
mod watcher;
|
||||
|
||||
// Export these for reference from sync code and tests.
|
||||
pub use crate::bootstrap::{TX0, USER0, V1_PARTS};
|
||||
|
||||
pub static TIMELINE_MAIN: i64 = 0;
|
||||
|
||||
pub use crate::schema::{AttributeBuilder, AttributeValidation};
|
||||
|
||||
pub use crate::bootstrap::CORE_SCHEMA_VERSION;
|
||||
|
||||
use edn::symbols;
|
||||
|
||||
pub use crate::entids::DB_SCHEMA_CORE;
|
||||
|
||||
pub use crate::db::{new_connection, TypedSQLValue};
|
||||
|
||||
#[cfg(feature = "sqlcipher")]
|
||||
pub use db::{change_encryption_key, new_connection_with_key};
|
||||
|
||||
pub use crate::watcher::TransactWatcher;
|
||||
|
||||
pub use crate::tx::{transact, transact_terms};
|
||||
|
||||
pub use crate::tx_observer::{InProgressObserverTransactWatcher, TxObservationService, TxObserver};
|
||||
|
||||
pub use crate::types::{AttributeSet, Partition, PartitionMap, TransactableValue, DB};
|
||||
|
||||
pub fn to_namespaced_keyword(s: &str) -> Result<symbols::Keyword> {
|
||||
let splits = [':', '/'];
|
||||
let mut i = s.split(&splits[..]);
|
||||
let nsk = match (i.next(), i.next(), i.next(), i.next()) {
|
||||
(Some(""), Some(namespace), Some(name), None) => {
|
||||
Some(symbols::Keyword::namespaced(namespace, name))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
nsk.ok_or_else(|| DbErrorKind::NotYetImplemented(format!("InvalidKeyword: {}", s)).into())
|
||||
}
|
||||
|
||||
/// Prepare an SQL `VALUES` block, like (?, ?, ?), (?, ?, ?).
|
||||
///
|
||||
/// The number of values per tuple determines `(?, ?, ?)`. The number of tuples determines `(...), (...)`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use mentat_db::{repeat_values};
|
||||
/// assert_eq!(repeat_values(1, 3), "(?), (?), (?)".to_string());
|
||||
/// assert_eq!(repeat_values(3, 1), "(?, ?, ?)".to_string());
|
||||
/// assert_eq!(repeat_values(2, 2), "(?, ?), (?, ?)".to_string());
|
||||
/// ```
|
||||
pub fn repeat_values(values_per_tuple: usize, tuples: usize) -> String {
|
||||
assert!(values_per_tuple >= 1);
|
||||
assert!(tuples >= 1);
|
||||
// Like "(?, ?, ?)".
|
||||
let inner = format!("({})", repeat("?").take(values_per_tuple).join(", "));
|
||||
// Like "(?, ?, ?), (?, ?, ?)".
|
||||
let values: String = repeat(inner).take(tuples).join(", ");
|
||||
values
|
||||
}
|
451
db/src/metadata.rs
Normal file
451
db/src/metadata.rs
Normal file
|
@ -0,0 +1,451 @@
|
|||
// 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)]
|
||||
|
||||
//! Most transactions can mutate the Mentat metadata by transacting assertions:
|
||||
//!
|
||||
//! - they can add (and, eventually, retract and alter) recognized idents using the `:db/ident`
|
||||
//! attribute;
|
||||
//!
|
||||
//! - they can add (and, eventually, retract and alter) schema attributes using various `:db/*`
|
||||
//! attributes;
|
||||
//!
|
||||
//! - eventually, they will be able to add (and possibly retract) entid partitions using a Mentat
|
||||
//! equivalent (perhaps :db/partition or :db.partition/start) to Datomic's `:db.install/partition`
|
||||
//! attribute.
|
||||
//!
|
||||
//! This module recognizes, validates, applies, and reports on these mutations.
|
||||
|
||||
use failure::ResultExt;
|
||||
|
||||
use std::collections::btree_map::Entry;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use crate::add_retract_alter_set::AddRetractAlterSet;
|
||||
use crate::entids;
|
||||
use db_traits::errors::{DbErrorKind, Result};
|
||||
use edn::symbols;
|
||||
|
||||
use core_traits::{attribute, Entid, TypedValue, ValueType};
|
||||
|
||||
use mentat_core::{AttributeMap, Schema};
|
||||
|
||||
use crate::schema::{AttributeBuilder, AttributeValidation};
|
||||
|
||||
use crate::types::EAV;
|
||||
|
||||
/// An alteration to an attribute.
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||
pub enum AttributeAlteration {
|
||||
/// From http://blog.datomic.com/2014/01/schema-alteration.html:
|
||||
/// - rename attributes
|
||||
/// - rename your own programmatic identities (uses of :db/ident)
|
||||
/// - add or remove indexes
|
||||
Index,
|
||||
/// - add or remove uniqueness constraints
|
||||
Unique,
|
||||
/// - change attribute cardinality
|
||||
Cardinality,
|
||||
/// - change whether history is retained for an attribute
|
||||
NoHistory,
|
||||
/// - change whether an attribute is treated as a component
|
||||
IsComponent,
|
||||
}
|
||||
|
||||
/// An alteration to an ident.
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||
pub enum IdentAlteration {
|
||||
Ident(symbols::Keyword),
|
||||
}
|
||||
|
||||
/// Summarizes changes to metadata such as a a `Schema` and (in the future) a `PartitionMap`.
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||
pub struct MetadataReport {
|
||||
// Entids that were not present in the original `AttributeMap` that was mutated.
|
||||
pub attributes_installed: BTreeSet<Entid>,
|
||||
|
||||
// Entids that were present in the original `AttributeMap` that was mutated, together with a
|
||||
// representation of the mutations that were applied.
|
||||
pub attributes_altered: BTreeMap<Entid, Vec<AttributeAlteration>>,
|
||||
|
||||
// Idents that were installed into the `AttributeMap`.
|
||||
pub idents_altered: BTreeMap<Entid, IdentAlteration>,
|
||||
}
|
||||
|
||||
impl MetadataReport {
|
||||
pub fn attributes_did_change(&self) -> bool {
|
||||
!(self.attributes_installed.is_empty() && self.attributes_altered.is_empty())
|
||||
}
|
||||
}
|
||||
|
||||
/// Update an 'AttributeMap' in place given two sets of ident and attribute retractions, which
|
||||
/// together contain enough information to reason about a "schema retraction".
|
||||
///
|
||||
/// Schema may only be retracted if all of its necessary attributes are being retracted:
|
||||
/// - :db/ident, :db/valueType, :db/cardinality.
|
||||
///
|
||||
/// Note that this is currently incomplete/flawed:
|
||||
/// - 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.
|
||||
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
|
||||
// if all of the schema-defining schema attributes are being retracted.
|
||||
// A defining set of attributes is :db/ident, :db/valueType, :db/cardinality.
|
||||
let mut filtered_retractions = vec![];
|
||||
let mut suspect_retractions = vec![];
|
||||
|
||||
// Filter out sets of schema altering retractions.
|
||||
let mut eas = BTreeMap::new();
|
||||
for (e, a, v) in retractions.into_iter() {
|
||||
if entids::is_a_schema_attribute(a) {
|
||||
eas.entry(e).or_insert_with(Vec::new).push(a);
|
||||
suspect_retractions.push((e, a, v));
|
||||
} else {
|
||||
filtered_retractions.push((e, a, v));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO (see https://github.com/mozilla/mentat/issues/796).
|
||||
// Retraction of idents is allowed, but if an ident names a schema attribute, then we should enforce
|
||||
// retraction of all of the associated schema attributes.
|
||||
// Unfortunately, our current in-memory schema representation (namely, how we define an Attribute) is not currently
|
||||
// rich enough: it lacks distinction between presence and absence, and instead assumes default values.
|
||||
|
||||
// Currently, in order to do this enforcement correctly, we'd need to inspect 'datoms'.
|
||||
|
||||
// Here is an incorrect way to enforce this. It's incorrect because it prevents us from retracting non-"schema naming" idents.
|
||||
// for retracted_e in ident_retractions.keys() {
|
||||
// if !eas.contains_key(retracted_e) {
|
||||
// bail!(DbErrorKind::BadSchemaAssertion(format!("Retracting :db/ident of a schema without retracting its defining attributes is not permitted.")));
|
||||
// }
|
||||
// }
|
||||
|
||||
for (e, a, v) in suspect_retractions.into_iter() {
|
||||
let attributes = eas.get(&e).unwrap();
|
||||
|
||||
// Found a set of retractions which negate a schema.
|
||||
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.
|
||||
if ident_retractions.contains_key(&e) {
|
||||
// Remove attributes corresponding to retracted attribute.
|
||||
attribute_map.remove(&e);
|
||||
} else {
|
||||
bail!(DbErrorKind::BadSchemaAssertion("Retracting defining attributes of a schema without retracting its :db/ident is not permitted.".to_string()));
|
||||
}
|
||||
} else {
|
||||
filtered_retractions.push((e, a, v));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(filtered_retractions)
|
||||
}
|
||||
|
||||
/// Update a `AttributeMap` in place from the given `[e a typed_value]` triples.
|
||||
///
|
||||
/// This is suitable for producing a `AttributeMap` from the `schema` materialized view, which does not
|
||||
/// contain install and alter markers.
|
||||
///
|
||||
/// 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> {
|
||||
fn attribute_builder_to_modify(
|
||||
attribute_id: Entid,
|
||||
existing: &AttributeMap,
|
||||
) -> AttributeBuilder {
|
||||
existing
|
||||
.get(&attribute_id)
|
||||
.map(AttributeBuilder::modify_attribute)
|
||||
.unwrap_or_else(AttributeBuilder::default)
|
||||
}
|
||||
|
||||
// Group mutations by impacted entid.
|
||||
let mut builders: BTreeMap<Entid, AttributeBuilder> = BTreeMap::new();
|
||||
|
||||
// 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.
|
||||
for (entid, attr, ref value) in retractions {
|
||||
let builder = builders
|
||||
.entry(entid)
|
||||
.or_insert_with(|| attribute_builder_to_modify(entid, attribute_map));
|
||||
match attr {
|
||||
// You can only retract :db/unique, :db/isComponent; all others must be altered instead
|
||||
// of retracted, or are not allowed to change.
|
||||
entids::DB_IS_COMPONENT => {
|
||||
match value {
|
||||
&TypedValue::Boolean(v) if builder.component == Some(v) => {
|
||||
builder.component(false);
|
||||
},
|
||||
v => {
|
||||
bail!(DbErrorKind::BadSchemaAssertion(format!("Attempted to retract :db/isComponent with the wrong value {:?}.", v)));
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
entids::DB_UNIQUE => {
|
||||
match *value {
|
||||
TypedValue::Ref(u) => {
|
||||
match u {
|
||||
entids::DB_UNIQUE_VALUE if builder.unique == Some(Some(attribute::Unique::Value)) => {
|
||||
builder.non_unique();
|
||||
},
|
||||
entids::DB_UNIQUE_IDENTITY if builder.unique == Some(Some(attribute::Unique::Identity)) => {
|
||||
builder.non_unique();
|
||||
},
|
||||
v => {
|
||||
bail!(DbErrorKind::BadSchemaAssertion(format!("Attempted to retract :db/unique with the wrong value {}.", v)));
|
||||
},
|
||||
}
|
||||
},
|
||||
_ => bail!(DbErrorKind::BadSchemaAssertion(format!("Expected [:db/retract _ :db/unique :db.unique/_] but got [:db/retract {} :db/unique {:?}]", entid, value)))
|
||||
}
|
||||
},
|
||||
|
||||
entids::DB_VALUE_TYPE |
|
||||
entids::DB_CARDINALITY |
|
||||
entids::DB_INDEX |
|
||||
entids::DB_FULLTEXT |
|
||||
entids::DB_NO_HISTORY => {
|
||||
bail!(DbErrorKind::BadSchemaAssertion(format!("Retracting attribute {} for entity {} not permitted.", attr, entid)));
|
||||
},
|
||||
|
||||
_ => {
|
||||
bail!(DbErrorKind::BadSchemaAssertion(format!("Do not recognize attribute {} for entid {}", attr, entid)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (entid, attr, ref value) in assertions.into_iter() {
|
||||
// For assertions, we can start with an empty attribute builder.
|
||||
let builder = builders.entry(entid).or_insert_with(Default::default);
|
||||
|
||||
// TODO: improve error messages throughout.
|
||||
match attr {
|
||||
entids::DB_VALUE_TYPE => {
|
||||
match *value {
|
||||
TypedValue::Ref(entids::DB_TYPE_BOOLEAN) => { builder.value_type(ValueType::Boolean); },
|
||||
TypedValue::Ref(entids::DB_TYPE_DOUBLE) => { builder.value_type(ValueType::Double); },
|
||||
TypedValue::Ref(entids::DB_TYPE_INSTANT) => { builder.value_type(ValueType::Instant); },
|
||||
TypedValue::Ref(entids::DB_TYPE_KEYWORD) => { builder.value_type(ValueType::Keyword); },
|
||||
TypedValue::Ref(entids::DB_TYPE_LONG) => { builder.value_type(ValueType::Long); },
|
||||
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_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)))
|
||||
}
|
||||
},
|
||||
|
||||
entids::DB_CARDINALITY => {
|
||||
match *value {
|
||||
TypedValue::Ref(entids::DB_CARDINALITY_MANY) => { builder.multival(true); },
|
||||
TypedValue::Ref(entids::DB_CARDINALITY_ONE) => { builder.multival(false); },
|
||||
_ => bail!(DbErrorKind::BadSchemaAssertion(format!("Expected [... :db/cardinality :db.cardinality/many|:db.cardinality/one] but got [... :db/cardinality {:?}]", value)))
|
||||
}
|
||||
},
|
||||
|
||||
entids::DB_UNIQUE => {
|
||||
match *value {
|
||||
TypedValue::Ref(entids::DB_UNIQUE_VALUE) => { builder.unique(attribute::Unique::Value); },
|
||||
TypedValue::Ref(entids::DB_UNIQUE_IDENTITY) => { builder.unique(attribute::Unique::Identity); },
|
||||
_ => bail!(DbErrorKind::BadSchemaAssertion(format!("Expected [... :db/unique :db.unique/value|:db.unique/identity] but got [... :db/unique {:?}]", value)))
|
||||
}
|
||||
},
|
||||
|
||||
entids::DB_INDEX => {
|
||||
match *value {
|
||||
TypedValue::Boolean(x) => { builder.index(x); },
|
||||
_ => bail!(DbErrorKind::BadSchemaAssertion(format!("Expected [... :db/index true|false] but got [... :db/index {:?}]", value)))
|
||||
}
|
||||
},
|
||||
|
||||
entids::DB_FULLTEXT => {
|
||||
match *value {
|
||||
TypedValue::Boolean(x) => { builder.fulltext(x); },
|
||||
_ => bail!(DbErrorKind::BadSchemaAssertion(format!("Expected [... :db/fulltext true|false] but got [... :db/fulltext {:?}]", value)))
|
||||
}
|
||||
},
|
||||
|
||||
entids::DB_IS_COMPONENT => {
|
||||
match *value {
|
||||
TypedValue::Boolean(x) => { builder.component(x); },
|
||||
_ => bail!(DbErrorKind::BadSchemaAssertion(format!("Expected [... :db/isComponent true|false] but got [... :db/isComponent {:?}]", value)))
|
||||
}
|
||||
},
|
||||
|
||||
entids::DB_NO_HISTORY => {
|
||||
match *value {
|
||||
TypedValue::Boolean(x) => { builder.no_history(x); },
|
||||
_ => bail!(DbErrorKind::BadSchemaAssertion(format!("Expected [... :db/noHistory true|false] but got [... :db/noHistory {:?}]", value)))
|
||||
}
|
||||
},
|
||||
|
||||
_ => {
|
||||
bail!(DbErrorKind::BadSchemaAssertion(format!("Do not recognize attribute {} for entid {}", attr, entid)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut attributes_installed: BTreeSet<Entid> = BTreeSet::default();
|
||||
let mut attributes_altered: BTreeMap<Entid, Vec<AttributeAlteration>> = BTreeMap::default();
|
||||
|
||||
for (entid, builder) in builders.into_iter() {
|
||||
match attribute_map.entry(entid) {
|
||||
Entry::Vacant(entry) => {
|
||||
// Validate once…
|
||||
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.
|
||||
let a = builder.build();
|
||||
a.validate(|| entid.to_string())?;
|
||||
entry.insert(a);
|
||||
attributes_installed.insert(entid);
|
||||
}
|
||||
|
||||
Entry::Occupied(mut entry) => {
|
||||
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());
|
||||
attributes_altered.insert(entid, mutations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(MetadataReport {
|
||||
attributes_installed,
|
||||
attributes_altered,
|
||||
idents_altered: BTreeMap::default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Update a `Schema` in place from the given `[e a typed_value added]` quadruples.
|
||||
///
|
||||
/// This layer enforces that ident assertions of the form [entid :db/ident ...] (as distinct from
|
||||
/// attribute assertions) are present and correct.
|
||||
///
|
||||
/// This is suitable for mutating a `Schema` from an applied transaction.
|
||||
///
|
||||
/// Returns a report summarizing the mutations that were applied.
|
||||
pub fn update_schema_from_entid_quadruples<U>(
|
||||
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
|
||||
// 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
|
||||
// value to a new value.
|
||||
let mut attribute_set: AddRetractAlterSet<(Entid, Entid), TypedValue> =
|
||||
AddRetractAlterSet::default();
|
||||
let mut ident_set: AddRetractAlterSet<Entid, symbols::Keyword> = AddRetractAlterSet::default();
|
||||
|
||||
for (e, a, typed_value, added) in assertions.into_iter() {
|
||||
// Here we handle :db/ident assertions.
|
||||
if a == entids::DB_IDENT {
|
||||
if let TypedValue::Keyword(ref keyword) = typed_value {
|
||||
ident_set.witness(e, keyword.as_ref().clone(), added);
|
||||
continue;
|
||||
} else {
|
||||
// Something is terribly wrong: the schema ensures we have a keyword.
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
attribute_set.witness((e, a), typed_value, added);
|
||||
}
|
||||
|
||||
// Collect triples.
|
||||
let retracted_triples = attribute_set
|
||||
.retracted
|
||||
.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.
|
||||
// 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,
|
||||
retracted_triples.collect(),
|
||||
&ident_set.retracted,
|
||||
)?;
|
||||
|
||||
// Now we process all other retractions.
|
||||
let report = update_attribute_map_from_entid_triples(
|
||||
&mut schema.attribute_map,
|
||||
asserted_triples.chain(altered_triples).collect(),
|
||||
non_schema_retractions,
|
||||
)?;
|
||||
|
||||
let mut idents_altered: BTreeMap<Entid, IdentAlteration> = BTreeMap::new();
|
||||
|
||||
// Asserted, altered, or retracted :db/idents update the relevant entids.
|
||||
for (entid, ident) in ident_set.asserted {
|
||||
schema.entid_map.insert(entid, ident.clone());
|
||||
schema.ident_map.insert(ident.clone(), entid);
|
||||
idents_altered.insert(entid, IdentAlteration::Ident(ident.clone()));
|
||||
}
|
||||
|
||||
for (entid, (old_ident, new_ident)) in ident_set.altered {
|
||||
schema.entid_map.insert(entid, new_ident.clone()); // Overwrite existing.
|
||||
schema.ident_map.remove(&old_ident); // Remove old.
|
||||
schema.ident_map.insert(new_ident.clone(), entid); // Insert new.
|
||||
idents_altered.insert(entid, IdentAlteration::Ident(new_ident.clone()));
|
||||
}
|
||||
|
||||
for (entid, ident) in &ident_set.retracted {
|
||||
schema.entid_map.remove(entid);
|
||||
schema.ident_map.remove(ident);
|
||||
idents_altered.insert(*entid, IdentAlteration::Ident(ident.clone()));
|
||||
}
|
||||
|
||||
// Component attributes need to change if either:
|
||||
// - a component attribute changed
|
||||
// - a schema attribute that was a component was retracted
|
||||
// These two checks are a rather heavy-handed way of keeping schema's
|
||||
// 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_
|
||||
// a component was removed, or a non-component related attribute changed).
|
||||
if report.attributes_did_change() || !ident_set.retracted.is_empty() {
|
||||
schema.update_component_attributes();
|
||||
}
|
||||
|
||||
Ok(MetadataReport {
|
||||
idents_altered,
|
||||
..report
|
||||
})
|
||||
}
|
641
db/src/schema.rs
Normal file
641
db/src/schema.rs
Normal file
|
@ -0,0 +1,641 @@
|
|||
// 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 crate::db::TypedSQLValue;
|
||||
use db_traits::errors::{DbErrorKind, Result};
|
||||
use edn;
|
||||
use edn::symbols;
|
||||
|
||||
use core_traits::{attribute, Attribute, Entid, KnownEntid, TypedValue, ValueType};
|
||||
|
||||
use crate::metadata;
|
||||
use crate::metadata::AttributeAlteration;
|
||||
use mentat_core::{AttributeMap, EntidMap, HasSchema, IdentMap, Schema};
|
||||
|
||||
pub trait AttributeValidation {
|
||||
fn validate<F>(&self, ident: F) -> Result<()>
|
||||
where
|
||||
F: Fn() -> String;
|
||||
}
|
||||
|
||||
impl AttributeValidation for Attribute {
|
||||
fn validate<F>(&self, ident: F) -> Result<()>
|
||||
where
|
||||
F: Fn() -> String,
|
||||
{
|
||||
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()
|
||||
)))
|
||||
}
|
||||
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()
|
||||
)))
|
||||
}
|
||||
if self.fulltext && self.value_type != ValueType::String {
|
||||
bail!(DbErrorKind::BadSchemaAssertion(format!(
|
||||
":db/fulltext true without :db/valueType :db.type/string for entid: {}",
|
||||
ident()
|
||||
)))
|
||||
}
|
||||
if self.fulltext && !self.index {
|
||||
bail!(DbErrorKind::BadSchemaAssertion(format!(
|
||||
":db/fulltext true without :db/index true for entid: {}",
|
||||
ident()
|
||||
)))
|
||||
}
|
||||
if self.component && self.value_type != ValueType::Ref {
|
||||
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,
|
||||
// since this may be inefficient. More generally, we should try to drive complex
|
||||
// :db/valueType (string, uri, json in the future) users to opt-in to some hash-indexing
|
||||
// scheme, as discussed in https://github.com/mozilla/mentat/issues/69.
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `Ok(())` if `attribute_map` defines a valid Mentat schema.
|
||||
fn validate_attribute_map(entid_map: &EntidMap, attribute_map: &AttributeMap) -> Result<()> {
|
||||
for (entid, attribute) in attribute_map {
|
||||
let ident = || {
|
||||
entid_map
|
||||
.get(entid)
|
||||
.map(|ident| ident.to_string())
|
||||
.unwrap_or_else(|| entid.to_string())
|
||||
};
|
||||
attribute.validate(ident)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||
pub struct AttributeBuilder {
|
||||
helpful: bool,
|
||||
pub value_type: Option<ValueType>,
|
||||
pub multival: Option<bool>,
|
||||
pub unique: Option<Option<attribute::Unique>>,
|
||||
pub index: Option<bool>,
|
||||
pub fulltext: Option<bool>,
|
||||
pub component: Option<bool>,
|
||||
pub no_history: Option<bool>,
|
||||
}
|
||||
|
||||
impl AttributeBuilder {
|
||||
/// Make a new AttributeBuilder for human consumption: it will help you
|
||||
/// by flipping relevant flags.
|
||||
pub fn helpful() -> Self {
|
||||
AttributeBuilder {
|
||||
helpful: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Make a new AttributeBuilder from an existing Attribute. This is important to allow
|
||||
/// retraction. Only attributes that we allow to change are duplicated here.
|
||||
pub fn modify_attribute(attribute: &Attribute) -> Self {
|
||||
let mut ab = AttributeBuilder::default();
|
||||
ab.multival = Some(attribute.multival);
|
||||
ab.unique = Some(attribute.unique);
|
||||
ab.component = Some(attribute.component);
|
||||
ab
|
||||
}
|
||||
|
||||
pub fn value_type(&mut self, value_type: ValueType) -> &mut Self {
|
||||
self.value_type = Some(value_type);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn multival(&mut self, multival: bool) -> &mut Self {
|
||||
self.multival = Some(multival);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn non_unique(&mut self) -> &mut Self {
|
||||
self.unique = Some(None);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn unique(&mut self, unique: attribute::Unique) -> &mut Self {
|
||||
if self.helpful && unique == attribute::Unique::Identity {
|
||||
self.index = Some(true);
|
||||
}
|
||||
self.unique = Some(Some(unique));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn index(&mut self, index: bool) -> &mut Self {
|
||||
self.index = Some(index);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn fulltext(&mut self, fulltext: bool) -> &mut Self {
|
||||
self.fulltext = Some(fulltext);
|
||||
if self.helpful && fulltext {
|
||||
self.index = Some(true);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn component(&mut self, component: bool) -> &mut Self {
|
||||
self.component = Some(component);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn no_history(&mut self, no_history: bool) -> &mut Self {
|
||||
self.no_history = Some(no_history);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn validate_install_attribute(&self) -> Result<()> {
|
||||
if self.value_type.is_none() {
|
||||
bail!(DbErrorKind::BadSchemaAssertion(
|
||||
"Schema attribute for new attribute does not set :db/valueType".into()
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate_alter_attribute(&self) -> Result<()> {
|
||||
if self.value_type.is_some() {
|
||||
bail!(DbErrorKind::BadSchemaAssertion(
|
||||
"Schema alteration must not set :db/valueType".into()
|
||||
));
|
||||
}
|
||||
if self.fulltext.is_some() {
|
||||
bail!(DbErrorKind::BadSchemaAssertion(
|
||||
"Schema alteration must not set :db/fulltext".into()
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn build(&self) -> Attribute {
|
||||
let mut attribute = Attribute::default();
|
||||
if let Some(value_type) = self.value_type {
|
||||
attribute.value_type = value_type;
|
||||
}
|
||||
if let Some(fulltext) = self.fulltext {
|
||||
attribute.fulltext = fulltext;
|
||||
}
|
||||
if let Some(multival) = self.multival {
|
||||
attribute.multival = multival;
|
||||
}
|
||||
if let Some(ref unique) = self.unique {
|
||||
attribute.unique = *unique;
|
||||
}
|
||||
if let Some(index) = self.index {
|
||||
attribute.index = index;
|
||||
}
|
||||
if let Some(component) = self.component {
|
||||
attribute.component = component;
|
||||
}
|
||||
if let Some(no_history) = self.no_history {
|
||||
attribute.no_history = no_history;
|
||||
}
|
||||
|
||||
attribute
|
||||
}
|
||||
|
||||
pub fn mutate(&self, attribute: &mut Attribute) -> Vec<AttributeAlteration> {
|
||||
let mut mutations = Vec::new();
|
||||
if let Some(multival) = self.multival {
|
||||
if multival != attribute.multival {
|
||||
attribute.multival = multival;
|
||||
mutations.push(AttributeAlteration::Cardinality);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref unique) = self.unique {
|
||||
if *unique != attribute.unique {
|
||||
attribute.unique = *unique;
|
||||
mutations.push(AttributeAlteration::Unique);
|
||||
}
|
||||
} else if attribute.unique != None {
|
||||
attribute.unique = None;
|
||||
mutations.push(AttributeAlteration::Unique);
|
||||
}
|
||||
|
||||
if let Some(index) = self.index {
|
||||
if index != attribute.index {
|
||||
attribute.index = index;
|
||||
mutations.push(AttributeAlteration::Index);
|
||||
}
|
||||
}
|
||||
if let Some(component) = self.component {
|
||||
if component != attribute.component {
|
||||
attribute.component = component;
|
||||
mutations.push(AttributeAlteration::IsComponent);
|
||||
}
|
||||
}
|
||||
if let Some(no_history) = self.no_history {
|
||||
if no_history != attribute.no_history {
|
||||
attribute.no_history = no_history;
|
||||
mutations.push(AttributeAlteration::NoHistory);
|
||||
}
|
||||
}
|
||||
|
||||
mutations
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SchemaBuilding {
|
||||
fn require_ident(&self, entid: Entid) -> Result<&symbols::Keyword>;
|
||||
fn require_entid(&self, ident: &symbols::Keyword) -> Result<KnownEntid>;
|
||||
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_triples<U>(ident_map: IdentMap, assertions: U) -> Result<Schema>
|
||||
where
|
||||
U: IntoIterator<Item = (symbols::Keyword, symbols::Keyword, TypedValue)>;
|
||||
}
|
||||
|
||||
impl SchemaBuilding for Schema {
|
||||
fn require_ident(&self, entid: Entid) -> Result<&symbols::Keyword> {
|
||||
self.get_ident(entid)
|
||||
.ok_or_else(|| DbErrorKind::UnrecognizedEntid(entid).into())
|
||||
}
|
||||
|
||||
fn require_entid(&self, ident: &symbols::Keyword) -> Result<KnownEntid> {
|
||||
self.get_entid(&ident)
|
||||
.ok_or_else(|| DbErrorKind::UnrecognizedIdent(ident.to_string()).into())
|
||||
}
|
||||
|
||||
fn require_attribute_for_entid(&self, entid: Entid) -> Result<&Attribute> {
|
||||
self.attribute_for_entid(entid)
|
||||
.ok_or_else(|| DbErrorKind::UnrecognizedEntid(entid).into())
|
||||
}
|
||||
|
||||
/// Create a valid `Schema` from the constituent maps.
|
||||
fn from_ident_map_and_attribute_map(
|
||||
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)?;
|
||||
Ok(Schema::new(ident_map, entid_map, attribute_map))
|
||||
}
|
||||
|
||||
/// 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>
|
||||
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_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))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut schema =
|
||||
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?,
|
||||
// No retractions.
|
||||
vec![],
|
||||
)?;
|
||||
|
||||
// Rebuild the component attributes list if necessary.
|
||||
if metadata_report.attributes_did_change() {
|
||||
schema.update_component_attributes();
|
||||
}
|
||||
Ok(schema)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SchemaTypeChecking {
|
||||
/// Do schema-aware typechecking and coercion.
|
||||
///
|
||||
/// 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.
|
||||
fn to_typed_value(
|
||||
&self,
|
||||
value: &edn::ValueAndSpan,
|
||||
value_type: ValueType,
|
||||
) -> Result<TypedValue>;
|
||||
}
|
||||
|
||||
impl SchemaTypeChecking for Schema {
|
||||
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
|
||||
// the attribute (rather than just the attribute's value type) into this function or a
|
||||
// wrapper function.
|
||||
match TypedValue::from_edn_value(&value.clone().without_spans()) {
|
||||
// We don't recognize this EDN at all. Get out!
|
||||
None => bail!(DbErrorKind::BadValuePair(format!("{}", value), value_type)),
|
||||
Some(typed_value) => match (value_type, typed_value) {
|
||||
// Most types don't coerce at all.
|
||||
(ValueType::Boolean, tv @ TypedValue::Boolean(_)) => Ok(tv),
|
||||
(ValueType::Long, tv @ TypedValue::Long(_)) => Ok(tv),
|
||||
(ValueType::Double, tv @ TypedValue::Double(_)) => Ok(tv),
|
||||
(ValueType::String, tv @ TypedValue::String(_)) => Ok(tv),
|
||||
(ValueType::Uuid, tv @ TypedValue::Uuid(_)) => Ok(tv),
|
||||
(ValueType::Instant, tv @ TypedValue::Instant(_)) => 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.
|
||||
(ValueType::Ref, TypedValue::Long(x)) => Ok(TypedValue::Ref(x)),
|
||||
(ValueType::Ref, TypedValue::Keyword(ref x)) => {
|
||||
self.require_entid(&x).map(|entid| entid.into())
|
||||
}
|
||||
|
||||
// Otherwise, we have a type mismatch.
|
||||
// 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
|
||||
// collection by 8!
|
||||
(vt @ ValueType::Boolean, _)
|
||||
| (vt @ ValueType::Long, _)
|
||||
| (vt @ ValueType::Double, _)
|
||||
| (vt @ ValueType::String, _)
|
||||
| (vt @ ValueType::Uuid, _)
|
||||
| (vt @ ValueType::Instant, _)
|
||||
| (vt @ ValueType::Keyword, _)
|
||||
| (vt @ ValueType::Bytes, _)
|
||||
| (vt @ ValueType::Ref, _) => {
|
||||
bail!(DbErrorKind::BadValuePair(format!("{}", value), vt))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use self::edn::Keyword;
|
||||
use super::*;
|
||||
|
||||
fn add_attribute(schema: &mut Schema, ident: Keyword, entid: Entid, attribute: Attribute) {
|
||||
schema.entid_map.insert(entid, ident.clone());
|
||||
schema.ident_map.insert(ident, entid);
|
||||
|
||||
if attribute.component {
|
||||
schema.component_attributes.push(entid);
|
||||
}
|
||||
|
||||
schema.attribute_map.insert(entid, attribute);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_attribute_map_success() {
|
||||
let mut schema = Schema::default();
|
||||
// attribute that is not an index has no uniqueness
|
||||
add_attribute(
|
||||
&mut schema,
|
||||
Keyword::namespaced("foo", "bar"),
|
||||
97,
|
||||
Attribute {
|
||||
index: false,
|
||||
value_type: ValueType::Boolean,
|
||||
fulltext: false,
|
||||
unique: None,
|
||||
multival: false,
|
||||
component: false,
|
||||
no_history: false,
|
||||
},
|
||||
);
|
||||
// attribute is unique by value and an index
|
||||
add_attribute(
|
||||
&mut schema,
|
||||
Keyword::namespaced("foo", "baz"),
|
||||
98,
|
||||
Attribute {
|
||||
index: true,
|
||||
value_type: ValueType::Long,
|
||||
fulltext: false,
|
||||
unique: Some(attribute::Unique::Value),
|
||||
multival: false,
|
||||
component: false,
|
||||
no_history: false,
|
||||
},
|
||||
);
|
||||
// attribue is unique by identity and an index
|
||||
add_attribute(
|
||||
&mut schema,
|
||||
Keyword::namespaced("foo", "bat"),
|
||||
99,
|
||||
Attribute {
|
||||
index: true,
|
||||
value_type: ValueType::Ref,
|
||||
fulltext: false,
|
||||
unique: Some(attribute::Unique::Identity),
|
||||
multival: false,
|
||||
component: false,
|
||||
no_history: false,
|
||||
},
|
||||
);
|
||||
// attribute is a components and a `Ref`
|
||||
add_attribute(
|
||||
&mut schema,
|
||||
Keyword::namespaced("foo", "bak"),
|
||||
100,
|
||||
Attribute {
|
||||
index: false,
|
||||
value_type: ValueType::Ref,
|
||||
fulltext: false,
|
||||
unique: None,
|
||||
multival: false,
|
||||
component: true,
|
||||
no_history: false,
|
||||
},
|
||||
);
|
||||
// fulltext attribute is a string and an index
|
||||
add_attribute(
|
||||
&mut schema,
|
||||
Keyword::namespaced("foo", "bap"),
|
||||
101,
|
||||
Attribute {
|
||||
index: true,
|
||||
value_type: ValueType::String,
|
||||
fulltext: true,
|
||||
unique: None,
|
||||
multival: false,
|
||||
component: false,
|
||||
no_history: false,
|
||||
},
|
||||
);
|
||||
|
||||
assert!(validate_attribute_map(&schema.entid_map, &schema.attribute_map).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_schema_unique_value_not_index() {
|
||||
let mut schema = Schema::default();
|
||||
// attribute unique by value but not index
|
||||
let ident = Keyword::namespaced("foo", "bar");
|
||||
add_attribute(
|
||||
&mut schema,
|
||||
ident,
|
||||
99,
|
||||
Attribute {
|
||||
index: false,
|
||||
value_type: ValueType::Boolean,
|
||||
fulltext: false,
|
||||
unique: Some(attribute::Unique::Value),
|
||||
multival: false,
|
||||
component: false,
|
||||
no_history: false,
|
||||
},
|
||||
);
|
||||
|
||||
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map)
|
||||
.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]
|
||||
fn invalid_schema_unique_identity_not_index() {
|
||||
let mut schema = Schema::default();
|
||||
// attribute is unique by identity but not index
|
||||
add_attribute(
|
||||
&mut schema,
|
||||
Keyword::namespaced("foo", "bar"),
|
||||
99,
|
||||
Attribute {
|
||||
index: false,
|
||||
value_type: ValueType::Long,
|
||||
fulltext: false,
|
||||
unique: Some(attribute::Unique::Identity),
|
||||
multival: false,
|
||||
component: false,
|
||||
no_history: false,
|
||||
},
|
||||
);
|
||||
|
||||
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map)
|
||||
.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]
|
||||
fn invalid_schema_component_not_ref() {
|
||||
let mut schema = Schema::default();
|
||||
// attribute that is a component is not a `Ref`
|
||||
add_attribute(
|
||||
&mut schema,
|
||||
Keyword::namespaced("foo", "bar"),
|
||||
99,
|
||||
Attribute {
|
||||
index: false,
|
||||
value_type: ValueType::Boolean,
|
||||
fulltext: false,
|
||||
unique: None,
|
||||
multival: false,
|
||||
component: true,
|
||||
no_history: false,
|
||||
},
|
||||
);
|
||||
|
||||
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map)
|
||||
.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]
|
||||
fn invalid_schema_fulltext_not_index() {
|
||||
let mut schema = Schema::default();
|
||||
// attribute that is fulltext is not an index
|
||||
add_attribute(
|
||||
&mut schema,
|
||||
Keyword::namespaced("foo", "bar"),
|
||||
99,
|
||||
Attribute {
|
||||
index: false,
|
||||
value_type: ValueType::String,
|
||||
fulltext: true,
|
||||
unique: None,
|
||||
multival: false,
|
||||
component: false,
|
||||
no_history: false,
|
||||
},
|
||||
);
|
||||
|
||||
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map)
|
||||
.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() {
|
||||
let mut schema = Schema::default();
|
||||
// attribute that is fulltext and not a `String`
|
||||
add_attribute(
|
||||
&mut schema,
|
||||
Keyword::namespaced("foo", "bar"),
|
||||
99,
|
||||
Attribute {
|
||||
index: true,
|
||||
value_type: ValueType::Long,
|
||||
fulltext: true,
|
||||
unique: None,
|
||||
multival: false,
|
||||
component: false,
|
||||
no_history: false,
|
||||
},
|
||||
);
|
||||
|
||||
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map)
|
||||
.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()
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
862
db/src/timelines.rs
Normal file
862
db/src/timelines.rs
Normal file
|
@ -0,0 +1,862 @@
|
|||
// 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::ops::RangeFrom;
|
||||
|
||||
use rusqlite::{self, params_from_iter};
|
||||
|
||||
use db_traits::errors::{DbErrorKind, Result};
|
||||
|
||||
use core_traits::{Entid, KnownEntid, TypedValue};
|
||||
|
||||
use mentat_core::Schema;
|
||||
|
||||
use edn::InternSet;
|
||||
|
||||
use edn::entities::OpType;
|
||||
|
||||
use crate::db;
|
||||
use crate::db::TypedSQLValue;
|
||||
|
||||
use crate::tx::{transact_terms_with_action, TransactorAction};
|
||||
|
||||
use crate::types::PartitionMap;
|
||||
|
||||
use crate::internal_types::{Term, TermWithoutTempIds};
|
||||
|
||||
use crate::watcher::NullWatcher;
|
||||
|
||||
/// Collects a supplied tx range into an DESC ordered Vec of valid txs,
|
||||
/// 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>> {
|
||||
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)> { Ok((row.get(0)?, row.get(1)?)) },
|
||||
)?;
|
||||
|
||||
let mut txs = vec![];
|
||||
|
||||
// TODO do this in SQL instead?
|
||||
let timeline = match rows.next() {
|
||||
Some(t) => {
|
||||
let t = t?;
|
||||
txs.push(t.0);
|
||||
t.1
|
||||
}
|
||||
None => bail!(DbErrorKind::TimelinesInvalidRange),
|
||||
};
|
||||
|
||||
for t in rows {
|
||||
let t = t?;
|
||||
txs.push(t.0);
|
||||
if t.1 != timeline {
|
||||
bail!(DbErrorKind::TimelinesMixed);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(txs)
|
||||
}
|
||||
|
||||
fn move_transactions_to(
|
||||
conn: &rusqlite::Connection,
|
||||
tx_ids: &[Entid],
|
||||
new_timeline: Entid,
|
||||
) -> Result<()> {
|
||||
// Move specified transactions over to a specified timeline.
|
||||
conn.execute(
|
||||
&format!(
|
||||
"UPDATE timelined_transactions SET timeline = {} WHERE tx IN {}",
|
||||
new_timeline,
|
||||
crate::repeat_values(tx_ids.len(), 1)
|
||||
),
|
||||
params_from_iter(tx_ids.iter()),
|
||||
)?;
|
||||
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> {
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT timeline FROM timelined_transactions WHERE timeline = ? GROUP BY timeline",
|
||||
)?;
|
||||
let rows = stmt.query_and_then(&[&timeline], |row| -> Result<i64> { Ok(row.get(0)?) })?;
|
||||
Ok(rows.count() == 0)
|
||||
}
|
||||
|
||||
/// 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>> {
|
||||
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 rows = stmt.query_and_then(
|
||||
&[&tx_id, &crate::TIMELINE_MAIN],
|
||||
|row| -> Result<TermWithoutTempIds> {
|
||||
let op = if row.get(5)? {
|
||||
OpType::Retract
|
||||
} else {
|
||||
OpType::Add
|
||||
};
|
||||
Ok(Term::AddOrRetract(
|
||||
op,
|
||||
KnownEntid(row.get(0)?),
|
||||
row.get(1)?,
|
||||
TypedValue::from_sql_value_pair(row.get(2)?, row.get(3)?)?,
|
||||
))
|
||||
},
|
||||
)?;
|
||||
|
||||
let mut terms = vec![];
|
||||
|
||||
for row in rows {
|
||||
terms.push(row?);
|
||||
}
|
||||
Ok(terms)
|
||||
}
|
||||
|
||||
/// Move specified transaction RangeFrom off of main timeline.
|
||||
pub fn move_from_main_timeline(
|
||||
conn: &rusqlite::Connection,
|
||||
schema: &Schema,
|
||||
partition_map: PartitionMap,
|
||||
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
|
||||
// will result in sensible end-state for that timeline.
|
||||
// Let's remove that foot gun by prohibiting moving transactions to a non-empty timeline.
|
||||
if !is_timeline_empty(conn, new_timeline)? {
|
||||
bail!(DbErrorKind::TimelinesMoveToNonEmpty);
|
||||
}
|
||||
|
||||
let txs_to_move = collect_ordered_txs_to_move(conn, txs_from, crate::TIMELINE_MAIN)?;
|
||||
|
||||
let mut last_schema = None;
|
||||
for tx_id in &txs_to_move {
|
||||
let reversed_terms = reversed_terms_for(conn, *tx_id)?;
|
||||
|
||||
// Rewind schema and datoms.
|
||||
let (report, _, new_schema, _) = transact_terms_with_action(
|
||||
conn,
|
||||
partition_map.clone(),
|
||||
schema,
|
||||
schema,
|
||||
NullWatcher(),
|
||||
reversed_terms.into_iter().map(|t| t.rewrap()),
|
||||
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;
|
||||
}
|
||||
|
||||
// Move transactions over to the target timeline.
|
||||
move_transactions_to(conn, &txs_to_move, new_timeline)?;
|
||||
|
||||
Ok((last_schema, db::read_partition_map(conn)?))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use edn;
|
||||
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use crate::debug::TestConn;
|
||||
|
||||
use crate::bootstrap;
|
||||
|
||||
// For convenience during testing.
|
||||
// Real consumers will perform similar operations when appropriate.
|
||||
fn update_conn(conn: &mut TestConn, schema: &Option<Schema>, pmap: &PartitionMap) {
|
||||
match schema {
|
||||
Some(ref s) => conn.schema = s.clone(),
|
||||
None => (),
|
||||
};
|
||||
conn.partition_map = pmap.clone();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pop_simple() {
|
||||
let mut conn = TestConn::default();
|
||||
conn.sanitized_partition_map();
|
||||
|
||||
let t = r#"
|
||||
[{:db/id :db/doc :db/doc "test"}]
|
||||
"#;
|
||||
|
||||
let partition_map0 = conn.partition_map.clone();
|
||||
|
||||
let report1 = assert_transact!(conn, t);
|
||||
let partition_map1 = conn.partition_map.clone();
|
||||
|
||||
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_matches!(conn.datoms(), "[]");
|
||||
assert_matches!(conn.transactions(), "[]");
|
||||
assert_eq!(new_partition_map, partition_map0);
|
||||
|
||||
conn.partition_map = partition_map0;
|
||||
let report2 = assert_transact!(conn, t);
|
||||
let partition_map2 = conn.partition_map.clone();
|
||||
|
||||
// Ensure that we can't move transactions to a non-empty timeline:
|
||||
move_from_main_timeline(
|
||||
&conn.sqlite,
|
||||
&conn.schema,
|
||||
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!(partition_map1, partition_map2);
|
||||
|
||||
assert_matches!(
|
||||
conn.datoms(),
|
||||
r#"
|
||||
[[37 :db/doc "test"]]
|
||||
"#
|
||||
);
|
||||
assert_matches!(
|
||||
conn.transactions(),
|
||||
r#"
|
||||
[[[37 :db/doc "test" ?tx true]
|
||||
[?tx :db/txInstant ?ms ?tx true]]]
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pop_ident() {
|
||||
let mut conn = TestConn::default();
|
||||
conn.sanitized_partition_map();
|
||||
|
||||
let t = r#"
|
||||
[{:db/ident :test/entid :db/doc "test" :db.schema/version 1}]
|
||||
"#;
|
||||
|
||||
let partition_map0 = conn.partition_map.clone();
|
||||
let schema0 = conn.schema.clone();
|
||||
|
||||
let report1 = assert_transact!(conn, t);
|
||||
let partition_map1 = conn.partition_map.clone();
|
||||
let schema1 = conn.schema.clone();
|
||||
|
||||
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_matches!(conn.datoms(), "[]");
|
||||
assert_matches!(conn.transactions(), "[]");
|
||||
assert_eq!(conn.partition_map, partition_map0);
|
||||
assert_eq!(conn.schema, schema0);
|
||||
|
||||
let report2 = assert_transact!(conn, t);
|
||||
|
||||
assert_eq!(report1.tx_id, report2.tx_id);
|
||||
assert_eq!(conn.partition_map, partition_map1);
|
||||
assert_eq!(conn.schema, schema1);
|
||||
|
||||
assert_matches!(
|
||||
conn.datoms(),
|
||||
r#"
|
||||
[[?e :db/ident :test/entid]
|
||||
[?e :db/doc "test"]
|
||||
[?e :db.schema/version 1]]
|
||||
"#
|
||||
);
|
||||
assert_matches!(
|
||||
conn.transactions(),
|
||||
r#"
|
||||
[[[?e :db/ident :test/entid ?tx true]
|
||||
[?e :db/doc "test" ?tx true]
|
||||
[?e :db.schema/version 1 ?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]
|
||||
fn test_pop_schema() {
|
||||
let mut conn = TestConn::default();
|
||||
conn.sanitized_partition_map();
|
||||
|
||||
let t = r#"
|
||||
[{:db/id "e" :db/ident :test/one :db/valueType :db.type/long :db/cardinality :db.cardinality/one}
|
||||
{:db/id "f" :db/ident :test/many :db/valueType :db.type/long :db/cardinality :db.cardinality/many}]
|
||||
"#;
|
||||
|
||||
let partition_map0 = conn.partition_map.clone();
|
||||
let schema0 = conn.schema.clone();
|
||||
|
||||
let report1 = assert_transact!(conn, t);
|
||||
let partition_map1 = conn.partition_map.clone();
|
||||
let schema1 = conn.schema.clone();
|
||||
|
||||
let (new_schema, new_partition_map) = move_from_main_timeline(
|
||||
&conn.sqlite,
|
||||
&conn.schema,
|
||||
conn.partition_map.clone(),
|
||||
report1.tx_id..,
|
||||
1,
|
||||
)
|
||||
.expect("moved single tx");
|
||||
update_conn(&mut conn, &new_schema, &new_partition_map);
|
||||
|
||||
assert_matches!(conn.datoms(), "[]");
|
||||
assert_matches!(conn.transactions(), "[]");
|
||||
assert_eq!(conn.partition_map, partition_map0);
|
||||
assert_eq!(conn.schema, schema0);
|
||||
|
||||
let report2 = assert_transact!(conn, t);
|
||||
let partition_map2 = conn.partition_map.clone();
|
||||
let schema2 = conn.schema.clone();
|
||||
|
||||
assert_eq!(report1.tx_id, report2.tx_id);
|
||||
assert_eq!(partition_map1, partition_map2);
|
||||
assert_eq!(schema1, schema2);
|
||||
|
||||
assert_matches!(
|
||||
conn.datoms(),
|
||||
r#"
|
||||
[[?e1 :db/ident :test/one]
|
||||
[?e1 :db/valueType :db.type/long]
|
||||
[?e1 :db/cardinality :db.cardinality/one]
|
||||
[?e2 :db/ident :test/many]
|
||||
[?e2 :db/valueType :db.type/long]
|
||||
[?e2 :db/cardinality :db.cardinality/many]]
|
||||
"#
|
||||
);
|
||||
assert_matches!(
|
||||
conn.transactions(),
|
||||
r#"
|
||||
[[[?e1 :db/ident :test/one ?tx1 true]
|
||||
[?e1 :db/valueType :db.type/long ?tx1 true]
|
||||
[?e1 :db/cardinality :db.cardinality/one ?tx1 true]
|
||||
[?e2 :db/ident :test/many ?tx1 true]
|
||||
[?e2 :db/valueType :db.type/long ?tx1 true]
|
||||
[?e2 :db/cardinality :db.cardinality/many ?tx1 true]
|
||||
[?tx1 :db/txInstant ?ms ?tx1 true]]]
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pop_schema_all_attributes() {
|
||||
let mut conn = TestConn::default();
|
||||
conn.sanitized_partition_map();
|
||||
|
||||
let t = r#"
|
||||
[{
|
||||
:db/id "e"
|
||||
:db/ident :test/one
|
||||
:db/valueType :db.type/string
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/unique :db.unique/value
|
||||
:db/index true
|
||||
:db/fulltext true
|
||||
}]
|
||||
"#;
|
||||
|
||||
let partition_map0 = conn.partition_map.clone();
|
||||
let schema0 = conn.schema.clone();
|
||||
|
||||
let report1 = assert_transact!(conn, t);
|
||||
let partition_map1 = conn.partition_map.clone();
|
||||
let schema1 = conn.schema.clone();
|
||||
|
||||
let (new_schema, new_partition_map) = move_from_main_timeline(
|
||||
&conn.sqlite,
|
||||
&conn.schema,
|
||||
conn.partition_map.clone(),
|
||||
report1.tx_id..,
|
||||
1,
|
||||
)
|
||||
.expect("moved single tx");
|
||||
update_conn(&mut conn, &new_schema, &new_partition_map);
|
||||
|
||||
assert_matches!(conn.datoms(), "[]");
|
||||
assert_matches!(conn.transactions(), "[]");
|
||||
assert_eq!(conn.partition_map, partition_map0);
|
||||
assert_eq!(conn.schema, schema0);
|
||||
|
||||
let report2 = assert_transact!(conn, t);
|
||||
let partition_map2 = conn.partition_map.clone();
|
||||
let schema2 = conn.schema.clone();
|
||||
|
||||
assert_eq!(report1.tx_id, report2.tx_id);
|
||||
assert_eq!(partition_map1, partition_map2);
|
||||
assert_eq!(schema1, schema2);
|
||||
|
||||
assert_matches!(
|
||||
conn.datoms(),
|
||||
r#"
|
||||
[[?e1 :db/ident :test/one]
|
||||
[?e1 :db/valueType :db.type/string]
|
||||
[?e1 :db/cardinality :db.cardinality/one]
|
||||
[?e1 :db/unique :db.unique/value]
|
||||
[?e1 :db/index true]
|
||||
[?e1 :db/fulltext true]]
|
||||
"#
|
||||
);
|
||||
assert_matches!(
|
||||
conn.transactions(),
|
||||
r#"
|
||||
[[[?e1 :db/ident :test/one ?tx1 true]
|
||||
[?e1 :db/valueType :db.type/string ?tx1 true]
|
||||
[?e1 :db/cardinality :db.cardinality/one ?tx1 true]
|
||||
[?e1 :db/unique :db.unique/value ?tx1 true]
|
||||
[?e1 :db/index true ?tx1 true]
|
||||
[?e1 :db/fulltext true ?tx1 true]
|
||||
[?tx1 :db/txInstant ?ms ?tx1 true]]]
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pop_schema_all_attributes_component() {
|
||||
let mut conn = TestConn::default();
|
||||
conn.sanitized_partition_map();
|
||||
|
||||
let t = r#"
|
||||
[{
|
||||
:db/id "e"
|
||||
:db/ident :test/one
|
||||
:db/valueType :db.type/ref
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/unique :db.unique/value
|
||||
:db/index true
|
||||
:db/isComponent true
|
||||
}]
|
||||
"#;
|
||||
|
||||
let partition_map0 = conn.partition_map.clone();
|
||||
let schema0 = conn.schema.clone();
|
||||
|
||||
let report1 = assert_transact!(conn, t);
|
||||
let partition_map1 = conn.partition_map.clone();
|
||||
let schema1 = conn.schema.clone();
|
||||
|
||||
let (new_schema, new_partition_map) = move_from_main_timeline(
|
||||
&conn.sqlite,
|
||||
&conn.schema,
|
||||
conn.partition_map.clone(),
|
||||
report1.tx_id..,
|
||||
1,
|
||||
)
|
||||
.expect("moved single tx");
|
||||
update_conn(&mut conn, &new_schema, &new_partition_map);
|
||||
|
||||
assert_matches!(conn.datoms(), "[]");
|
||||
assert_matches!(conn.transactions(), "[]");
|
||||
assert_eq!(conn.partition_map, partition_map0);
|
||||
|
||||
// Assert all of schema's components individually, for some guidance in case of failures:
|
||||
assert_eq!(conn.schema.entid_map, schema0.entid_map);
|
||||
assert_eq!(conn.schema.ident_map, schema0.ident_map);
|
||||
assert_eq!(conn.schema.attribute_map, schema0.attribute_map);
|
||||
assert_eq!(
|
||||
conn.schema.component_attributes,
|
||||
schema0.component_attributes
|
||||
);
|
||||
// Assert the whole schema, just in case we missed something:
|
||||
assert_eq!(conn.schema, schema0);
|
||||
|
||||
let report2 = assert_transact!(conn, t);
|
||||
let partition_map2 = conn.partition_map.clone();
|
||||
let schema2 = conn.schema.clone();
|
||||
|
||||
assert_eq!(report1.tx_id, report2.tx_id);
|
||||
assert_eq!(partition_map1, partition_map2);
|
||||
assert_eq!(schema1, schema2);
|
||||
|
||||
assert_matches!(
|
||||
conn.datoms(),
|
||||
r#"
|
||||
[[?e1 :db/ident :test/one]
|
||||
[?e1 :db/valueType :db.type/ref]
|
||||
[?e1 :db/cardinality :db.cardinality/one]
|
||||
[?e1 :db/unique :db.unique/value]
|
||||
[?e1 :db/isComponent true]
|
||||
[?e1 :db/index true]]
|
||||
"#
|
||||
);
|
||||
assert_matches!(
|
||||
conn.transactions(),
|
||||
r#"
|
||||
[[[?e1 :db/ident :test/one ?tx1 true]
|
||||
[?e1 :db/valueType :db.type/ref ?tx1 true]
|
||||
[?e1 :db/cardinality :db.cardinality/one ?tx1 true]
|
||||
[?e1 :db/unique :db.unique/value ?tx1 true]
|
||||
[?e1 :db/isComponent true ?tx1 true]
|
||||
[?e1 :db/index true ?tx1 true]
|
||||
[?tx1 :db/txInstant ?ms ?tx1 true]]]
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pop_in_sequence() {
|
||||
let mut conn = TestConn::default();
|
||||
conn.sanitized_partition_map();
|
||||
|
||||
let partition_map_after_bootstrap = conn.partition_map.clone();
|
||||
|
||||
assert_eq!(
|
||||
(65536..65538),
|
||||
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 65537 :db/ident :test/many :db/valueType :db.type/long :db/cardinality :db.cardinality/many}
|
||||
]"#
|
||||
);
|
||||
|
||||
let first = "[
|
||||
[65536 :db/ident :test/one]
|
||||
[65536 :db/valueType :db.type/long]
|
||||
[65536 :db/cardinality :db.cardinality/one]
|
||||
[65536 :db/unique :db.unique/identity]
|
||||
[65536 :db/index true]
|
||||
[65537 :db/ident :test/many]
|
||||
[65537 :db/valueType :db.type/long]
|
||||
[65537 :db/cardinality :db.cardinality/many]
|
||||
]";
|
||||
assert_matches!(conn.datoms(), first);
|
||||
|
||||
let partition_map0 = conn.partition_map.clone();
|
||||
|
||||
assert_eq!(
|
||||
(65538..65539),
|
||||
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/many 2]
|
||||
[:db/add 65538 :test/many 3]
|
||||
]"#
|
||||
);
|
||||
let schema1 = conn.schema.clone();
|
||||
let partition_map1 = conn.partition_map.clone();
|
||||
|
||||
assert_matches!(
|
||||
conn.last_transaction(),
|
||||
"[[65538 :test/one 1 ?tx true]
|
||||
[65538 :test/many 2 ?tx true]
|
||||
[65538 :test/many 3 ?tx true]
|
||||
[?tx :db/txInstant ?ms ?tx true]]"
|
||||
);
|
||||
|
||||
let second = "[
|
||||
[65536 :db/ident :test/one]
|
||||
[65536 :db/valueType :db.type/long]
|
||||
[65536 :db/cardinality :db.cardinality/one]
|
||||
[65536 :db/unique :db.unique/identity]
|
||||
[65536 :db/index true]
|
||||
[65537 :db/ident :test/many]
|
||||
[65537 :db/valueType :db.type/long]
|
||||
[65537 :db/cardinality :db.cardinality/many]
|
||||
[65538 :test/one 1]
|
||||
[65538 :test/many 2]
|
||||
[65538 :test/many 3]
|
||||
]";
|
||||
assert_matches!(conn.datoms(), second);
|
||||
|
||||
let tx_report2 = assert_transact!(
|
||||
conn,
|
||||
r#"[
|
||||
[:db/add 65538 :test/one 2]
|
||||
[:db/add 65538 :test/many 2]
|
||||
[:db/retract 65538 :test/many 3]
|
||||
[:db/add 65538 :test/many 4]
|
||||
]"#
|
||||
);
|
||||
let schema2 = conn.schema.clone();
|
||||
|
||||
assert_matches!(
|
||||
conn.last_transaction(),
|
||||
"[[65538 :test/one 1 ?tx false]
|
||||
[65538 :test/one 2 ?tx true]
|
||||
[65538 :test/many 3 ?tx false]
|
||||
[65538 :test/many 4 ?tx true]
|
||||
[?tx :db/txInstant ?ms ?tx true]]"
|
||||
);
|
||||
|
||||
let third = "[
|
||||
[65536 :db/ident :test/one]
|
||||
[65536 :db/valueType :db.type/long]
|
||||
[65536 :db/cardinality :db.cardinality/one]
|
||||
[65536 :db/unique :db.unique/identity]
|
||||
[65536 :db/index true]
|
||||
[65537 :db/ident :test/many]
|
||||
[65537 :db/valueType :db.type/long]
|
||||
[65537 :db/cardinality :db.cardinality/many]
|
||||
[65538 :test/one 2]
|
||||
[65538 :test/many 2]
|
||||
[65538 :test/many 4]
|
||||
]";
|
||||
assert_matches!(conn.datoms(), third);
|
||||
|
||||
let (new_schema, new_partition_map) = move_from_main_timeline(
|
||||
&conn.sqlite,
|
||||
&conn.schema,
|
||||
conn.partition_map.clone(),
|
||||
tx_report2.tx_id..,
|
||||
1,
|
||||
)
|
||||
.expect("moved timeline");
|
||||
update_conn(&mut conn, &new_schema, &new_partition_map);
|
||||
|
||||
assert_matches!(conn.datoms(), second);
|
||||
// Moving didn't change the schema.
|
||||
assert_eq!(None, new_schema);
|
||||
assert_eq!(conn.schema, schema2);
|
||||
// But it did change the partition map.
|
||||
assert_eq!(conn.partition_map, partition_map1);
|
||||
|
||||
let (new_schema, new_partition_map) = move_from_main_timeline(
|
||||
&conn.sqlite,
|
||||
&conn.schema,
|
||||
conn.partition_map.clone(),
|
||||
tx_report1.tx_id..,
|
||||
2,
|
||||
)
|
||||
.expect("moved timeline");
|
||||
update_conn(&mut conn, &new_schema, &new_partition_map);
|
||||
assert_matches!(conn.datoms(), first);
|
||||
assert_eq!(None, new_schema);
|
||||
assert_eq!(schema1, conn.schema);
|
||||
assert_eq!(conn.partition_map, partition_map0);
|
||||
|
||||
let (new_schema, new_partition_map) = move_from_main_timeline(
|
||||
&conn.sqlite,
|
||||
&conn.schema,
|
||||
conn.partition_map.clone(),
|
||||
tx_report0.tx_id..,
|
||||
3,
|
||||
)
|
||||
.expect("moved timeline");
|
||||
update_conn(&mut conn, &new_schema, &new_partition_map);
|
||||
assert_eq!(true, new_schema.is_some());
|
||||
assert_eq!(bootstrap::bootstrap_schema(), conn.schema);
|
||||
assert_eq!(partition_map_after_bootstrap, conn.partition_map);
|
||||
assert_matches!(conn.datoms(), "[]");
|
||||
assert_matches!(conn.transactions(), "[]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_range() {
|
||||
let mut conn = TestConn::default();
|
||||
conn.sanitized_partition_map();
|
||||
|
||||
let partition_map_after_bootstrap = conn.partition_map.clone();
|
||||
|
||||
assert_eq!(
|
||||
(65536..65539),
|
||||
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 65537 :db/ident :test/many :db/valueType :db.type/long :db/cardinality :db.cardinality/many}
|
||||
]"#
|
||||
);
|
||||
|
||||
assert_transact!(
|
||||
conn,
|
||||
r#"[
|
||||
[:db/add 65538 :test/one 1]
|
||||
[:db/add 65538 :test/many 2]
|
||||
[:db/add 65538 :test/many 3]
|
||||
]"#
|
||||
);
|
||||
|
||||
assert_transact!(
|
||||
conn,
|
||||
r#"[
|
||||
[:db/add 65538 :test/one 2]
|
||||
[:db/add 65538 :test/many 2]
|
||||
[:db/retract 65538 :test/many 3]
|
||||
[:db/add 65538 :test/many 4]
|
||||
]"#
|
||||
);
|
||||
|
||||
// Remove all of these transactions from the main timeline,
|
||||
// ensure we get back to a "just bootstrapped" state.
|
||||
let (new_schema, new_partition_map) = move_from_main_timeline(
|
||||
&conn.sqlite,
|
||||
&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);
|
||||
assert_eq!(true, new_schema.is_some());
|
||||
assert_eq!(bootstrap::bootstrap_schema(), conn.schema);
|
||||
assert_eq!(partition_map_after_bootstrap, conn.partition_map);
|
||||
assert_matches!(conn.datoms(), "[]");
|
||||
assert_matches!(conn.transactions(), "[]");
|
||||
}
|
||||
}
|
1157
db/src/tx.rs
Normal file
1157
db/src/tx.rs
Normal file
File diff suppressed because it is too large
Load diff
75
db/src/tx_checking.rs
Normal file
75
db/src/tx_checking.rs
Normal file
|
@ -0,0 +1,75 @@
|
|||
// 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.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use core_traits::{Entid, TypedValue, ValueType};
|
||||
|
||||
use db_traits::errors::CardinalityConflict;
|
||||
|
||||
use crate::internal_types::AEVTrie;
|
||||
|
||||
/// Map from found [e a v] to expected type.
|
||||
pub(crate) type TypeDisagreements = BTreeMap<(Entid, Entid, TypedValue), ValueType>;
|
||||
|
||||
/// Ensure that the given terms type check.
|
||||
///
|
||||
/// We try to be maximally helpful by yielding every malformed datom, rather than only the first.
|
||||
/// In the future, we might change this choice, or allow the consumer to specify the robustness of
|
||||
/// the type checking desired, since there is a cost to providing helpful diagnostics.
|
||||
pub(crate) fn type_disagreements<'schema>(aev_trie: &AEVTrie<'schema>) -> TypeDisagreements {
|
||||
let mut errors: TypeDisagreements = TypeDisagreements::default();
|
||||
|
||||
for (&(a, attribute), evs) in aev_trie {
|
||||
for (&e, ref ars) in evs {
|
||||
for v in ars.add.iter().chain(ars.retract.iter()) {
|
||||
if attribute.value_type != v.value_type() {
|
||||
errors.insert((e, a, v.clone()), attribute.value_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errors
|
||||
}
|
||||
|
||||
/// Ensure that the given terms obey the cardinality restrictions of the given schema.
|
||||
///
|
||||
/// That is, ensure that any cardinality one attribute is added with at most one distinct value for
|
||||
/// any specific entity (although that one value may be repeated for the given entity).
|
||||
/// It is an error to:
|
||||
///
|
||||
/// - add two distinct values for the same cardinality one attribute and entity in a single transaction
|
||||
/// - add and remove the same values for the same attribute and entity in a single transaction
|
||||
///
|
||||
/// 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
|
||||
/// consumer to specify the robustness of the cardinality checking desired.
|
||||
pub(crate) fn cardinality_conflicts<'schema>(
|
||||
aev_trie: &AEVTrie<'schema>,
|
||||
) -> Vec<CardinalityConflict> {
|
||||
let mut errors = vec![];
|
||||
|
||||
for (&(a, attribute), evs) in aev_trie {
|
||||
for (&e, ref ars) in evs {
|
||||
if !attribute.multival && ars.add.len() > 1 {
|
||||
let vs = ars.add.clone();
|
||||
errors.push(CardinalityConflict::CardinalityOneAddConflict { e, a, vs });
|
||||
}
|
||||
|
||||
let vs: BTreeSet<_> = ars.retract.intersection(&ars.add).cloned().collect();
|
||||
if !vs.is_empty() {
|
||||
errors.push(CardinalityConflict::AddRetractConflict { e, a, vs })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errors
|
||||
}
|
211
db/src/tx_observer.rs
Normal file
211
db/src/tx_observer.rs
Normal file
|
@ -0,0 +1,211 @@
|
|||
// 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.
|
||||
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use std::sync::mpsc::{channel, Receiver, RecvError, Sender};
|
||||
|
||||
use std::thread;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use core_traits::{Entid, TypedValue};
|
||||
|
||||
use mentat_core::Schema;
|
||||
|
||||
use edn::entities::OpType;
|
||||
|
||||
use db_traits::errors::Result;
|
||||
|
||||
use crate::types::AttributeSet;
|
||||
|
||||
use crate::watcher::TransactWatcher;
|
||||
|
||||
pub struct TxObserver {
|
||||
#[allow(clippy::type_complexity)]
|
||||
notify_fn: Arc<Box<dyn Fn(&str, IndexMap<&Entid, &AttributeSet>) + Send + Sync>>,
|
||||
attributes: AttributeSet,
|
||||
}
|
||||
|
||||
impl TxObserver {
|
||||
pub fn new<F>(attributes: AttributeSet, notify_fn: F) -> TxObserver
|
||||
where
|
||||
F: Fn(&str, IndexMap<&Entid, &AttributeSet>) + 'static + Send + Sync,
|
||||
{
|
||||
TxObserver {
|
||||
notify_fn: Arc::new(Box::new(notify_fn)),
|
||||
attributes,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn applicable_reports<'r>(
|
||||
&self,
|
||||
reports: &'r IndexMap<Entid, AttributeSet>,
|
||||
) -> IndexMap<&'r Entid, &'r AttributeSet> {
|
||||
reports
|
||||
.into_iter()
|
||||
.filter(|&(_txid, attrs)| !self.attributes.is_disjoint(attrs))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn notify(&self, key: &str, reports: IndexMap<&Entid, &AttributeSet>) {
|
||||
(*self.notify_fn)(key, reports);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Command {
|
||||
fn execute(&mut self);
|
||||
}
|
||||
|
||||
pub struct TxCommand {
|
||||
reports: IndexMap<Entid, AttributeSet>,
|
||||
observers: Weak<IndexMap<String, Arc<TxObserver>>>,
|
||||
}
|
||||
|
||||
impl TxCommand {
|
||||
fn new(
|
||||
observers: &Arc<IndexMap<String, Arc<TxObserver>>>,
|
||||
reports: IndexMap<Entid, AttributeSet>,
|
||||
) -> Self {
|
||||
TxCommand {
|
||||
reports,
|
||||
observers: Arc::downgrade(observers),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Command for TxCommand {
|
||||
fn execute(&mut self) {
|
||||
if let Some(observers) = self.observers.upgrade() {
|
||||
for (key, observer) in observers.iter() {
|
||||
let applicable_reports = observer.applicable_reports(&self.reports);
|
||||
if !applicable_reports.is_empty() {
|
||||
observer.notify(&key, applicable_reports);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TxObservationService {
|
||||
observers: Arc<IndexMap<String, Arc<TxObserver>>>,
|
||||
executor: Option<Sender<Box<dyn Command + Send>>>,
|
||||
}
|
||||
|
||||
impl TxObservationService {
|
||||
pub fn new() -> Self {
|
||||
TxObservationService {
|
||||
observers: Arc::new(IndexMap::new()),
|
||||
executor: None,
|
||||
}
|
||||
}
|
||||
|
||||
// For testing purposes
|
||||
pub fn is_registered(&self, key: &str) -> bool {
|
||||
self.observers.contains_key(key)
|
||||
}
|
||||
|
||||
pub fn register(&mut self, key: String, observer: Arc<TxObserver>) {
|
||||
Arc::make_mut(&mut self.observers).insert(key, observer);
|
||||
}
|
||||
|
||||
pub fn deregister(&mut self, key: &str) {
|
||||
Arc::make_mut(&mut self.observers).remove(key);
|
||||
}
|
||||
|
||||
pub fn has_observers(&self) -> bool {
|
||||
!self.observers.is_empty()
|
||||
}
|
||||
|
||||
pub fn in_progress_did_commit(&mut self, txes: IndexMap<Entid, AttributeSet>) {
|
||||
// Don't spawn a thread only to say nothing.
|
||||
if !self.has_observers() {
|
||||
return;
|
||||
}
|
||||
|
||||
let executor = self.executor.get_or_insert_with(|| {
|
||||
#[allow(clippy::type_complexity)]
|
||||
let (tx, rx): (
|
||||
Sender<Box<dyn Command + Send>>,
|
||||
Receiver<Box<dyn Command + Send>>,
|
||||
) = channel();
|
||||
let mut worker = CommandExecutor::new(rx);
|
||||
|
||||
thread::spawn(move || {
|
||||
worker.main();
|
||||
});
|
||||
|
||||
tx
|
||||
});
|
||||
|
||||
let cmd = Box::new(TxCommand::new(&self.observers, txes));
|
||||
executor.send(cmd).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TxObservationService {
|
||||
fn drop(&mut self) {
|
||||
self.executor = None;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct InProgressObserverTransactWatcher {
|
||||
collected_attributes: AttributeSet,
|
||||
pub txes: IndexMap<Entid, AttributeSet>,
|
||||
}
|
||||
|
||||
impl InProgressObserverTransactWatcher {
|
||||
pub fn new() -> InProgressObserverTransactWatcher {
|
||||
InProgressObserverTransactWatcher {
|
||||
collected_attributes: Default::default(),
|
||||
txes: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TransactWatcher for InProgressObserverTransactWatcher {
|
||||
fn datom(&mut self, _op: OpType, _e: Entid, a: Entid, _v: &TypedValue) {
|
||||
self.collected_attributes.insert(a);
|
||||
}
|
||||
|
||||
fn done(&mut self, t: &Entid, _schema: &Schema) -> Result<()> {
|
||||
let collected_attributes = ::std::mem::take(&mut self.collected_attributes);
|
||||
self.txes.insert(*t, collected_attributes);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct CommandExecutor {
|
||||
receiver: Receiver<Box<dyn Command + Send>>,
|
||||
}
|
||||
|
||||
impl CommandExecutor {
|
||||
fn new(rx: Receiver<Box<dyn Command + Send>>) -> Self {
|
||||
CommandExecutor { receiver: rx }
|
||||
}
|
||||
|
||||
fn main(&mut self) {
|
||||
loop {
|
||||
match self.receiver.recv() {
|
||||
Err(RecvError) => {
|
||||
// "The recv operation can only fail if the sending half of a channel (or
|
||||
// sync_channel) is disconnected, implying that no further messages will ever be
|
||||
// received."
|
||||
// No need to log here.
|
||||
return;
|
||||
}
|
||||
|
||||
Ok(mut cmd) => cmd.execute(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
230
db/src/types.rs
Normal file
230
db/src/types.rs
Normal file
|
@ -0,0 +1,230 @@
|
|||
// 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::collections::{BTreeMap, BTreeSet, HashMap};
|
||||
use std::iter::FromIterator;
|
||||
use std::ops::{Deref, DerefMut, Range};
|
||||
|
||||
extern crate mentat_core;
|
||||
|
||||
use core_traits::{Entid, TypedValue, ValueType};
|
||||
|
||||
pub use self::mentat_core::{DateTime, Schema, Utc};
|
||||
|
||||
use edn::entities::{EntityPlace, TempId};
|
||||
|
||||
use db_traits::errors;
|
||||
|
||||
/// Represents one partition of the entid space.
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||
#[cfg_attr(feature = "syncable", derive(Serialize, Deserialize))]
|
||||
pub struct Partition {
|
||||
/// The first entid in the partition.
|
||||
pub start: Entid,
|
||||
/// Maximum allowed entid in the partition.
|
||||
pub end: Entid,
|
||||
/// `true` if entids in the partition can be excised with `:db/excise`.
|
||||
pub allow_excision: bool,
|
||||
/// The next entid to be allocated in the partition.
|
||||
/// Unless you must use this directly, prefer using provided setter and getter helpers.
|
||||
pub(crate) next_entid_to_allocate: Entid,
|
||||
}
|
||||
|
||||
impl Partition {
|
||||
pub fn new(
|
||||
start: Entid,
|
||||
end: Entid,
|
||||
next_entid_to_allocate: Entid,
|
||||
allow_excision: bool,
|
||||
) -> Partition {
|
||||
assert!(
|
||||
start <= next_entid_to_allocate && next_entid_to_allocate <= end,
|
||||
"A partition represents a monotonic increasing sequence of entids."
|
||||
);
|
||||
Partition {
|
||||
start,
|
||||
end,
|
||||
next_entid_to_allocate,
|
||||
allow_excision,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains_entid(&self, e: Entid) -> bool {
|
||||
(e >= self.start) && (e < self.next_entid_to_allocate)
|
||||
}
|
||||
|
||||
pub fn allows_entid(&self, e: Entid) -> bool {
|
||||
(e >= self.start) && (e <= self.end)
|
||||
}
|
||||
|
||||
pub fn next_entid(&self) -> Entid {
|
||||
self.next_entid_to_allocate
|
||||
}
|
||||
|
||||
pub fn set_next_entid(&mut self, e: Entid) {
|
||||
assert!(
|
||||
self.allows_entid(e),
|
||||
"Partition index must be within its allocated space."
|
||||
);
|
||||
self.next_entid_to_allocate = e;
|
||||
}
|
||||
|
||||
pub fn allocate_entids(&mut self, n: usize) -> Range<i64> {
|
||||
let idx = self.next_entid();
|
||||
self.set_next_entid(idx + n as i64);
|
||||
idx..self.next_entid()
|
||||
}
|
||||
}
|
||||
|
||||
/// Map partition names to `Partition` instances.
|
||||
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||
#[cfg_attr(feature = "syncable", derive(Serialize, Deserialize))]
|
||||
pub struct PartitionMap(BTreeMap<String, Partition>);
|
||||
|
||||
impl Deref for PartitionMap {
|
||||
type Target = BTreeMap<String, Partition>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for PartitionMap {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(String, Partition)> for PartitionMap {
|
||||
fn from_iter<T: IntoIterator<Item = (String, Partition)>>(iter: T) -> Self {
|
||||
PartitionMap(iter.into_iter().collect())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the metadata required to query from, or apply transactions to, a Mentat store.
|
||||
///
|
||||
/// See https://github.com/mozilla/mentat/wiki/Thoughts:-modeling-db-conn-in-Rust.
|
||||
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||
pub struct DB {
|
||||
/// Map partition name->`Partition`.
|
||||
///
|
||||
/// TODO: represent partitions as entids.
|
||||
pub partition_map: PartitionMap,
|
||||
|
||||
/// The schema of the store.
|
||||
pub schema: Schema,
|
||||
}
|
||||
|
||||
impl DB {
|
||||
pub fn new(partition_map: PartitionMap, schema: Schema) -> DB {
|
||||
DB {
|
||||
partition_map,
|
||||
schema,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A pair [a v] in the store.
|
||||
///
|
||||
/// Used to represent lookup-refs and [TEMPID a v] upserts as they are resolved.
|
||||
pub type AVPair = (Entid, TypedValue);
|
||||
|
||||
/// Used to represent assertions and retractions.
|
||||
pub(crate) type EAV = (Entid, Entid, TypedValue);
|
||||
|
||||
/// Map [a v] pairs to existing entids.
|
||||
///
|
||||
/// Used to resolve lookup-refs and upserts.
|
||||
pub type AVMap<'a> = HashMap<&'a AVPair, Entid>;
|
||||
|
||||
// represents a set of entids that are correspond to attributes
|
||||
pub type AttributeSet = BTreeSet<Entid>;
|
||||
|
||||
/// The transactor is tied to `edn::ValueAndSpan` right now, but in the future we'd like to support
|
||||
/// `TypedValue` directly for programmatic use. `TransactableValue` encapsulates the interface
|
||||
/// value types (i.e., values in the value place) need to support to be transacted.
|
||||
pub trait TransactableValue: Clone {
|
||||
/// 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.
|
||||
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
|
||||
/// to valid entity places.
|
||||
fn into_entity_place(self) -> errors::Result<EntityPlace<Self>>;
|
||||
|
||||
fn as_tempid(&self) -> Option<TempId>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Partition;
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "A partition represents a monotonic increasing sequence of entids.")]
|
||||
fn test_partition_limits_sanity1() {
|
||||
Partition::new(100, 1000, 1001, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "A partition represents a monotonic increasing sequence of entids.")]
|
||||
fn test_partition_limits_sanity2() {
|
||||
Partition::new(100, 1000, 99, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Partition index must be within its allocated space.")]
|
||||
fn test_partition_limits_boundary1() {
|
||||
let mut part = Partition::new(100, 1000, 100, true);
|
||||
part.set_next_entid(2000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Partition index must be within its allocated space.")]
|
||||
fn test_partition_limits_boundary2() {
|
||||
let mut part = Partition::new(100, 1000, 100, true);
|
||||
part.set_next_entid(1001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Partition index must be within its allocated space.")]
|
||||
fn test_partition_limits_boundary3() {
|
||||
let mut part = Partition::new(100, 1000, 100, true);
|
||||
part.set_next_entid(99);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Partition index must be within its allocated space.")]
|
||||
fn test_partition_limits_boundary4() {
|
||||
let mut part = Partition::new(100, 1000, 100, true);
|
||||
part.set_next_entid(-100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Partition index must be within its allocated space.")]
|
||||
fn test_partition_limits_boundary5() {
|
||||
let mut part = Partition::new(100, 1000, 100, true);
|
||||
part.allocate_entids(901); // One more than allowed.
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_partition_limits_boundary6() {
|
||||
let mut part = Partition::new(100, 1000, 100, true);
|
||||
part.set_next_entid(100); // First entid that's allowed.
|
||||
part.set_next_entid(101); // Just after first.
|
||||
|
||||
assert_eq!(101..111, part.allocate_entids(10));
|
||||
|
||||
part.set_next_entid(1000); // Last entid that's allowed.
|
||||
part.set_next_entid(999); // Just before last.
|
||||
}
|
||||
}
|
408
db/src/upsert_resolution.rs
Normal file
408
db/src/upsert_resolution.rs
Normal file
|
@ -0,0 +1,408 @@
|
|||
// 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)]
|
||||
|
||||
//! This module implements the upsert resolution algorithm described at
|
||||
//! https://github.com/mozilla/mentat/wiki/Transacting:-upsert-resolution-algorithm.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use indexmap;
|
||||
use petgraph::unionfind;
|
||||
|
||||
use crate::internal_types::{
|
||||
Population, TempIdHandle, TempIdMap, Term, TermWithTempIds, TermWithoutTempIds, TypedValueOr,
|
||||
};
|
||||
use crate::types::AVPair;
|
||||
use db_traits::errors::{DbErrorKind, Result};
|
||||
|
||||
use mentat_core::util::Either::*;
|
||||
|
||||
use core_traits::{attribute, Attribute, Entid, TypedValue};
|
||||
|
||||
use crate::schema::SchemaBuilding;
|
||||
use edn::entities::OpType;
|
||||
use mentat_core::Schema;
|
||||
|
||||
/// 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)]
|
||||
struct UpsertE(TempIdHandle, Entid, TypedValue);
|
||||
|
||||
/// A "Complex upsert" that looks like [:db/add TEMPID a OTHERID], where a is :db.unique/identity
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||
struct UpsertEV(TempIdHandle, Entid, TempIdHandle);
|
||||
|
||||
/// A generation collects entities into populations at a single evolutionary step in the upsert
|
||||
/// resolution evolution process.
|
||||
///
|
||||
/// The upsert resolution process is only concerned with [:db/add ...] entities until the final
|
||||
/// entid allocations. That's why we separate into special simple and complex upsert types
|
||||
/// immediately, and then collect the more general term types for final resolution.
|
||||
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||
pub(crate) struct Generation {
|
||||
/// "Simple upserts" that look like [:db/add TEMPID a v], where a is :db.unique/identity.
|
||||
upserts_e: Vec<UpsertE>,
|
||||
|
||||
/// "Complex upserts" that look like [:db/add TEMPID a OTHERID], where a is :db.unique/identity
|
||||
upserts_ev: Vec<UpsertEV>,
|
||||
|
||||
/// Entities that look like:
|
||||
/// - [:db/add TEMPID b OTHERID]. b may be :db.unique/identity if it has failed to upsert.
|
||||
/// - [:db/add TEMPID b v]. b may be :db.unique/identity if it has failed to upsert.
|
||||
/// - [:db/add e b OTHERID].
|
||||
allocations: Vec<TermWithTempIds>,
|
||||
|
||||
/// Entities that upserted and no longer reference tempids. These assertions are guaranteed to
|
||||
/// be in the store.
|
||||
upserted: Vec<TermWithoutTempIds>,
|
||||
|
||||
/// Entities that resolved due to other upserts and no longer reference tempids. These
|
||||
/// assertions may or may not be in the store.
|
||||
resolved: Vec<TermWithoutTempIds>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||
pub(crate) struct FinalPopulations {
|
||||
/// Upserts that upserted.
|
||||
pub upserted: Vec<TermWithoutTempIds>,
|
||||
|
||||
/// Allocations that resolved due to other upserts.
|
||||
pub resolved: Vec<TermWithoutTempIds>,
|
||||
|
||||
/// Allocations that required new entid allocations.
|
||||
pub allocated: Vec<TermWithoutTempIds>,
|
||||
}
|
||||
|
||||
impl Generation {
|
||||
/// 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.
|
||||
pub(crate) fn from<I>(terms: I, schema: &Schema) -> Result<(Generation, Population)>
|
||||
where
|
||||
I: IntoIterator<Item = TermWithTempIds>,
|
||||
{
|
||||
let mut generation = Generation::default();
|
||||
let mut inert = vec![];
|
||||
|
||||
let is_unique = |a: Entid| -> Result<bool> {
|
||||
let attribute: &Attribute = schema.require_attribute_for_entid(a)?;
|
||||
Ok(attribute.unique == Some(attribute::Unique::Identity))
|
||||
};
|
||||
|
||||
for term in terms.into_iter() {
|
||||
match term {
|
||||
Term::AddOrRetract(op, Right(e), a, Right(v)) => {
|
||||
if op == OpType::Add && is_unique(a)? {
|
||||
generation.upserts_ev.push(UpsertEV(e, a, v));
|
||||
} else {
|
||||
generation
|
||||
.allocations
|
||||
.push(Term::AddOrRetract(op, Right(e), a, Right(v)));
|
||||
}
|
||||
}
|
||||
Term::AddOrRetract(op, Right(e), a, Left(v)) => {
|
||||
if op == OpType::Add && is_unique(a)? {
|
||||
generation.upserts_e.push(UpsertE(e, a, v));
|
||||
} else {
|
||||
generation
|
||||
.allocations
|
||||
.push(Term::AddOrRetract(op, Right(e), a, Left(v)));
|
||||
}
|
||||
}
|
||||
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)) => {
|
||||
inert.push(Term::AddOrRetract(op, Left(e), a, Left(v)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((generation, inert))
|
||||
}
|
||||
|
||||
/// Return true if it's possible to evolve this generation further.
|
||||
///
|
||||
/// Note that there can be complex upserts but no simple upserts to help resolve them, and in
|
||||
/// this case, we cannot evolve further.
|
||||
pub(crate) fn can_evolve(&self) -> bool {
|
||||
!self.upserts_e.is_empty()
|
||||
}
|
||||
|
||||
/// Evolve this generation one step further by rewriting the existing :db/add entities using the
|
||||
/// given temporary IDs.
|
||||
///
|
||||
/// TODO: Considering doing this in place; the function already consumes `self`.
|
||||
pub(crate) fn evolve_one_step(self, temp_id_map: &TempIdMap) -> Generation {
|
||||
let mut next = Generation::default();
|
||||
|
||||
// We'll iterate our own allocations to resolve more things, but terms that have already
|
||||
// resolved stay resolved.
|
||||
next.resolved = self.resolved;
|
||||
|
||||
for UpsertE(t, a, v) in self.upserts_e {
|
||||
match temp_id_map.get(&*t) {
|
||||
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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for UpsertEV(t1, a, t2) in self.upserts_ev {
|
||||
match (temp_id_map.get(&*t1), temp_id_map.get(&*t2)) {
|
||||
(Some(_), Some(&n2)) => {
|
||||
// Even though we can resolve entirely, it's possible that the remaining upsert
|
||||
// could conflict. Moving straight to resolved doesn't give us a chance to
|
||||
// search the store for the conflict.
|
||||
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)))
|
||||
}
|
||||
(None, None) => next.upserts_ev.push(UpsertEV(t1, a, t2)),
|
||||
}
|
||||
}
|
||||
|
||||
// There's no particular need to separate resolved from allocations right here and right
|
||||
// now, although it is convenient.
|
||||
for term in self.allocations {
|
||||
// TODO: find an expression that destructures less? I still expect this to be efficient
|
||||
// but it's a little verbose.
|
||||
match term {
|
||||
Term::AddOrRetract(op, Right(t1), a, Right(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)))
|
||||
}
|
||||
(None, Some(&n2)) => next.allocations.push(Term::AddOrRetract(
|
||||
op,
|
||||
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)),
|
||||
None => next
|
||||
.allocations
|
||||
.push(Term::AddOrRetract(op, Right(t), a, Left(v))),
|
||||
},
|
||||
Term::AddOrRetract(op, Left(e), a, Right(t)) => match temp_id_map.get(&*t) {
|
||||
Some(&n) => {
|
||||
next.resolved
|
||||
.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!(),
|
||||
}
|
||||
}
|
||||
|
||||
next
|
||||
}
|
||||
|
||||
// Collect id->[a v] pairs that might upsert at this evolutionary step.
|
||||
pub(crate) fn temp_id_avs(&self) -> Vec<(TempIdHandle, AVPair)> {
|
||||
let mut temp_id_avs: Vec<(TempIdHandle, AVPair)> = vec![];
|
||||
// TODO: map/collect.
|
||||
for &UpsertE(ref t, ref a, ref v) in &self.upserts_e {
|
||||
// TODO: figure out how to make this less expensive, i.e., don't require
|
||||
// clone() of an arbitrary value.
|
||||
temp_id_avs.push((t.clone(), (*a, v.clone())));
|
||||
}
|
||||
temp_id_avs
|
||||
}
|
||||
|
||||
/// Evolve potential upserts that haven't resolved into allocations.
|
||||
pub(crate) fn allocate_unresolved_upserts(&mut self) -> Result<()> {
|
||||
let mut upserts_ev = vec![];
|
||||
::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))
|
||||
}),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// After evolution is complete, yield the set of tempids that require entid allocation.
|
||||
///
|
||||
/// Some of the tempids may be identified, so we also provide a map from tempid to a dense set
|
||||
/// of contiguous integer labels.
|
||||
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_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 tempid_avs: BTreeMap<(Entid, TypedValueOr<TempIdHandle>), Vec<TempIdHandle>> =
|
||||
BTreeMap::default();
|
||||
|
||||
for term in self.allocations.iter() {
|
||||
match term {
|
||||
Term::AddOrRetract(OpType::Add, Right(ref t1), a, Right(ref t2)) => {
|
||||
temp_ids.insert(t1.clone());
|
||||
temp_ids.insert(t2.clone());
|
||||
let attribute: &Attribute = schema.require_attribute_for_entid(*a)?;
|
||||
if attribute.unique == Some(attribute::Unique::Identity) {
|
||||
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(_)) => {
|
||||
temp_ids.insert(t.clone());
|
||||
let attribute: &Attribute = schema.require_attribute_for_entid(*a)?;
|
||||
if attribute.unique == Some(attribute::Unique::Identity) {
|
||||
tempid_avs
|
||||
.entry((*a, x.clone()))
|
||||
.or_insert_with(Vec::new)
|
||||
.push(t.clone());
|
||||
}
|
||||
}
|
||||
Term::AddOrRetract(OpType::Add, Left(_), _, Right(ref t)) => {
|
||||
temp_ids.insert(t.clone());
|
||||
}
|
||||
Term::AddOrRetract(OpType::Add, Left(_), _, Left(_)) => unreachable!(),
|
||||
Term::AddOrRetract(OpType::Retract, _, _, _) => {
|
||||
// [:db/retract ...] entities never allocate entids; they have to resolve due to
|
||||
// other upserts (or they fail the transaction).
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now we union-find all the known tempids. Two tempids are unioned if they both appear as
|
||||
// the entity of an `[a v]` upsert, including when the value column `v` is itself a tempid.
|
||||
let mut uf = unionfind::UnionFind::new(temp_ids.len());
|
||||
|
||||
// The union-find implementation from petgraph operates on contiguous indices, so we need to
|
||||
// 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();
|
||||
|
||||
debug!(
|
||||
"need to label tempids aggregated using tempid_avs {:?}",
|
||||
tempid_avs
|
||||
);
|
||||
|
||||
for vs in tempid_avs.values() {
|
||||
if let Some(&first_index) = vs.first().and_then(|first| temp_ids.get(first)) {
|
||||
for tempid in vs {
|
||||
temp_ids.get(tempid).map(|&i| uf.union(first_index, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug!("union-find aggregation {:?}", uf.clone().into_labeling());
|
||||
|
||||
// Now that we have aggregated tempids, we need to label them using the smallest number of
|
||||
// contiguous labels possible.
|
||||
let mut tempid_map: BTreeMap<TempIdHandle, usize> = BTreeMap::default();
|
||||
|
||||
let mut dense_labels: indexmap::IndexSet<usize> = indexmap::IndexSet::default();
|
||||
|
||||
// We want to produce results that are as deterministic as possible, so we allocate labels
|
||||
// for tempids in sorted order. This has the effect of making "a" allocate before "b",
|
||||
// which is pleasant for testing.
|
||||
for (tempid, tempid_index) in temp_ids {
|
||||
let rep = uf.find_mut(tempid_index);
|
||||
dense_labels.insert(rep);
|
||||
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
|
||||
);
|
||||
|
||||
Ok(tempid_map)
|
||||
}
|
||||
|
||||
/// After evolution is complete, use the provided allocated entids to segment `self` into
|
||||
/// populations, each with no references to tempids.
|
||||
pub(crate) fn into_final_populations(
|
||||
self,
|
||||
temp_id_map: &TempIdMap,
|
||||
) -> Result<FinalPopulations> {
|
||||
assert!(self.upserts_e.is_empty());
|
||||
assert!(self.upserts_ev.is_empty());
|
||||
|
||||
let mut populations = FinalPopulations::default();
|
||||
|
||||
populations.upserted = self.upserted;
|
||||
populations.resolved = self.resolved;
|
||||
|
||||
for term in self.allocations {
|
||||
let allocated = match term {
|
||||
// TODO: consider require implementing require on temp_id_map.
|
||||
Term::AddOrRetract(op, Right(t1), a, Right(t2)) => {
|
||||
match (op, temp_id_map.get(&*t1), temp_id_map.get(&*t2)) {
|
||||
(op, Some(&n1), Some(&n2)) => Term::AddOrRetract(op, n1, a, TypedValue::Ref(n2.0)),
|
||||
(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))),
|
||||
}
|
||||
}
|
||||
Term::AddOrRetract(op, Right(t), a, Left(v)) => {
|
||||
match (op, temp_id_map.get(&*t)) {
|
||||
(op, Some(&n)) => Term::AddOrRetract(op, n, a, v),
|
||||
(OpType::Add, _) => unreachable!(), // This is a coding error.
|
||||
(OpType::Retract, _) => bail!(DbErrorKind::NotYetImplemented(format!(
|
||||
"[:db/retract ...] entity referenced tempid that did not upsert: {}",
|
||||
t
|
||||
))),
|
||||
}
|
||||
}
|
||||
Term::AddOrRetract(op, Left(e), a, Right(t)) => {
|
||||
match (op, temp_id_map.get(&*t)) {
|
||||
(op, Some(&n)) => Term::AddOrRetract(op, e, a, TypedValue::Ref(n.0)),
|
||||
(OpType::Add, _) => unreachable!(), // This is a coding error.
|
||||
(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.
|
||||
};
|
||||
populations.allocated.push(allocated);
|
||||
}
|
||||
|
||||
Ok(populations)
|
||||
}
|
||||
}
|
46
db/src/watcher.rs
Normal file
46
db/src/watcher.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
// 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.
|
||||
|
||||
// A trivial interface for extracting information from a transact as it happens.
|
||||
// We have two situations in which we need to do this:
|
||||
//
|
||||
// - InProgress and Conn both have attribute caches. InProgress's is different from Conn's,
|
||||
// because it needs to be able to roll back. These wish to see changes in a certain set of
|
||||
// attributes in order to synchronously update the cache during a write.
|
||||
// - When observers are registered we want to flip some flags as writes occur so that we can
|
||||
// notifying them outside the transaction.
|
||||
|
||||
use core_traits::{Entid, TypedValue};
|
||||
|
||||
use mentat_core::Schema;
|
||||
|
||||
use edn::entities::OpType;
|
||||
|
||||
use db_traits::errors::Result;
|
||||
|
||||
pub trait TransactWatcher {
|
||||
fn datom(&mut self, op: OpType, e: Entid, a: Entid, v: &TypedValue);
|
||||
|
||||
/// Only return an error if you want to interrupt the transact!
|
||||
/// Called with the schema _prior to_ the transact -- any attributes or
|
||||
/// attribute changes transacted during this transact are not reflected in
|
||||
/// the schema.
|
||||
fn done(&mut self, t: &Entid, schema: &Schema) -> Result<()>;
|
||||
}
|
||||
|
||||
pub struct NullWatcher();
|
||||
|
||||
impl TransactWatcher for NullWatcher {
|
||||
fn datom(&mut self, _op: OpType, _e: Entid, _a: Entid, _v: &TypedValue) {}
|
||||
|
||||
fn done(&mut self, _t: &Entid, _schema: &Schema) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
122
db/tests/value_tests.rs
Normal file
122
db/tests/value_tests.rs
Normal file
|
@ -0,0 +1,122 @@
|
|||
// 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.
|
||||
|
||||
extern crate core_traits;
|
||||
extern crate edn;
|
||||
extern crate mentat_db;
|
||||
extern crate ordered_float;
|
||||
extern crate rusqlite;
|
||||
|
||||
use ordered_float::OrderedFloat;
|
||||
|
||||
use edn::symbols;
|
||||
|
||||
use core_traits::{TypedValue, ValueType};
|
||||
use mentat_db::db::TypedSQLValue;
|
||||
|
||||
// It's not possible to test to_sql_value_pair since rusqlite::ToSqlOutput doesn't implement
|
||||
// PartialEq.
|
||||
#[test]
|
||||
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(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!(
|
||||
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!(
|
||||
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!(
|
||||
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]
|
||||
fn test_to_edn_value_pair() {
|
||||
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!(
|
||||
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!(
|
||||
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!(
|
||||
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!(
|
||||
TypedValue::typed_ns_keyword("db", "keyword").to_edn_value_pair(),
|
||||
(
|
||||
edn::Value::Keyword(symbols::Keyword::namespaced("db", "keyword")),
|
||||
ValueType::Keyword
|
||||
)
|
||||
);
|
||||
}
|
3
docs/.gitignore
vendored
Normal file
3
docs/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
_site
|
||||
.sass-cache
|
||||
.jekyll-metadata
|
24
docs/404.html
Normal file
24
docs/404.html
Normal file
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
<style type="text/css" media="screen">
|
||||
.container {
|
||||
margin: 10px auto;
|
||||
max-width: 600px;
|
||||
text-align: center;
|
||||
}
|
||||
h1 {
|
||||
margin: 30px 0;
|
||||
font-size: 4em;
|
||||
line-height: 1;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="container">
|
||||
<h1>404</h1>
|
||||
|
||||
<p><strong>Page not found :(</strong></p>
|
||||
<p>The requested page could not be found.</p>
|
||||
</div>
|
|
@ -1,9 +1,17 @@
|
|||
# How to contribute to Datomish
|
||||
---
|
||||
layout: page
|
||||
title: Contributing
|
||||
permalink: /contributing/
|
||||
---
|
||||
|
||||
# How to contribute to Project Mentat
|
||||
|
||||
This project is very new, so we'll probably revise these guidelines. Please
|
||||
comment on a bug before putting significant effort in, if you'd like to
|
||||
contribute.
|
||||
|
||||
You probably want to quickly read the [front page of the wiki](https://github.com/mozilla/mentat/wiki) to get up to speed.
|
||||
|
||||
## Guidelines
|
||||
|
||||
* Follow the Style Guide (see below).
|
||||
|
@ -19,7 +27,7 @@ description including any additional information that might help future
|
|||
spelunkers (see below).
|
||||
|
||||
```
|
||||
Frobnicate the URL bazzer before flattening pilchard, r=mossop,rnewman. Fixes #6.
|
||||
Frobnicate the URL bazzer before flattening pilchard. (#123) r=mossop,rnewman.
|
||||
|
||||
The frobnication method used is as described in Podder's Miscellany, page 15.
|
||||
Note that this pull request doesn't include tests, because we're bad people.
|
||||
|
@ -29,10 +37,10 @@ Signed-off-by: Random J Developer <random@developer.example.org>
|
|||
|
||||
## Example
|
||||
|
||||
* Fork this repo at [github.com/mozilla/datomish](https://github.com/mozilla/datomish#fork-destination-box).
|
||||
* Fork this repo at [github.com/mozilla/mentat](https://github.com/mozilla/mentat#fork-destination-box).
|
||||
* Clone your fork locally. Make sure you use the correct clone URL.
|
||||
```
|
||||
git clone git@github.com:YOURNAME/datomish.git
|
||||
git clone git@github.com:YOURNAME/mentat.git
|
||||
```
|
||||
Check your remotes:
|
||||
```
|
||||
|
@ -40,7 +48,7 @@ git remote --verbose
|
|||
```
|
||||
Make sure you have an upstream remote defined:
|
||||
```
|
||||
git remote add upstream https://github.com/mozilla/datomish
|
||||
git remote add upstream https://github.com/mozilla/mentat
|
||||
```
|
||||
|
||||
* Create a new branch to start working on a bug or feature:
|
||||
|
@ -56,10 +64,10 @@ git commit --signoff --message "Some commit message"
|
|||
* Rebase your work during development and before submitting a pull request,
|
||||
avoiding merge commits, such that your commits are a logical sequence to
|
||||
read rather than a record of your fevered typing.
|
||||
* Make sure you're on the correct branch and are pulling from the correct upstream:
|
||||
* Make sure you're on the correct branch and are pulling from the correct upstream (currently `rust`):
|
||||
```
|
||||
git checkout some-new-branch
|
||||
git pull upstream master --rebase
|
||||
git pull upstream rust --rebase
|
||||
```
|
||||
Or using `git reset --soft` (as described in [a tale of three trees](http://www.infoq.com/presentations/A-Tale-of-Three-Trees))
|
||||
|
||||
|
@ -114,11 +122,43 @@ git commit --amend --reset-author --no-edit
|
|||
|
||||
# Style Guide
|
||||
|
||||
Our Rust code approximately follows the [Rust style guide](https://github.com/rust-lang-nursery/fmt-rfcs/blob/master/guide/guide.md). We use four-space indents, with categorized and alphabetized imports; see the examples in the tree. We try to follow [these guidelines](https://aturon.github.io/), too.
|
||||
|
||||
We do not automatically use `rustfmt` because it tends to make code incrementally worse, but you should be prepared to consider its suggestions.
|
||||
|
||||
An example of 'good' Rust code, omitting the license block:
|
||||
|
||||
```rust
|
||||
#![allow(…)]
|
||||
|
||||
extern crate foo;
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::error::Error;
|
||||
use std::iter::{once, repeat};
|
||||
|
||||
use rusqlite;
|
||||
|
||||
use mentat_core::{
|
||||
Attribute,
|
||||
AttributeBitFlags,
|
||||
Entid,
|
||||
};
|
||||
|
||||
type MyError = Box<Error + Send + Sync>;
|
||||
|
||||
pub type Thing = Borrow<String>;
|
||||
|
||||
pub fn foo_thing(x: Thing) -> Result<(), MyError> {
|
||||
// Do things here.
|
||||
}
|
||||
```
|
||||
|
||||
Our JavaScript code follows the [airbnb style](https://github.com/airbnb/javascript)
|
||||
with a [few exceptions](../../blob/master/.eslintrc). The precise rules are
|
||||
likely to change a little as we get started so for now let eslint be your guide.
|
||||
|
||||
Our ClojureScript code follows… well, no guide so far.
|
||||
Our ClojureScript code (no longer live) doesn't follow a specific style guide.
|
||||
|
||||
# How to sign-off your commits
|
||||
|
||||
|
@ -160,13 +200,13 @@ then you just add a line saying
|
|||
|
||||
Signed-off-by: Random J Developer <random@developer.example.org>
|
||||
|
||||
using your real name (sorry, no pseudonyms or anonymous contributions.)
|
||||
using your real name (sorry, no pseudonyms or anonymous contributions).
|
||||
|
||||
If you're using the command line, you can get this done automatically with
|
||||
|
||||
$ git commit --signoff
|
||||
|
||||
Some GUIs (e.g. SourceTree) have an option to automatically sign commits.
|
||||
Some GUIs (_e.g._, SourceTree) have an option to automatically sign commits.
|
||||
|
||||
If you need to slightly modify patches you receive in order to merge them,
|
||||
because the code is not exactly the same in your tree and the submitters'.
|
||||
|
@ -174,11 +214,11 @@ If you stick strictly to rule (c), you should ask the submitter to submit, but
|
|||
this is a totally counter-productive waste of time and energy.
|
||||
Rule (b) allows you to adjust the code, but then it is very impolite to change
|
||||
one submitter's code and make them endorse your bugs. To solve this problem,
|
||||
it is recommended that you add a line between the last Signed-off-by header and
|
||||
it is recommended that you add a line between the last `Signed-off-by` header and
|
||||
yours, indicating the nature of your changes. While there is nothing mandatory
|
||||
about this, it seems like prepending the description with your mail and/or name,
|
||||
all enclosed in square brackets, is noticeable enough to make it obvious that
|
||||
you are responsible for last-minute changes. Example :
|
||||
you are responsible for last-minute changes. Example:
|
||||
|
||||
Signed-off-by: Random J Developer <random@developer.example.org>
|
||||
[lucky@maintainer.example.org: struct foo moved from foo.c to foo.h]
|
||||
|
@ -187,5 +227,5 @@ you are responsible for last-minute changes. Example :
|
|||
This practice is particularly helpful if you maintain a stable branch and
|
||||
want at the same time to credit the author, track changes, merge the fix,
|
||||
and protect the submitter from complaints. Note that under no circumstances
|
||||
can you change the author's identity (the From header), as it is the one
|
||||
can you change the author's identity (the `From` header), as it is the one
|
||||
which appears in the change-log.
|
32
docs/Gemfile
Normal file
32
docs/Gemfile
Normal file
|
@ -0,0 +1,32 @@
|
|||
source "https://rubygems.org"
|
||||
|
||||
# Hello! This is where you manage which Jekyll version is used to run.
|
||||
# When you want to use a different version, change it below, save the
|
||||
# file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
|
||||
#
|
||||
# bundle exec jekyll serve
|
||||
#
|
||||
# This will help ensure the proper Jekyll version is running.
|
||||
# Happy Jekylling!
|
||||
# gem "jekyll", "~> 3.7.3"
|
||||
|
||||
# This is the default theme for new Jekyll sites. You may change this to anything you like.
|
||||
gem "minima", "~> 2.5.1"
|
||||
|
||||
# If you want to use GitHub Pages, remove the "gem "jekyll"" above and
|
||||
# uncomment the line below. To upgrade, run `bundle update github-pages`.
|
||||
# gem "github-pages", group: :jekyll_plugins
|
||||
|
||||
# If you have any plugins, put them here!
|
||||
group :jekyll_plugins do
|
||||
gem "jekyll-feed", "~> 0.15.1"
|
||||
gem "github-pages", "~> 215"
|
||||
gem "jekyll-commonmark-ghpages", "~> 0.1.6"
|
||||
end
|
||||
|
||||
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
||||
gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
|
||||
|
||||
# Performance-booster for watching directories on Windows
|
||||
gem "wdm", "~> 0.1.0" if Gem.win_platform?
|
||||
|
277
docs/Gemfile.lock
Normal file
277
docs/Gemfile.lock
Normal file
|
@ -0,0 +1,277 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (6.0.4)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
zeitwerk (~> 2.2, >= 2.2.2)
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
coffee-script (2.4.1)
|
||||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.11.1)
|
||||
colorator (1.1.0)
|
||||
commonmarker (0.17.13)
|
||||
ruby-enum (~> 0.5)
|
||||
concurrent-ruby (1.1.9)
|
||||
dnsruby (1.61.7)
|
||||
simpleidn (~> 0.1)
|
||||
em-websocket (0.5.2)
|
||||
eventmachine (>= 0.12.9)
|
||||
http_parser.rb (~> 0.6.0)
|
||||
ethon (0.14.0)
|
||||
ffi (>= 1.15.0)
|
||||
eventmachine (1.2.7)
|
||||
execjs (2.8.1)
|
||||
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)
|
||||
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)
|
||||
gemoji (3.0.1)
|
||||
github-pages (215)
|
||||
github-pages-health-check (= 1.17.2)
|
||||
jekyll (= 3.9.0)
|
||||
jekyll-avatar (= 0.7.0)
|
||||
jekyll-coffeescript (= 1.1.1)
|
||||
jekyll-commonmark-ghpages (= 0.1.6)
|
||||
jekyll-default-layout (= 0.1.4)
|
||||
jekyll-feed (= 0.15.1)
|
||||
jekyll-gist (= 1.5.0)
|
||||
jekyll-github-metadata (= 2.13.0)
|
||||
jekyll-mentions (= 1.6.0)
|
||||
jekyll-optional-front-matter (= 0.3.2)
|
||||
jekyll-paginate (= 1.1.0)
|
||||
jekyll-readme-index (= 0.3.0)
|
||||
jekyll-redirect-from (= 0.16.0)
|
||||
jekyll-relative-links (= 0.6.1)
|
||||
jekyll-remote-theme (= 0.4.3)
|
||||
jekyll-sass-converter (= 1.5.2)
|
||||
jekyll-seo-tag (= 2.7.1)
|
||||
jekyll-sitemap (= 1.4.0)
|
||||
jekyll-swiss (= 1.0.0)
|
||||
jekyll-theme-architect (= 0.1.1)
|
||||
jekyll-theme-cayman (= 0.1.1)
|
||||
jekyll-theme-dinky (= 0.1.1)
|
||||
jekyll-theme-hacker (= 0.1.2)
|
||||
jekyll-theme-leap-day (= 0.1.1)
|
||||
jekyll-theme-merlot (= 0.1.1)
|
||||
jekyll-theme-midnight (= 0.1.1)
|
||||
jekyll-theme-minimal (= 0.1.1)
|
||||
jekyll-theme-modernist (= 0.1.1)
|
||||
jekyll-theme-primer (= 0.5.4)
|
||||
jekyll-theme-slate (= 0.1.1)
|
||||
jekyll-theme-tactile (= 0.1.1)
|
||||
jekyll-theme-time-machine (= 0.1.1)
|
||||
jekyll-titles-from-headings (= 0.5.3)
|
||||
jemoji (= 0.12.0)
|
||||
kramdown (= 2.3.1)
|
||||
kramdown-parser-gfm (= 1.1.0)
|
||||
liquid (= 4.0.3)
|
||||
mercenary (~> 0.3)
|
||||
minima (= 2.5.1)
|
||||
nokogiri (>= 1.10.4, < 2.0)
|
||||
rouge (= 3.26.0)
|
||||
terminal-table (~> 1.4)
|
||||
github-pages-health-check (1.17.2)
|
||||
addressable (~> 2.3)
|
||||
dnsruby (~> 1.60)
|
||||
octokit (~> 4.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
typhoeus (~> 1.3)
|
||||
html-pipeline (2.14.0)
|
||||
activesupport (>= 2)
|
||||
nokogiri (>= 1.4)
|
||||
http_parser.rb (0.6.0)
|
||||
i18n (0.9.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jekyll (3.9.0)
|
||||
addressable (~> 2.4)
|
||||
colorator (~> 1.0)
|
||||
em-websocket (~> 0.5)
|
||||
i18n (~> 0.7)
|
||||
jekyll-sass-converter (~> 1.0)
|
||||
jekyll-watch (~> 2.0)
|
||||
kramdown (>= 1.17, < 3)
|
||||
liquid (~> 4.0)
|
||||
mercenary (~> 0.3.3)
|
||||
pathutil (~> 0.9)
|
||||
rouge (>= 1.7, < 4)
|
||||
safe_yaml (~> 1.0)
|
||||
jekyll-avatar (0.7.0)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
jekyll-coffeescript (1.1.1)
|
||||
coffee-script (~> 2.2)
|
||||
coffee-script-source (~> 1.11.1)
|
||||
jekyll-commonmark (1.3.1)
|
||||
commonmarker (~> 0.14)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-commonmark-ghpages (0.1.6)
|
||||
commonmarker (~> 0.17.6)
|
||||
jekyll-commonmark (~> 1.2)
|
||||
rouge (>= 2.0, < 4.0)
|
||||
jekyll-default-layout (0.1.4)
|
||||
jekyll (~> 3.0)
|
||||
jekyll-feed (0.15.1)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-gist (1.5.0)
|
||||
octokit (~> 4.2)
|
||||
jekyll-github-metadata (2.13.0)
|
||||
jekyll (>= 3.4, < 5.0)
|
||||
octokit (~> 4.0, != 4.4.0)
|
||||
jekyll-mentions (1.6.0)
|
||||
html-pipeline (~> 2.3)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-optional-front-matter (0.3.2)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
jekyll-paginate (1.1.0)
|
||||
jekyll-readme-index (0.3.0)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
jekyll-redirect-from (0.16.0)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-relative-links (0.6.1)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-remote-theme (0.4.3)
|
||||
addressable (~> 2.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)
|
||||
sass (~> 3.4)
|
||||
jekyll-seo-tag (2.7.1)
|
||||
jekyll (>= 3.8, < 5.0)
|
||||
jekyll-sitemap (1.4.0)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-swiss (1.0.0)
|
||||
jekyll-theme-architect (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-cayman (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-dinky (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-hacker (0.1.2)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-leap-day (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-merlot (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-midnight (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-minimal (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-modernist (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-primer (0.5.4)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-github-metadata (~> 2.9)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-slate (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-tactile (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-time-machine (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-titles-from-headings (0.5.3)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-watch (2.2.1)
|
||||
listen (~> 3.0)
|
||||
jemoji (0.12.0)
|
||||
gemoji (~> 3.0)
|
||||
html-pipeline (~> 2.2)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
kramdown (2.3.1)
|
||||
rexml
|
||||
kramdown-parser-gfm (1.1.0)
|
||||
kramdown (~> 2.0)
|
||||
liquid (4.0.3)
|
||||
listen (3.5.1)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
mercenary (0.3.6)
|
||||
mini_portile2 (2.6.1)
|
||||
minima (2.5.1)
|
||||
jekyll (>= 3.5, < 5.0)
|
||||
jekyll-feed (~> 0.9)
|
||||
jekyll-seo-tag (~> 2.1)
|
||||
minitest (5.14.4)
|
||||
multipart-post (2.1.1)
|
||||
nokogiri (1.12.5)
|
||||
mini_portile2 (~> 2.6.1)
|
||||
racc (~> 1.4)
|
||||
octokit (4.21.0)
|
||||
faraday (>= 0.9)
|
||||
sawyer (~> 0.8.0, >= 0.5.3)
|
||||
pathutil (0.16.2)
|
||||
forwardable-extended (~> 2.6)
|
||||
public_suffix (4.0.6)
|
||||
racc (1.5.2)
|
||||
rb-fsevent (0.11.0)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
rexml (3.2.5)
|
||||
rouge (3.26.0)
|
||||
ruby-enum (0.9.0)
|
||||
i18n
|
||||
ruby2_keywords (0.0.4)
|
||||
rubyzip (2.3.0)
|
||||
safe_yaml (1.0.5)
|
||||
sass (3.7.4)
|
||||
sass-listen (~> 4.0.0)
|
||||
sass-listen (4.0.0)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
sawyer (0.8.2)
|
||||
addressable (>= 2.3.5)
|
||||
faraday (> 0.8, < 2.0)
|
||||
simpleidn (0.2.1)
|
||||
unf (~> 0.1.4)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
thread_safe (0.3.6)
|
||||
typhoeus (1.4.0)
|
||||
ethon (>= 0.9.0)
|
||||
tzinfo (1.2.9)
|
||||
thread_safe (~> 0.1)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.7)
|
||||
unicode-display_width (1.7.0)
|
||||
zeitwerk (2.4.2)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
github-pages (~> 215)
|
||||
jekyll-commonmark-ghpages (~> 0.1.6)
|
||||
jekyll-feed (~> 0.15.1)
|
||||
minima (~> 2.5.1)
|
||||
tzinfo-data
|
||||
|
||||
BUNDLED WITH
|
||||
2.2.21
|
18
docs/README.md
Normal file
18
docs/README.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Project Mentat Documentation Site
|
||||
|
||||
This site is a place to provide the users of Mentat with all the documentation, examples and tutorials required in order to use Mentat inside a project.
|
||||
|
||||
This site will contain the following:
|
||||
|
||||
- API Documentation for Mentat and it's SDKs.
|
||||
- Tutorials for cross compilation of Mentat for other platforms. (Coming)
|
||||
- Examples of how to design data for storage in Mentat.
|
||||
- Examples of how to use Mentat and it's SDKs. (Coming)
|
||||
- Quick Start Guides for installing and using Mentat. (Coming)
|
||||
|
||||
# Build and run locally
|
||||
|
||||
1. Install [Jekyll](https://jekyllrb.com/docs/installation/)
|
||||
2. `cd docs`
|
||||
3. `bundle exec jekyll serve --incremental`
|
||||
4. open local docs site at http://127.0.0.1:4000/
|
43
docs/_config.yml
Normal file
43
docs/_config.yml
Normal file
|
@ -0,0 +1,43 @@
|
|||
# Welcome to Jekyll!
|
||||
#
|
||||
# This config file is meant for settings that affect your whole blog, values
|
||||
# which you are expected to set up once and rarely edit after that. If you find
|
||||
# yourself editing this file very often, consider using Jekyll's data files
|
||||
# feature for the data you need to update frequently.
|
||||
#
|
||||
# For technical reasons, this file is *NOT* reloaded automatically when you use
|
||||
# 'bundle exec jekyll serve'. If you change this file, please restart the server process.
|
||||
|
||||
# Site settings
|
||||
# These are used to personalize your new site. If you look in the HTML files,
|
||||
# you will see them accessed via {{ site.title }}, {{ site.email }}, and so on.
|
||||
# You can create any custom variable you would like, and they will be accessible
|
||||
# in the templates via {{ site.myvariable }}.
|
||||
title: Mentat
|
||||
description: >- # this means to ignore newlines until "baseurl:"
|
||||
Project Mentat is a persistent, embedded knowledge base. It draws heavily on DataScript and Datomic.
|
||||
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.
|
||||
baseurl: /mentat # the subpath of your site, e.g. /blog
|
||||
url: https://mozilla.github.io # the base hostname & protocol for your site, e.g. http://example.com
|
||||
|
||||
api_heading: API Documentation
|
||||
list_title: Examples
|
||||
|
||||
# Build settings
|
||||
markdown: kramdown
|
||||
theme: minima
|
||||
plugins:
|
||||
- jekyll-feed
|
||||
|
||||
# Exclude from processing.
|
||||
# The following items will not be processed, by default. Create a custom list
|
||||
# to override the default setting.
|
||||
# exclude:
|
||||
# - Gemfile
|
||||
# - Gemfile.lock
|
||||
# - node_modules
|
||||
# - vendor/bundle/
|
||||
# - vendor/cache/
|
||||
# - vendor/gems/
|
||||
# - vendor/ruby/
|
24
docs/_includes/footer.html
Normal file
24
docs/_includes/footer.html
Normal file
|
@ -0,0 +1,24 @@
|
|||
<footer class="site-footer h-card">
|
||||
<data class="u-url" href="{{ "/" | relative_url }}"></data>
|
||||
|
||||
<div class="wrapper">
|
||||
|
||||
<!-- <h2 class="footer-heading"></h2> -->
|
||||
|
||||
<div class="footer-col-wrapper">
|
||||
<div class="footer-col footer-col-1">
|
||||
<ul class="contact-list">
|
||||
<li class="p-name">{{ site.title }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-col footer-col-2">
|
||||
</div>
|
||||
|
||||
<div class="footer-col footer-col-3">
|
||||
<p>Project Mentat is currently licensed under the Apache License v2.0.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</footer>
|
30
docs/_includes/header.html
Normal file
30
docs/_includes/header.html
Normal file
|
@ -0,0 +1,30 @@
|
|||
<header class="site-header" role="banner">
|
||||
|
||||
<div class="wrapper">
|
||||
{%- assign default_paths = site.pages | map: "path" -%}
|
||||
{%- assign page_paths = site.header_pages | default: default_paths -%}
|
||||
<a class="site-title" rel="author" href="{{ "/" | relative_url }}">{{ site.title | escape }}</a>
|
||||
|
||||
{%- if page_paths -%}
|
||||
<nav class="site-nav">
|
||||
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
|
||||
<label for="nav-trigger">
|
||||
<span class="menu-icon">
|
||||
<svg viewBox="0 0 18 15" width="18px" height="15px">
|
||||
<path d="M18,1.484c0,0.82-0.665,1.484-1.484,1.484H1.484C0.665,2.969,0,2.304,0,1.484l0,0C0,0.665,0.665,0,1.484,0 h15.032C17.335,0,18,0.665,18,1.484L18,1.484z M18,7.516C18,8.335,17.335,9,16.516,9H1.484C0.665,9,0,8.335,0,7.516l0,0 c0-0.82,0.665-1.484,1.484-1.484h15.032C17.335,6.031,18,6.696,18,7.516L18,7.516z M18,13.516C18,14.335,17.335,15,16.516,15H1.484 C0.665,15,0,14.335,0,13.516l0,0c0-0.82,0.665-1.483,1.484-1.483h15.032C17.335,12.031,18,12.695,18,13.516L18,13.516z"/>
|
||||
</svg>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<div class="trigger">
|
||||
{%- for path in page_paths -%}
|
||||
{%- assign my_page = site.pages | where: "path", path | first -%}
|
||||
{%- if my_page.title -%}
|
||||
<a class="page-link" href="{{ my_page.url | relative_url }}">{{ my_page.title | escape }}</a>
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
</div>
|
||||
</nav>
|
||||
{%- endif -%}
|
||||
</div>
|
||||
</header>
|
21
docs/_layouts/home.html
Normal file
21
docs/_layouts/home.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
<div class="home">
|
||||
|
||||
{{ content }}
|
||||
|
||||
{%- if site.posts.size > 0 -%}
|
||||
{% assign posts_by_cat = site.posts | group_by:"category" %}
|
||||
{% for category in posts_by_cat %}
|
||||
<h2>{{category.name | capitalize}}</h2>
|
||||
<ul class="post-list">
|
||||
{% for post in category.items %}
|
||||
<li><a href="{{post.url | relative_url}}">{{post.title | escape}}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
{%- endif -%}
|
||||
|
||||
</div>
|
14
docs/_layouts/page.html
Normal file
14
docs/_layouts/page.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
layout: default
|
||||
---
|
||||
<article class="post">
|
||||
|
||||
<header class="post-header">
|
||||
<h1 class="post-title">{{ page.title | escape }}</h1>
|
||||
</header>
|
||||
|
||||
<div class="post-content">
|
||||
{{ content }}
|
||||
</div>
|
||||
|
||||
</article>
|
27
docs/_layouts/post.html
Normal file
27
docs/_layouts/post.html
Normal file
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
layout: default
|
||||
---
|
||||
<article class="post h-entry" itemscope itemtype="http://schema.org/BlogPosting">
|
||||
|
||||
<header class="post-header">
|
||||
<h1 class="post-title p-name" itemprop="name headline">{{ page.title | escape }}</h1>
|
||||
<p class="post-meta">
|
||||
<time class="dt-published" datetime="{{ page.date | date_to_xmlschema }}" itemprop="datePublished">
|
||||
{%- assign date_format = site.minima.date_format | default: "%b %-d, %Y" -%}
|
||||
{{ page.date | date: date_format }}
|
||||
</time>
|
||||
{%- if page.author -%}
|
||||
• <span itemprop="author" itemscope itemtype="http://schema.org/Person"><span class="p-author h-card" itemprop="name">{{ page.author }}</span></span>
|
||||
{%- endif -%}</p>
|
||||
</header>
|
||||
|
||||
<div class="post-content e-content" itemprop="articleBody">
|
||||
{{ content }}
|
||||
</div>
|
||||
|
||||
{%- if site.disqus.shortname -%}
|
||||
{%- include disqus_comments.html -%}
|
||||
{%- endif -%}
|
||||
|
||||
<a class="u-url" href="{{ page.url | relative_url }}" hidden></a>
|
||||
</article>
|
396
docs/_posts/2018-04-17-worked-examples.md
Normal file
396
docs/_posts/2018-04-17-worked-examples.md
Normal file
|
@ -0,0 +1,396 @@
|
|||
---
|
||||
layout: post
|
||||
list_title: Examples
|
||||
title: "Modeling data using Mentat"
|
||||
date: 2018-04-17 16:07:37 +0100
|
||||
category: examples
|
||||
---
|
||||
# Worked examples of modeling data using Mentat
|
||||
|
||||
Used correctly, Mentat makes it easy for you to grow to accommodate new kinds of data, for data to synchronize between devices, for multiple consumers to share data, and even for errors to be fixed.
|
||||
|
||||
But what does "correctly" mean?
|
||||
|
||||
The following discussion and set of worked examples aim to help. During discussion sections a simplified syntax is used for schema examples.
|
||||
|
||||
## Principles
|
||||
|
||||
### Think about the domain, not about your UI
|
||||
|
||||
Given a set of mockups, or an MVP list of requirements, it's easy to leap into defining a data model that supports exactly those things. In doing so we will likely end up with a data model that can't support future capabilities, or that has crucial mismatches with the real world.
|
||||
|
||||
For example, one might design a contact manager UI like macOS's — a list of string fields for a person:
|
||||
|
||||
* First name
|
||||
* Last name
|
||||
* Address line 1
|
||||
* Address line 2
|
||||
* Phone
|
||||
* _etc._
|
||||
|
||||
We might model this in Mentat as simple value properties:
|
||||
|
||||
```edn
|
||||
[:person/name :db.type/string :db.cardinality/one] ; Incorrect: people can have many names!
|
||||
[:person/home_address_line_one :db.type/string :db.cardinality/one]
|
||||
[:person/home_address_line_two :db.type/string :db.cardinality/one]
|
||||
[:person/home_city :db.type/string :db.cardinality/one]
|
||||
[:person/home_phone :db.type/string :db.cardinality/one]
|
||||
```
|
||||
|
||||
or in JSON as a simple object:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Alice Smith",
|
||||
"home_address_line_one": "123 Main St",
|
||||
"home_city": "Anywhere",
|
||||
"home_phone": "555-867-5309"
|
||||
}
|
||||
```
|
||||
|
||||
We might realize that this proliferation of attributes is going in the wrong direction, and add nested structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Alice Smith",
|
||||
"home_address": {
|
||||
"line_one": "123 Main St",
|
||||
"city": "Anywhere"
|
||||
}}
|
||||
```
|
||||
|
||||
(quick, is a home phone number a property of the address or the person?)
|
||||
|
||||
Or we might allow for some people having multiple addresses and multiple homes:
|
||||
|
||||
```json
|
||||
{"name": "Alice Smith",
|
||||
"addresses": [{
|
||||
"type": "home",
|
||||
"line_one": "123 Main St",
|
||||
"city": "Anywhere"
|
||||
}]}
|
||||
```
|
||||
|
||||
There are [lots of reasons the address model is wrong](https://www.mjt.me.uk/posts/falsehoods-programmers-believe-about-addresses/), and [the same is true of names](https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/). But even the _structure_ of this is wrong, when you think about it.
|
||||
|
||||
A _physical place_, for our purposes, has an address. (It might have more than one.)
|
||||
|
||||
Each place might play a number of _roles_ to a number of people: the same house is the home of everyone who lives there, and the same business address is one of the work addresses for each employee. If I work from home, my work and business addresses are the same. It's not quite true to say that an address is a "home": an address _identifies_ a _place_, and that place _is a home to a person_.
|
||||
|
||||
But a typical contact application gets this wrong: the same _strings_ are duplicated (flattened and denormalized) into the independent contact records of each person. If a business moves location, or its building is renamed, we must change the addresses of multiple contacts.
|
||||
|
||||
A more correct model for this is _relational_:
|
||||
|
||||
```edn
|
||||
[:person/name :db.type/string :db.cardinality/one]
|
||||
[:person/lives_at :db.type/ref :db.cardinality/many] ; Points to a place.
|
||||
[:person/works_at :db.type/ref :db.cardinality/many] ; Points to a place.
|
||||
[:place/address :db.type/ref :db.cardinality/many] ; A place can have multiple addresses.
|
||||
[:address/mailing_address :db.type/string :db.cardinality/one ; Each address can be represented once as a string.
|
||||
:db.unique/identity]
|
||||
[:address/city :db.type/string :db.cardinality/one] ; Perhaps this is useful?
|
||||
```
|
||||
|
||||
Imagine that Alice works from home, and Bob works at his office on South Street. Alice's data looks like this:
|
||||
|
||||
```edn
|
||||
[{:person/name "Alice Smith"
|
||||
:person/lives_at "alice_home"
|
||||
:person/works_at "alice_home"}
|
||||
{:db/id "alice_home"
|
||||
:place/address "main_street_123"}
|
||||
{:db/id "main_street_123"
|
||||
:address/mailing_address "123 Main St, Anywhere, WA 12345, USA"
|
||||
:address/city "Anywhere"}]
|
||||
```
|
||||
|
||||
and Bob's like this:
|
||||
|
||||
```edn
|
||||
[{:person/name "Bob Salmon"
|
||||
:person/works_at "bob_office"}
|
||||
{:db/id "bob_office"
|
||||
:place/owner "Example Holdings LLC"
|
||||
:place/address "south_street_555"}
|
||||
{:db/id "south_street_555"
|
||||
:address/mailing_address "555 South St, Anywhere, WA 12345, USA"
|
||||
:address/city "Anywhere"}]
|
||||
```
|
||||
|
||||
Now if Alice (ID 1234) moves her business out of her house (1235) into an office in Bob's building (1236), we simply break one relationship and add a new one to a new place with the same address:
|
||||
|
||||
```edn
|
||||
[[:db/retract 1234 :person/works_at 1235] ; Alice no longer works at home.
|
||||
[:db/add 1234 :person/works_at "new_office"]
|
||||
{:db/id "new_office"
|
||||
:place/address [:address/mailing_address "555 South St, Anywhere, WA 12345, USA"]}]
|
||||
```
|
||||
|
||||
If the building is now renamed to "The Office Factory", we can update its address in one step, affecting both Alice's and Bob's offices:
|
||||
|
||||
```edn
|
||||
[[:db/retract 1236 :address/mailing_address "555 South St, Anywhere, WA 12345, USA"]
|
||||
[:db/add 1236 :address/mailing_address "The Office Factory, South St, Anywhere, WA 12345, USA"]]
|
||||
```
|
||||
|
||||
You can see here how changes are minimal and correspond to real changes in the domain — two properties that help with syncing. There is no duplication of strings.
|
||||
|
||||
We can find everyone who works at The Office Factory in a simple query without comparing strings across 'records':
|
||||
|
||||
```edn
|
||||
[:find ?name
|
||||
:where [?address :address/mailing_address "The Office Factory, South St, Anywhere, WA 12345, USA"]
|
||||
[?office :place/address ?address]
|
||||
[?person :person/works_at ?office]
|
||||
[?person :person/name ?name]]
|
||||
```
|
||||
|
||||
Let's say we later want to model move-in and move-out dates — useful for employment records and immigration paperwork!
|
||||
|
||||
Trying to add this to the JSON model is an exercise in frustration, because there is no stable way to identify people or places! (Go ahead, try it.)
|
||||
|
||||
To do it in Mentat simply requires defining a small bit of vocabulary:
|
||||
|
||||
```edn
|
||||
[:place.change/person :db.type/ref :db.cardinality/many]
|
||||
[:place.change/from :db.type/ref :db.cardinality/one] ; optional
|
||||
[:place.change/to :db.type/ref :db.cardinality/one] ; optional
|
||||
[:place.change/role :db.type/ref :db.cardinality/one] ; :person/lives_at or :person/works_at
|
||||
[:place.change/on :db.type/instant :db.cardinality/one]
|
||||
[:place.change/reason :db.type/string :db.cardinality/one] ; optional
|
||||
```
|
||||
|
||||
so we can describe Alice's office move:
|
||||
|
||||
```edn
|
||||
[{:place.change/person 1234
|
||||
:place.change/from 1235
|
||||
:place.change/to 1237
|
||||
:place.change/role :person/works_at
|
||||
:place.change/on #inst "2018-02-02T13:00:00Z"}]
|
||||
```
|
||||
|
||||
or Jane's sale of her holiday home:
|
||||
|
||||
```edn
|
||||
[{:place.change/person 2468
|
||||
:place.change/reason "Sale"
|
||||
:place.change/from 1235
|
||||
:place.change/role :person/lives_at
|
||||
:place.change/on #inst "2018-08-12T14:00:00Z"}]
|
||||
```
|
||||
|
||||
Note that we don't need to repeat the addresses, we don't need to change the existing data, and we don't need to complicate matters for existing code.
|
||||
|
||||
Now we can find everyone who moved office in February:
|
||||
|
||||
```edn
|
||||
[:find ?name
|
||||
:where [?move :place.change/role :person/works_at]
|
||||
[?move :place.change/on ?on]
|
||||
[(>= ?on #inst "2018-02-01T00:00:00Z")]
|
||||
[(< ?on #inst "2018-03-01T00:00:00Z")]
|
||||
[?move :place.change/person ?person]
|
||||
[?person :person/name ?name]]
|
||||
```
|
||||
|
||||
## Tend towards recording observations, not changing state
|
||||
|
||||
These principles are all different aspects of normalization.
|
||||
|
||||
The introduction of fine-grained entities to represent data pushes us towards immutability: changes are increasingly changing an 'arrow' to point at one immutable entity or another, rather than re-describing a mutable entity.
|
||||
|
||||
In the previous example we introduced _places_ and _addresses_. Places and addresses themselves rarely change, allowing us to mostly isolate the churn in our data to the meaningful relationships between entities.
|
||||
|
||||
Another example of this approach is shown in modeling browser history.
|
||||
|
||||
Firefox's representation of history is, at its core, relatively simplistic; just two tables a little like this:
|
||||
|
||||
```sql
|
||||
CREATE TABLE history (
|
||||
id INTEGER PRIMARY KEY,
|
||||
guid TEXT NOT NULL UNIQUE,
|
||||
url TEXT NOT NULL UNIQUE,
|
||||
title TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE visits (
|
||||
id INTEGER PRIMARY KEY,
|
||||
history_id INTEGER NOT NULL REFERENCES history(id),
|
||||
type TINYINT,
|
||||
timestamp INTEGER
|
||||
);
|
||||
```
|
||||
|
||||
Each time a URL is visited, an entry is added to the `visits` table and a row is added or updated in `history`. The title of the fetched page is used to update `history.title`, so that `history.title` always represents the most recently encountered title.
|
||||
|
||||
This works fine until more features are added.
|
||||
|
||||
### Forgetting
|
||||
|
||||
Browsers often have some capacity for deleting history. Sometimes this appears in the form of an explicit 'forget' operation — "Forget the last five minutes of browsing". Deleting visits in this way is fine: `DELETE FROM visits WHERE timestamp < ?`. But the mutability in the data model — title — trips us up. We're unable to roll back the title of the history entry.
|
||||
|
||||
### Syncing
|
||||
|
||||
But even if you are using Mentat or Datomic, and can turn to the log to reconstruct the old state, a mutable title on `history` will cause conflicts when syncing: one side's observed titles will 'lose' and be discarded in order to avoid a conflict. That's not right: those titles _were seen_. Unlike a conflicting counter or flag, these weren't abortive, temporary states; they were _observations of the world_, so there shouldn't be a winner and a loser.
|
||||
|
||||
### Containers
|
||||
|
||||
The true data model becomes apparent when we consider containers. Containers are a Firefox feature to sandbox the cookies, site data, and history of different named sub-profiles. You can have a container just for Facebook, or one for your banking; those Facebook cookies won't follow you around the web in your 'personal' container. You can simultaneously use separate Gmail accounts for work and personal email.
|
||||
|
||||
When Firefox added container support, it did so by annotating visits with a `container`:
|
||||
|
||||
```sql
|
||||
CREATE TABLE visits (
|
||||
id INTEGER PRIMARY KEY,
|
||||
history_id INTEGER NOT NULL REFERENCES history(id),
|
||||
type TINYINT,
|
||||
timestamp INTEGER,
|
||||
container INTEGER
|
||||
);
|
||||
```
|
||||
|
||||
This means that each container _competes for the title on `history`_. If you visit `facebook.com` in your usual logged-in container, the browser will run something like this SQL:
|
||||
|
||||
```
|
||||
UPDATE TABLE history
|
||||
SET title = '(2) Facebook'
|
||||
WHERE url = 'https://www.facebook.com';
|
||||
```
|
||||
|
||||
If you visit it in the wrong container by mistake, you'll get the Facebook login page, and Firefox will run:
|
||||
|
||||
```
|
||||
UPDATE TABLE history
|
||||
SET title = 'Facebook - Log In or Sign Up'
|
||||
WHERE url = 'https://www.facebook.com';
|
||||
```
|
||||
|
||||
Next time you open your history, _you'll see the login page title, even if you had a logged-in `facebook.com` session open in another tab_. There's no way to differentiate between the containers' views.
|
||||
|
||||
The correct data model for history is:
|
||||
|
||||
- Users visit a URL on a device in a container.
|
||||
- Pages are fetched as a result of a visit (or dynamically after load). Pages can embed media and other resources.
|
||||
- Pages, being HTML, have titles.
|
||||
- Pages, titles, and visits are all _observations_, and as such cannot conflict.
|
||||
- The _last observed_ title to show for a URL is an _aggregation_ of those events.
|
||||
|
||||
The entire notion of a history table — a concept centered on the URL — having a title is a subtly incorrect choice that causes problems with more modern browser features.
|
||||
|
||||
Modeled in Mentat:
|
||||
|
||||
```edn
|
||||
[{:db/ident :visit/visitedOnDevice
|
||||
:db/valueType :db.type/ref
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :visit/visitAt
|
||||
:db/valueType :db.type/instant
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :site/visit
|
||||
:db/valueType :db.type/ref
|
||||
:db/isComponent true
|
||||
:db/cardinality :db.cardinality/many}
|
||||
{:db/ident :site/url
|
||||
:db/valueType :db.type/string
|
||||
:db/unique :db.unique/identity
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/index true}
|
||||
{:db/ident :visit/page
|
||||
:db/valueType :db.type/ref
|
||||
:db/isComponent true ; Debatable.
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :page/title
|
||||
:db/valueType :db.type/string
|
||||
:db/fulltext true
|
||||
:db/index true
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :visit/container
|
||||
:db/valueType :db.type/ref
|
||||
:db/cardinality :db.cardinality/one}]
|
||||
```
|
||||
|
||||
Create some containers:
|
||||
|
||||
```edn
|
||||
[{:db/ident :container/facebook}
|
||||
{:db/ident :container/personal}]
|
||||
```
|
||||
|
||||
Add a device:
|
||||
|
||||
```edn
|
||||
[{:db/ident :device/my-desktop}]
|
||||
```
|
||||
|
||||
|
||||
Visit Facebook in each container:
|
||||
|
||||
```edn
|
||||
[{:visit/visitedOnDevice :device/my-desktop
|
||||
:visit/visitAt #inst "2018-04-06T18:46:00Z"
|
||||
:visit/container :container/facebook
|
||||
:db/id "fbvisit"
|
||||
:visit/page "fbpage"}
|
||||
{:db/id "fbpage"
|
||||
:page/title "(2) Facebook"}
|
||||
{:site/url "https://www.facebook.com"
|
||||
:site/visit "fbvisit"}]
|
||||
```
|
||||
|
||||
```edn
|
||||
[{:visit/visitedOnDevice :device/my-desktop
|
||||
:visit/visitAt #inst "2018-04-06T18:46:02Z"
|
||||
:visit/container :container/personal
|
||||
:db/id "personalvisit"
|
||||
:visit/page "personalpage"}
|
||||
{:db/id "personalpage"
|
||||
:page/title "Facebook - Log In or Sign Up"}
|
||||
{:site/url "https://www.facebook.com"
|
||||
:site/visit "personalvisit"}]
|
||||
```
|
||||
|
||||
Now we can show the title from the latest visit in a given container:
|
||||
|
||||
```edn
|
||||
.q [:find (max ?visitDate) (the ?title)
|
||||
:where [?site :site/url "https://www.facebook.com"]
|
||||
[?site :site/visit ?visit]
|
||||
[?visit :visit/container :container/facebook]
|
||||
[?visit :visit/visitAt ?visitDate]
|
||||
[?visit :visit/page ?page]
|
||||
[?page :page/title ?title]]
|
||||
=>
|
||||
| (the ?title) | (max ?visitDate) |
|
||||
--- ---
|
||||
| "(2) Facebook" | 2018-04-06 18:46:00 UTC |
|
||||
--- ---
|
||||
|
||||
.q [:find (the ?title) (max ?visitDate)
|
||||
:where [?site :site/url "https://www.facebook.com"]
|
||||
[?site :site/visit ?visit]
|
||||
[?visit :visit/container :container/personal]
|
||||
[?visit :visit/visitAt ?visitDate]
|
||||
[?visit :visit/page ?page]
|
||||
[?page :page/title ?title]]
|
||||
=>
|
||||
| (the ?title) | (max ?visitDate) |
|
||||
--- ---
|
||||
| "Facebook - Log In or Sign Up" | 2018-04-06 18:46:02 UTC |
|
||||
--- ---
|
||||
```
|
||||
|
||||
|
||||
## Normalize; you can always denormalize for use.
|
||||
|
||||
To come.
|
||||
|
||||
## Use unique identities and cardinality-one attributes to make merging happen during a sync.
|
||||
|
||||
To come.
|
||||
|
||||
## Reify to handle conflict and atomicity.
|
||||
|
||||
To come.
|
122
docs/about.md
Normal file
122
docs/about.md
Normal file
|
@ -0,0 +1,122 @@
|
|||
---
|
||||
layout: page
|
||||
title: About
|
||||
permalink: /about/
|
||||
---
|
||||
|
||||
# Project Mentat
|
||||
|
||||
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.
|
||||
|
||||
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).
|
||||
|
||||
The Rust implementation gives us a smaller compiled output, better performance, more type safety, better tooling, and easier deployment into Firefox and mobile platforms.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
|
||||
## Data storage is hard
|
||||
|
||||
We've observed that data storage is a particular area of difficulty for software development teams:
|
||||
|
||||
- It's hard to define storage schemas well. A developer must:
|
||||
- Model their domain entities and relationships.
|
||||
- Encode that model _efficiently_ and _correctly_ using the features available in the database.
|
||||
- Plan for future extensions and performance tuning.
|
||||
|
||||
In a SQL database, the same schema definition defines everything from high-level domain relationships through to numeric field sizes in the same smear of keywords. It's difficult for someone unfamiliar with the domain to determine from such a schema what's a domain fact and what's an implementation concession — are all part numbers always 16 characters long, or are we trying to save space? — or, indeed, whether a missing constraint is deliberate or a bug.
|
||||
|
||||
The developer must think about foreign key constraints, compound uniqueness, and nullability. They must consider indexing, synchronizing, and stable identifiers. Most developers simply don't do enough work in SQL to get all of these things right. Storage thus becomes the specialty of a few individuals.
|
||||
|
||||
Which one of these is correct?
|
||||
|
||||
```edn
|
||||
{:db/id :person/email
|
||||
:db/valueType :db.type/string
|
||||
:db/cardinality :db.cardinality/many ; People can have multiple email addresses.
|
||||
:db/unique :db.unique/identity ; For our purposes, each email identifies one person.
|
||||
:db/index true} ; We want fast lookups by email.
|
||||
{:db/id :person/friend
|
||||
:db/valueType :db.type/ref
|
||||
:db/cardinality :db.cardinality/many} ; People can have many friends.
|
||||
```
|
||||
```sql
|
||||
CREATE TABLE people (
|
||||
id INTEGER PRIMARY KEY, -- Bug: because of the primary key, each person can have no more than 1 email.
|
||||
email VARCHAR(64), -- Bug?: no NOT NULL, so a person can have no email.
|
||||
-- Bug: nobody will ever have a long email address, right?
|
||||
);
|
||||
CREATE TABLE friendships (
|
||||
FOREIGN KEY person REFERENCES people(id), -- Bug?: no indexing, so lookups by friend or person will be slow.
|
||||
FOREIGN KEY friend REFERENCES people(id), -- Bug: no compound uniqueness constraint, so we can have dupe friendships.
|
||||
);
|
||||
```
|
||||
|
||||
They both have limitations — the Mentat schema allows only for an open world (it's possible to declare friendships with people whose email isn't known), and requires validation code to enforce email string correctness — but we think that even such a tiny SQL example is harder to understand and obscures important domain decisions.
|
||||
|
||||
- Queries are intimately tied to structural storage choices. That not only hides the declarative domain-level meaning of the query — it's hard to tell what a query is trying to do when it's a 100-line mess of subqueries and `LEFT OUTER JOIN`s — but it also means a simple structural schema change requires auditing _every query_ for correctness.
|
||||
|
||||
- Developers often capture less event-shaped than they perhaps should, simply because their initial requirements don't warrant it. It's quite common to later want to [know when a fact was recorded](https://bugzilla.mozilla.org/show_bug.cgi?id=1341939), or _in which order_ two facts were recorded (particularly for migrations), or on which device an event took place… or even that a fact was _ever_ recorded and then deleted.
|
||||
|
||||
- Common queries are hard. Storing values only once, upserts, complicated joins, and group-wise maxima are all difficult for non-expert developers to get right.
|
||||
|
||||
- It's hard to evolve storage schemas. Writing a robust SQL schema migration is hard, particularly if a bad migration has ever escaped into the wild! Teams learn to fear and avoid schema changes, and eventually they ship a table called `metadata`, with three `TEXT` columns, so they never have to write a migration again. That decision pushes storage complexity into application code. (Or they start storing unversioned JSON blobs in the database…)
|
||||
|
||||
- It's hard to share storage with another component, let alone share _data_ with another component. Conway's Law applies: your software system will often grow to have one database per team.
|
||||
|
||||
- It's hard to build efficient storage and querying architectures. Materialized views require knowledge of triggers, or the implementation of bottleneck APIs. _Ad hoc_ caches are often wrong, are almost never formally designed (do you want a write-back, write-through, or write-around cache? Do you know the difference?), and often aren't reusable. The average developer, faced with a SQL database, has little choice but to build a simple table that tries to meet every need.
|
||||
|
||||
|
||||
## Comparison to DataScript
|
||||
|
||||
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.
|
||||
|
||||
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?"
|
||||
|
||||
(Note that [domain-level schemas are very valuable](http://martinfowler.com/articles/schemaless/).)
|
||||
|
||||
Another possible question would be: "What if we could bake some of the concepts of [CQRS and event sourcing](http://www.baeldung.com/cqrs-event-sourced-architecture-resources) into a persistent relational store, such that the transaction log itself were of value to queries?"
|
||||
|
||||
Some thought has been given to how databases as values — long-term references to a snapshot of the store at an instant in time — could work in this model. It's not impossible; it simply has different performance characteristics.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
## Comparison to Datomic
|
||||
|
||||
Datomic is a server-side, enterprise-grade data storage system. Datomic has a beautiful conceptual model. It's intended to be backed by a storage cluster, in which it keeps index chunks forever. Index chunks are replicated to peers, allowing it to run queries at the edges. Writes are serialized through a transactor.
|
||||
|
||||
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
|
||||
|
||||
SQLite is a traditional SQL database in most respects: schemas conflate semantic, structural, and datatype concerns, as described above; the main interface with the database is human-first textual queries; sparse and graph-structured data are 'unnatural', if not always inefficient; experimenting with and evolving data models are error-prone and complicated activities; and so on.
|
||||
|
||||
Mentat aims to offer many of the advantages of SQLite — single-file use, embeddability, and good performance — while building a more relaxed, reusable, and expressive data model on top.
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
Please note that this project is released with a Contributor Code of Conduct.
|
||||
By participating in this project you agree to abide by its terms.
|
||||
|
||||
See [CONTRIBUTING.md](/CONTRIBUTING/) for further notes.
|
||||
|
||||
This project is very new, so we'll probably revise these guidelines. Please
|
||||
comment on an issue before putting significant effort in if you'd like to
|
||||
contribute.
|
60
docs/apis/0.7/java/allclasses-frame.html
Normal file
60
docs/apis/0.7/java/allclasses-frame.html
Normal file
|
@ -0,0 +1,60 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<!-- NewPage -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Generated by javadoc (1.8.0_152-release) on Thu Jun 28 11:01:15 BST 2018 -->
|
||||
<title>All Classes</title>
|
||||
<meta name="date" content="2018-06-28">
|
||||
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
|
||||
<script type="text/javascript" src="script.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="bar">All Classes</h1>
|
||||
<div class="indexContainer">
|
||||
<ul>
|
||||
<li><a href="org/mozilla/mentat/CacheDirection.html" title="enum in org.mozilla.mentat" target="classFrame">CacheDirection</a></li>
|
||||
<li><a href="org/mozilla/mentat/CollResult.html" title="class in org.mozilla.mentat" target="classFrame">CollResult</a></li>
|
||||
<li><a href="org/mozilla/mentat/CollResultHandler.html" title="interface in org.mozilla.mentat" target="classFrame"><span class="interfaceName">CollResultHandler</span></a></li>
|
||||
<li><a href="org/mozilla/mentat/ColResultIterator.html" title="class in org.mozilla.mentat" target="classFrame">ColResultIterator</a></li>
|
||||
<li><a href="org/mozilla/mentat/EntityBuilder.html" title="class in org.mozilla.mentat" target="classFrame">EntityBuilder</a></li>
|
||||
<li><a href="org/mozilla/mentat/InProgress.html" title="class in org.mozilla.mentat" target="classFrame">InProgress</a></li>
|
||||
<li><a href="org/mozilla/mentat/InProgressBuilder.html" title="class in org.mozilla.mentat" target="classFrame">InProgressBuilder</a></li>
|
||||
<li><a href="org/mozilla/mentat/InProgressTransactionResult.html" title="class in org.mozilla.mentat" target="classFrame">InProgressTransactionResult</a></li>
|
||||
<li><a href="org/mozilla/mentat/InProgressTransactionResult.ByReference.html" title="class in org.mozilla.mentat" target="classFrame">InProgressTransactionResult.ByReference</a></li>
|
||||
<li><a href="org/mozilla/mentat/InProgressTransactionResult.ByValue.html" title="class in org.mozilla.mentat" target="classFrame">InProgressTransactionResult.ByValue</a></li>
|
||||
<li><a href="org/mozilla/mentat/JNA.html" title="interface in org.mozilla.mentat" target="classFrame"><span class="interfaceName">JNA</span></a></li>
|
||||
<li><a href="org/mozilla/mentat/JNA.EntityBuilder.html" title="class in org.mozilla.mentat" target="classFrame">JNA.EntityBuilder</a></li>
|
||||
<li><a href="org/mozilla/mentat/JNA.InProgress.html" title="class in org.mozilla.mentat" target="classFrame">JNA.InProgress</a></li>
|
||||
<li><a href="org/mozilla/mentat/JNA.InProgressBuilder.html" title="class in org.mozilla.mentat" target="classFrame">JNA.InProgressBuilder</a></li>
|
||||
<li><a href="org/mozilla/mentat/JNA.QueryBuilder.html" title="class in org.mozilla.mentat" target="classFrame">JNA.QueryBuilder</a></li>
|
||||
<li><a href="org/mozilla/mentat/JNA.RelResult.html" title="class in org.mozilla.mentat" target="classFrame">JNA.RelResult</a></li>
|
||||
<li><a href="org/mozilla/mentat/JNA.RelResultIter.html" title="class in org.mozilla.mentat" target="classFrame">JNA.RelResultIter</a></li>
|
||||
<li><a href="org/mozilla/mentat/JNA.Store.html" title="class in org.mozilla.mentat" target="classFrame">JNA.Store</a></li>
|
||||
<li><a href="org/mozilla/mentat/JNA.TxReport.html" title="class in org.mozilla.mentat" target="classFrame">JNA.TxReport</a></li>
|
||||
<li><a href="org/mozilla/mentat/JNA.TypedValue.html" title="class in org.mozilla.mentat" target="classFrame">JNA.TypedValue</a></li>
|
||||
<li><a href="org/mozilla/mentat/JNA.TypedValueList.html" title="class in org.mozilla.mentat" target="classFrame">JNA.TypedValueList</a></li>
|
||||
<li><a href="org/mozilla/mentat/JNA.TypedValueListIter.html" title="class in org.mozilla.mentat" target="classFrame">JNA.TypedValueListIter</a></li>
|
||||
<li><a href="org/mozilla/mentat/Mentat.html" title="class in org.mozilla.mentat" target="classFrame">Mentat</a></li>
|
||||
<li><a href="org/mozilla/mentat/Query.html" title="class in org.mozilla.mentat" target="classFrame">Query</a></li>
|
||||
<li><a href="org/mozilla/mentat/RelResult.html" title="class in org.mozilla.mentat" target="classFrame">RelResult</a></li>
|
||||
<li><a href="org/mozilla/mentat/RelResultHandler.html" title="interface in org.mozilla.mentat" target="classFrame"><span class="interfaceName">RelResultHandler</span></a></li>
|
||||
<li><a href="org/mozilla/mentat/RelResultIterator.html" title="class in org.mozilla.mentat" target="classFrame">RelResultIterator</a></li>
|
||||
<li><a href="org/mozilla/mentat/RustError.html" title="class in org.mozilla.mentat" target="classFrame">RustError</a></li>
|
||||
<li><a href="org/mozilla/mentat/RustError.ByReference.html" title="class in org.mozilla.mentat" target="classFrame">RustError.ByReference</a></li>
|
||||
<li><a href="org/mozilla/mentat/RustError.ByValue.html" title="class in org.mozilla.mentat" target="classFrame">RustError.ByValue</a></li>
|
||||
<li><a href="org/mozilla/mentat/ScalarResultHandler.html" title="interface in org.mozilla.mentat" target="classFrame"><span class="interfaceName">ScalarResultHandler</span></a></li>
|
||||
<li><a href="org/mozilla/mentat/TupleResult.html" title="class in org.mozilla.mentat" target="classFrame">TupleResult</a></li>
|
||||
<li><a href="org/mozilla/mentat/TupleResultHandler.html" title="interface in org.mozilla.mentat" target="classFrame"><span class="interfaceName">TupleResultHandler</span></a></li>
|
||||
<li><a href="org/mozilla/mentat/TxChange.html" title="class in org.mozilla.mentat" target="classFrame">TxChange</a></li>
|
||||
<li><a href="org/mozilla/mentat/TxChange.ByReference.html" title="class in org.mozilla.mentat" target="classFrame">TxChange.ByReference</a></li>
|
||||
<li><a href="org/mozilla/mentat/TxChange.ByValue.html" title="class in org.mozilla.mentat" target="classFrame">TxChange.ByValue</a></li>
|
||||
<li><a href="org/mozilla/mentat/TxChangeList.html" title="class in org.mozilla.mentat" target="classFrame">TxChangeList</a></li>
|
||||
<li><a href="org/mozilla/mentat/TxChangeList.ByReference.html" title="class in org.mozilla.mentat" target="classFrame">TxChangeList.ByReference</a></li>
|
||||
<li><a href="org/mozilla/mentat/TxChangeList.ByValue.html" title="class in org.mozilla.mentat" target="classFrame">TxChangeList.ByValue</a></li>
|
||||
<li><a href="org/mozilla/mentat/TxObserverCallback.html" title="interface in org.mozilla.mentat" target="classFrame"><span class="interfaceName">TxObserverCallback</span></a></li>
|
||||
<li><a href="org/mozilla/mentat/TxReport.html" title="class in org.mozilla.mentat" target="classFrame">TxReport</a></li>
|
||||
<li><a href="org/mozilla/mentat/TypedValue.html" title="class in org.mozilla.mentat" target="classFrame">TypedValue</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
60
docs/apis/0.7/java/allclasses-noframe.html
Normal file
60
docs/apis/0.7/java/allclasses-noframe.html
Normal file
|
@ -0,0 +1,60 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<!-- NewPage -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Generated by javadoc (1.8.0_152-release) on Thu Jun 28 11:01:15 BST 2018 -->
|
||||
<title>All Classes</title>
|
||||
<meta name="date" content="2018-06-28">
|
||||
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
|
||||
<script type="text/javascript" src="script.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="bar">All Classes</h1>
|
||||
<div class="indexContainer">
|
||||
<ul>
|
||||
<li><a href="org/mozilla/mentat/CacheDirection.html" title="enum in org.mozilla.mentat">CacheDirection</a></li>
|
||||
<li><a href="org/mozilla/mentat/CollResult.html" title="class in org.mozilla.mentat">CollResult</a></li>
|
||||
<li><a href="org/mozilla/mentat/CollResultHandler.html" title="interface in org.mozilla.mentat"><span class="interfaceName">CollResultHandler</span></a></li>
|
||||
<li><a href="org/mozilla/mentat/ColResultIterator.html" title="class in org.mozilla.mentat">ColResultIterator</a></li>
|
||||
<li><a href="org/mozilla/mentat/EntityBuilder.html" title="class in org.mozilla.mentat">EntityBuilder</a></li>
|
||||
<li><a href="org/mozilla/mentat/InProgress.html" title="class in org.mozilla.mentat">InProgress</a></li>
|
||||
<li><a href="org/mozilla/mentat/InProgressBuilder.html" title="class in org.mozilla.mentat">InProgressBuilder</a></li>
|
||||
<li><a href="org/mozilla/mentat/InProgressTransactionResult.html" title="class in org.mozilla.mentat">InProgressTransactionResult</a></li>
|
||||
<li><a href="org/mozilla/mentat/InProgressTransactionResult.ByReference.html" title="class in org.mozilla.mentat">InProgressTransactionResult.ByReference</a></li>
|
||||
<li><a href="org/mozilla/mentat/InProgressTransactionResult.ByValue.html" title="class in org.mozilla.mentat">InProgressTransactionResult.ByValue</a></li>
|
||||
<li><a href="org/mozilla/mentat/JNA.html" title="interface in org.mozilla.mentat"><span class="interfaceName">JNA</span></a></li>
|
||||
<li><a href="org/mozilla/mentat/JNA.EntityBuilder.html" title="class in org.mozilla.mentat">JNA.EntityBuilder</a></li>
|
||||
<li><a href="org/mozilla/mentat/JNA.InProgress.html" title="class in org.mozilla.mentat">JNA.InProgress</a></li>
|
||||
<li><a href="org/mozilla/mentat/JNA.InProgressBuilder.html" title="class in org.mozilla.mentat">JNA.InProgressBuilder</a></li>
|
||||
<li><a href="org/mozilla/mentat/JNA.QueryBuilder.html" title="class in org.mozilla.mentat">JNA.QueryBuilder</a></li>
|
||||
<li><a href="org/mozilla/mentat/JNA.RelResult.html" title="class in org.mozilla.mentat">JNA.RelResult</a></li>
|
||||
<li><a href="org/mozilla/mentat/JNA.RelResultIter.html" title="class in org.mozilla.mentat">JNA.RelResultIter</a></li>
|
||||
<li><a href="org/mozilla/mentat/JNA.Store.html" title="class in org.mozilla.mentat">JNA.Store</a></li>
|
||||
<li><a href="org/mozilla/mentat/JNA.TxReport.html" title="class in org.mozilla.mentat">JNA.TxReport</a></li>
|
||||
<li><a href="org/mozilla/mentat/JNA.TypedValue.html" title="class in org.mozilla.mentat">JNA.TypedValue</a></li>
|
||||
<li><a href="org/mozilla/mentat/JNA.TypedValueList.html" title="class in org.mozilla.mentat">JNA.TypedValueList</a></li>
|
||||
<li><a href="org/mozilla/mentat/JNA.TypedValueListIter.html" title="class in org.mozilla.mentat">JNA.TypedValueListIter</a></li>
|
||||
<li><a href="org/mozilla/mentat/Mentat.html" title="class in org.mozilla.mentat">Mentat</a></li>
|
||||
<li><a href="org/mozilla/mentat/Query.html" title="class in org.mozilla.mentat">Query</a></li>
|
||||
<li><a href="org/mozilla/mentat/RelResult.html" title="class in org.mozilla.mentat">RelResult</a></li>
|
||||
<li><a href="org/mozilla/mentat/RelResultHandler.html" title="interface in org.mozilla.mentat"><span class="interfaceName">RelResultHandler</span></a></li>
|
||||
<li><a href="org/mozilla/mentat/RelResultIterator.html" title="class in org.mozilla.mentat">RelResultIterator</a></li>
|
||||
<li><a href="org/mozilla/mentat/RustError.html" title="class in org.mozilla.mentat">RustError</a></li>
|
||||
<li><a href="org/mozilla/mentat/RustError.ByReference.html" title="class in org.mozilla.mentat">RustError.ByReference</a></li>
|
||||
<li><a href="org/mozilla/mentat/RustError.ByValue.html" title="class in org.mozilla.mentat">RustError.ByValue</a></li>
|
||||
<li><a href="org/mozilla/mentat/ScalarResultHandler.html" title="interface in org.mozilla.mentat"><span class="interfaceName">ScalarResultHandler</span></a></li>
|
||||
<li><a href="org/mozilla/mentat/TupleResult.html" title="class in org.mozilla.mentat">TupleResult</a></li>
|
||||
<li><a href="org/mozilla/mentat/TupleResultHandler.html" title="interface in org.mozilla.mentat"><span class="interfaceName">TupleResultHandler</span></a></li>
|
||||
<li><a href="org/mozilla/mentat/TxChange.html" title="class in org.mozilla.mentat">TxChange</a></li>
|
||||
<li><a href="org/mozilla/mentat/TxChange.ByReference.html" title="class in org.mozilla.mentat">TxChange.ByReference</a></li>
|
||||
<li><a href="org/mozilla/mentat/TxChange.ByValue.html" title="class in org.mozilla.mentat">TxChange.ByValue</a></li>
|
||||
<li><a href="org/mozilla/mentat/TxChangeList.html" title="class in org.mozilla.mentat">TxChangeList</a></li>
|
||||
<li><a href="org/mozilla/mentat/TxChangeList.ByReference.html" title="class in org.mozilla.mentat">TxChangeList.ByReference</a></li>
|
||||
<li><a href="org/mozilla/mentat/TxChangeList.ByValue.html" title="class in org.mozilla.mentat">TxChangeList.ByValue</a></li>
|
||||
<li><a href="org/mozilla/mentat/TxObserverCallback.html" title="interface in org.mozilla.mentat"><span class="interfaceName">TxObserverCallback</span></a></li>
|
||||
<li><a href="org/mozilla/mentat/TxReport.html" title="class in org.mozilla.mentat">TxReport</a></li>
|
||||
<li><a href="org/mozilla/mentat/TypedValue.html" title="class in org.mozilla.mentat">TypedValue</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
149
docs/apis/0.7/java/constant-values.html
Normal file
149
docs/apis/0.7/java/constant-values.html
Normal file
|
@ -0,0 +1,149 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<!-- NewPage -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Generated by javadoc (1.8.0_152-release) on Thu Jun 28 11:01:15 BST 2018 -->
|
||||
<title>Constant Field Values</title>
|
||||
<meta name="date" content="2018-06-28">
|
||||
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
|
||||
<script type="text/javascript" src="script.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript"><!--
|
||||
try {
|
||||
if (location.href.indexOf('is-external=true') == -1) {
|
||||
parent.document.title="Constant Field Values";
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
<noscript>
|
||||
<div>JavaScript is disabled on your browser.</div>
|
||||
</noscript>
|
||||
<!-- ========= START OF TOP NAVBAR ======= -->
|
||||
<div class="topNav"><a name="navbar.top">
|
||||
<!-- -->
|
||||
</a>
|
||||
<div class="skipNav"><a href="#skip.navbar.top" title="Skip navigation links">Skip navigation links</a></div>
|
||||
<a name="navbar.top.firstrow">
|
||||
<!-- -->
|
||||
</a>
|
||||
<ul class="navList" title="Navigation">
|
||||
<li><a href="org/mozilla/mentat/package-summary.html">Package</a></li>
|
||||
<li>Class</li>
|
||||
<li><a href="overview-tree.html">Tree</a></li>
|
||||
<li><a href="deprecated-list.html">Deprecated</a></li>
|
||||
<li><a href="index-files/index-1.html">Index</a></li>
|
||||
<li><a href="help-doc.html">Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="subNav">
|
||||
<ul class="navList">
|
||||
<li>Prev</li>
|
||||
<li>Next</li>
|
||||
</ul>
|
||||
<ul class="navList">
|
||||
<li><a href="index.html?constant-values.html" target="_top">Frames</a></li>
|
||||
<li><a href="constant-values.html" target="_top">No Frames</a></li>
|
||||
</ul>
|
||||
<ul class="navList" id="allclasses_navbar_top">
|
||||
<li><a href="allclasses-noframe.html">All Classes</a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<script type="text/javascript"><!--
|
||||
allClassesLink = document.getElementById("allclasses_navbar_top");
|
||||
if(window==top) {
|
||||
allClassesLink.style.display = "block";
|
||||
}
|
||||
else {
|
||||
allClassesLink.style.display = "none";
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
</div>
|
||||
<a name="skip.navbar.top">
|
||||
<!-- -->
|
||||
</a></div>
|
||||
<!-- ========= END OF TOP NAVBAR ========= -->
|
||||
<div class="header">
|
||||
<h1 title="Constant Field Values" class="title">Constant Field Values</h1>
|
||||
<h2 title="Contents">Contents</h2>
|
||||
<ul>
|
||||
<li><a href="#org.mozilla">org.mozilla.*</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="constantValuesContainer"><a name="org.mozilla">
|
||||
<!-- -->
|
||||
</a>
|
||||
<h2 title="org.mozilla">org.mozilla.*</h2>
|
||||
<ul class="blockList">
|
||||
<li class="blockList">
|
||||
<table class="constantsSummary" border="0" cellpadding="3" cellspacing="0" summary="Constant Field Values table, listing constant fields, and values">
|
||||
<caption><span>org.mozilla.mentat.<a href="org/mozilla/mentat/JNA.html" title="interface in org.mozilla.mentat">JNA</a></span><span class="tabEnd"> </span></caption>
|
||||
<tr>
|
||||
<th class="colFirst" scope="col">Modifier and Type</th>
|
||||
<th scope="col">Constant Field</th>
|
||||
<th class="colLast" scope="col">Value</th>
|
||||
</tr>
|
||||
<tbody>
|
||||
<tr class="altColor">
|
||||
<td class="colFirst"><a name="org.mozilla.mentat.JNA.JNA_LIBRARY_NAME">
|
||||
<!-- -->
|
||||
</a><code>public static final <a href="https://developer.android.com/reference/java/lang/String.html?is-external=true" title="class or interface in java.lang">String</a></code></td>
|
||||
<td><code><a href="org/mozilla/mentat/JNA.html#JNA_LIBRARY_NAME">JNA_LIBRARY_NAME</a></code></td>
|
||||
<td class="colLast"><code>"mentat_ffi"</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- ======= START OF BOTTOM NAVBAR ====== -->
|
||||
<div class="bottomNav"><a name="navbar.bottom">
|
||||
<!-- -->
|
||||
</a>
|
||||
<div class="skipNav"><a href="#skip.navbar.bottom" title="Skip navigation links">Skip navigation links</a></div>
|
||||
<a name="navbar.bottom.firstrow">
|
||||
<!-- -->
|
||||
</a>
|
||||
<ul class="navList" title="Navigation">
|
||||
<li><a href="org/mozilla/mentat/package-summary.html">Package</a></li>
|
||||
<li>Class</li>
|
||||
<li><a href="overview-tree.html">Tree</a></li>
|
||||
<li><a href="deprecated-list.html">Deprecated</a></li>
|
||||
<li><a href="index-files/index-1.html">Index</a></li>
|
||||
<li><a href="help-doc.html">Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="subNav">
|
||||
<ul class="navList">
|
||||
<li>Prev</li>
|
||||
<li>Next</li>
|
||||
</ul>
|
||||
<ul class="navList">
|
||||
<li><a href="index.html?constant-values.html" target="_top">Frames</a></li>
|
||||
<li><a href="constant-values.html" target="_top">No Frames</a></li>
|
||||
</ul>
|
||||
<ul class="navList" id="allclasses_navbar_bottom">
|
||||
<li><a href="allclasses-noframe.html">All Classes</a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<script type="text/javascript"><!--
|
||||
allClassesLink = document.getElementById("allclasses_navbar_bottom");
|
||||
if(window==top) {
|
||||
allClassesLink.style.display = "block";
|
||||
}
|
||||
else {
|
||||
allClassesLink.style.display = "none";
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
</div>
|
||||
<a name="skip.navbar.bottom">
|
||||
<!-- -->
|
||||
</a></div>
|
||||
<!-- ======== END OF BOTTOM NAVBAR ======= -->
|
||||
</body>
|
||||
</html>
|
120
docs/apis/0.7/java/deprecated-list.html
Normal file
120
docs/apis/0.7/java/deprecated-list.html
Normal file
|
@ -0,0 +1,120 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<!-- NewPage -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Generated by javadoc (1.8.0_152-release) on Thu Jun 28 11:01:15 BST 2018 -->
|
||||
<title>Deprecated List</title>
|
||||
<meta name="date" content="2018-06-28">
|
||||
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
|
||||
<script type="text/javascript" src="script.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript"><!--
|
||||
try {
|
||||
if (location.href.indexOf('is-external=true') == -1) {
|
||||
parent.document.title="Deprecated List";
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
<noscript>
|
||||
<div>JavaScript is disabled on your browser.</div>
|
||||
</noscript>
|
||||
<!-- ========= START OF TOP NAVBAR ======= -->
|
||||
<div class="topNav"><a name="navbar.top">
|
||||
<!-- -->
|
||||
</a>
|
||||
<div class="skipNav"><a href="#skip.navbar.top" title="Skip navigation links">Skip navigation links</a></div>
|
||||
<a name="navbar.top.firstrow">
|
||||
<!-- -->
|
||||
</a>
|
||||
<ul class="navList" title="Navigation">
|
||||
<li><a href="org/mozilla/mentat/package-summary.html">Package</a></li>
|
||||
<li>Class</li>
|
||||
<li><a href="overview-tree.html">Tree</a></li>
|
||||
<li class="navBarCell1Rev">Deprecated</li>
|
||||
<li><a href="index-files/index-1.html">Index</a></li>
|
||||
<li><a href="help-doc.html">Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="subNav">
|
||||
<ul class="navList">
|
||||
<li>Prev</li>
|
||||
<li>Next</li>
|
||||
</ul>
|
||||
<ul class="navList">
|
||||
<li><a href="index.html?deprecated-list.html" target="_top">Frames</a></li>
|
||||
<li><a href="deprecated-list.html" target="_top">No Frames</a></li>
|
||||
</ul>
|
||||
<ul class="navList" id="allclasses_navbar_top">
|
||||
<li><a href="allclasses-noframe.html">All Classes</a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<script type="text/javascript"><!--
|
||||
allClassesLink = document.getElementById("allclasses_navbar_top");
|
||||
if(window==top) {
|
||||
allClassesLink.style.display = "block";
|
||||
}
|
||||
else {
|
||||
allClassesLink.style.display = "none";
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
</div>
|
||||
<a name="skip.navbar.top">
|
||||
<!-- -->
|
||||
</a></div>
|
||||
<!-- ========= END OF TOP NAVBAR ========= -->
|
||||
<div class="header">
|
||||
<h1 title="Deprecated API" class="title">Deprecated API</h1>
|
||||
<h2 title="Contents">Contents</h2>
|
||||
</div>
|
||||
<!-- ======= START OF BOTTOM NAVBAR ====== -->
|
||||
<div class="bottomNav"><a name="navbar.bottom">
|
||||
<!-- -->
|
||||
</a>
|
||||
<div class="skipNav"><a href="#skip.navbar.bottom" title="Skip navigation links">Skip navigation links</a></div>
|
||||
<a name="navbar.bottom.firstrow">
|
||||
<!-- -->
|
||||
</a>
|
||||
<ul class="navList" title="Navigation">
|
||||
<li><a href="org/mozilla/mentat/package-summary.html">Package</a></li>
|
||||
<li>Class</li>
|
||||
<li><a href="overview-tree.html">Tree</a></li>
|
||||
<li class="navBarCell1Rev">Deprecated</li>
|
||||
<li><a href="index-files/index-1.html">Index</a></li>
|
||||
<li><a href="help-doc.html">Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="subNav">
|
||||
<ul class="navList">
|
||||
<li>Prev</li>
|
||||
<li>Next</li>
|
||||
</ul>
|
||||
<ul class="navList">
|
||||
<li><a href="index.html?deprecated-list.html" target="_top">Frames</a></li>
|
||||
<li><a href="deprecated-list.html" target="_top">No Frames</a></li>
|
||||
</ul>
|
||||
<ul class="navList" id="allclasses_navbar_bottom">
|
||||
<li><a href="allclasses-noframe.html">All Classes</a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<script type="text/javascript"><!--
|
||||
allClassesLink = document.getElementById("allclasses_navbar_bottom");
|
||||
if(window==top) {
|
||||
allClassesLink.style.display = "block";
|
||||
}
|
||||
else {
|
||||
allClassesLink.style.display = "none";
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
</div>
|
||||
<a name="skip.navbar.bottom">
|
||||
<!-- -->
|
||||
</a></div>
|
||||
<!-- ======== END OF BOTTOM NAVBAR ======= -->
|
||||
</body>
|
||||
</html>
|
217
docs/apis/0.7/java/help-doc.html
Normal file
217
docs/apis/0.7/java/help-doc.html
Normal file
|
@ -0,0 +1,217 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<!-- NewPage -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Generated by javadoc (1.8.0_152-release) on Thu Jun 28 11:01:15 BST 2018 -->
|
||||
<title>API Help</title>
|
||||
<meta name="date" content="2018-06-28">
|
||||
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
|
||||
<script type="text/javascript" src="script.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript"><!--
|
||||
try {
|
||||
if (location.href.indexOf('is-external=true') == -1) {
|
||||
parent.document.title="API Help";
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
<noscript>
|
||||
<div>JavaScript is disabled on your browser.</div>
|
||||
</noscript>
|
||||
<!-- ========= START OF TOP NAVBAR ======= -->
|
||||
<div class="topNav"><a name="navbar.top">
|
||||
<!-- -->
|
||||
</a>
|
||||
<div class="skipNav"><a href="#skip.navbar.top" title="Skip navigation links">Skip navigation links</a></div>
|
||||
<a name="navbar.top.firstrow">
|
||||
<!-- -->
|
||||
</a>
|
||||
<ul class="navList" title="Navigation">
|
||||
<li><a href="org/mozilla/mentat/package-summary.html">Package</a></li>
|
||||
<li>Class</li>
|
||||
<li><a href="overview-tree.html">Tree</a></li>
|
||||
<li><a href="deprecated-list.html">Deprecated</a></li>
|
||||
<li><a href="index-files/index-1.html">Index</a></li>
|
||||
<li class="navBarCell1Rev">Help</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="subNav">
|
||||
<ul class="navList">
|
||||
<li>Prev</li>
|
||||
<li>Next</li>
|
||||
</ul>
|
||||
<ul class="navList">
|
||||
<li><a href="index.html?help-doc.html" target="_top">Frames</a></li>
|
||||
<li><a href="help-doc.html" target="_top">No Frames</a></li>
|
||||
</ul>
|
||||
<ul class="navList" id="allclasses_navbar_top">
|
||||
<li><a href="allclasses-noframe.html">All Classes</a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<script type="text/javascript"><!--
|
||||
allClassesLink = document.getElementById("allclasses_navbar_top");
|
||||
if(window==top) {
|
||||
allClassesLink.style.display = "block";
|
||||
}
|
||||
else {
|
||||
allClassesLink.style.display = "none";
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
</div>
|
||||
<a name="skip.navbar.top">
|
||||
<!-- -->
|
||||
</a></div>
|
||||
<!-- ========= END OF TOP NAVBAR ========= -->
|
||||
<div class="header">
|
||||
<h1 class="title">How This API Document Is Organized</h1>
|
||||
<div class="subTitle">This API (Application Programming Interface) document has pages corresponding to the items in the navigation bar, described as follows.</div>
|
||||
</div>
|
||||
<div class="contentContainer">
|
||||
<ul class="blockList">
|
||||
<li class="blockList">
|
||||
<h2>Package</h2>
|
||||
<p>Each package has a page that contains a list of its classes and interfaces, with a summary for each. This page can contain six categories:</p>
|
||||
<ul>
|
||||
<li>Interfaces (italic)</li>
|
||||
<li>Classes</li>
|
||||
<li>Enums</li>
|
||||
<li>Exceptions</li>
|
||||
<li>Errors</li>
|
||||
<li>Annotation Types</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="blockList">
|
||||
<h2>Class/Interface</h2>
|
||||
<p>Each class, interface, nested class and nested interface has its own separate page. Each of these pages has three sections consisting of a class/interface description, summary tables, and detailed member descriptions:</p>
|
||||
<ul>
|
||||
<li>Class inheritance diagram</li>
|
||||
<li>Direct Subclasses</li>
|
||||
<li>All Known Subinterfaces</li>
|
||||
<li>All Known Implementing Classes</li>
|
||||
<li>Class/interface declaration</li>
|
||||
<li>Class/interface description</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>Nested Class Summary</li>
|
||||
<li>Field Summary</li>
|
||||
<li>Constructor Summary</li>
|
||||
<li>Method Summary</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>Field Detail</li>
|
||||
<li>Constructor Detail</li>
|
||||
<li>Method Detail</li>
|
||||
</ul>
|
||||
<p>Each summary entry contains the first sentence from the detailed description for that item. The summary entries are alphabetical, while the detailed descriptions are in the order they appear in the source code. This preserves the logical groupings established by the programmer.</p>
|
||||
</li>
|
||||
<li class="blockList">
|
||||
<h2>Annotation Type</h2>
|
||||
<p>Each annotation type has its own separate page with the following sections:</p>
|
||||
<ul>
|
||||
<li>Annotation Type declaration</li>
|
||||
<li>Annotation Type description</li>
|
||||
<li>Required Element Summary</li>
|
||||
<li>Optional Element Summary</li>
|
||||
<li>Element Detail</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="blockList">
|
||||
<h2>Enum</h2>
|
||||
<p>Each enum has its own separate page with the following sections:</p>
|
||||
<ul>
|
||||
<li>Enum declaration</li>
|
||||
<li>Enum description</li>
|
||||
<li>Enum Constant Summary</li>
|
||||
<li>Enum Constant Detail</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="blockList">
|
||||
<h2>Tree (Class Hierarchy)</h2>
|
||||
<p>There is a <a href="overview-tree.html">Class Hierarchy</a> page for all packages, plus a hierarchy for each package. Each hierarchy page contains a list of classes and a list of interfaces. The classes are organized by inheritance structure starting with <code>java.lang.Object</code>. The interfaces do not inherit from <code>java.lang.Object</code>.</p>
|
||||
<ul>
|
||||
<li>When viewing the Overview page, clicking on "Tree" displays the hierarchy for all packages.</li>
|
||||
<li>When viewing a particular package, class or interface page, clicking "Tree" displays the hierarchy for only that package.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="blockList">
|
||||
<h2>Deprecated API</h2>
|
||||
<p>The <a href="deprecated-list.html">Deprecated API</a> page lists all of the API that have been deprecated. A deprecated API is not recommended for use, generally due to improvements, and a replacement API is usually given. Deprecated APIs may be removed in future implementations.</p>
|
||||
</li>
|
||||
<li class="blockList">
|
||||
<h2>Index</h2>
|
||||
<p>The <a href="index-files/index-1.html">Index</a> contains an alphabetic list of all classes, interfaces, constructors, methods, and fields.</p>
|
||||
</li>
|
||||
<li class="blockList">
|
||||
<h2>Prev/Next</h2>
|
||||
<p>These links take you to the next or previous class, interface, package, or related page.</p>
|
||||
</li>
|
||||
<li class="blockList">
|
||||
<h2>Frames/No Frames</h2>
|
||||
<p>These links show and hide the HTML frames. All pages are available with or without frames.</p>
|
||||
</li>
|
||||
<li class="blockList">
|
||||
<h2>All Classes</h2>
|
||||
<p>The <a href="allclasses-noframe.html">All Classes</a> link shows all classes and interfaces except non-static nested types.</p>
|
||||
</li>
|
||||
<li class="blockList">
|
||||
<h2>Serialized Form</h2>
|
||||
<p>Each serializable or externalizable class has a description of its serialization fields and methods. This information is of interest to re-implementors, not to developers using the API. While there is no link in the navigation bar, you can get to this information by going to any serialized class and clicking "Serialized Form" in the "See also" section of the class description.</p>
|
||||
</li>
|
||||
<li class="blockList">
|
||||
<h2>Constant Field Values</h2>
|
||||
<p>The <a href="constant-values.html">Constant Field Values</a> page lists the static final fields and their values.</p>
|
||||
</li>
|
||||
</ul>
|
||||
<span class="emphasizedPhrase">This help file applies to API documentation generated using the standard doclet.</span></div>
|
||||
<!-- ======= START OF BOTTOM NAVBAR ====== -->
|
||||
<div class="bottomNav"><a name="navbar.bottom">
|
||||
<!-- -->
|
||||
</a>
|
||||
<div class="skipNav"><a href="#skip.navbar.bottom" title="Skip navigation links">Skip navigation links</a></div>
|
||||
<a name="navbar.bottom.firstrow">
|
||||
<!-- -->
|
||||
</a>
|
||||
<ul class="navList" title="Navigation">
|
||||
<li><a href="org/mozilla/mentat/package-summary.html">Package</a></li>
|
||||
<li>Class</li>
|
||||
<li><a href="overview-tree.html">Tree</a></li>
|
||||
<li><a href="deprecated-list.html">Deprecated</a></li>
|
||||
<li><a href="index-files/index-1.html">Index</a></li>
|
||||
<li class="navBarCell1Rev">Help</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="subNav">
|
||||
<ul class="navList">
|
||||
<li>Prev</li>
|
||||
<li>Next</li>
|
||||
</ul>
|
||||
<ul class="navList">
|
||||
<li><a href="index.html?help-doc.html" target="_top">Frames</a></li>
|
||||
<li><a href="help-doc.html" target="_top">No Frames</a></li>
|
||||
</ul>
|
||||
<ul class="navList" id="allclasses_navbar_bottom">
|
||||
<li><a href="allclasses-noframe.html">All Classes</a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<script type="text/javascript"><!--
|
||||
allClassesLink = document.getElementById("allclasses_navbar_bottom");
|
||||
if(window==top) {
|
||||
allClassesLink.style.display = "block";
|
||||
}
|
||||
else {
|
||||
allClassesLink.style.display = "none";
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
</div>
|
||||
<a name="skip.navbar.bottom">
|
||||
<!-- -->
|
||||
</a></div>
|
||||
<!-- ======== END OF BOTTOM NAVBAR ======= -->
|
||||
</body>
|
||||
</html>
|
255
docs/apis/0.7/java/index-files/index-1.html
Normal file
255
docs/apis/0.7/java/index-files/index-1.html
Normal file
|
@ -0,0 +1,255 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<!-- NewPage -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Generated by javadoc (1.8.0_152-release) on Thu Jun 28 11:01:15 BST 2018 -->
|
||||
<title>A-Index</title>
|
||||
<meta name="date" content="2018-06-28">
|
||||
<link rel="stylesheet" type="text/css" href="../stylesheet.css" title="Style">
|
||||
<script type="text/javascript" src="../script.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript"><!--
|
||||
try {
|
||||
if (location.href.indexOf('is-external=true') == -1) {
|
||||
parent.document.title="A-Index";
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
<noscript>
|
||||
<div>JavaScript is disabled on your browser.</div>
|
||||
</noscript>
|
||||
<!-- ========= START OF TOP NAVBAR ======= -->
|
||||
<div class="topNav"><a name="navbar.top">
|
||||
<!-- -->
|
||||
</a>
|
||||
<div class="skipNav"><a href="#skip.navbar.top" title="Skip navigation links">Skip navigation links</a></div>
|
||||
<a name="navbar.top.firstrow">
|
||||
<!-- -->
|
||||
</a>
|
||||
<ul class="navList" title="Navigation">
|
||||
<li><a href="../org/mozilla/mentat/package-summary.html">Package</a></li>
|
||||
<li>Class</li>
|
||||
<li><a href="../overview-tree.html">Tree</a></li>
|
||||
<li><a href="../deprecated-list.html">Deprecated</a></li>
|
||||
<li class="navBarCell1Rev">Index</li>
|
||||
<li><a href="../help-doc.html">Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="subNav">
|
||||
<ul class="navList">
|
||||
<li>Prev Letter</li>
|
||||
<li><a href="index-2.html">Next Letter</a></li>
|
||||
</ul>
|
||||
<ul class="navList">
|
||||
<li><a href="../index.html?index-files/index-1.html" target="_top">Frames</a></li>
|
||||
<li><a href="index-1.html" target="_top">No Frames</a></li>
|
||||
</ul>
|
||||
<ul class="navList" id="allclasses_navbar_top">
|
||||
<li><a href="../allclasses-noframe.html">All Classes</a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<script type="text/javascript"><!--
|
||||
allClassesLink = document.getElementById("allclasses_navbar_top");
|
||||
if(window==top) {
|
||||
allClassesLink.style.display = "block";
|
||||
}
|
||||
else {
|
||||
allClassesLink.style.display = "none";
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
</div>
|
||||
<a name="skip.navbar.top">
|
||||
<!-- -->
|
||||
</a></div>
|
||||
<!-- ========= END OF TOP NAVBAR ========= -->
|
||||
<div class="contentContainer"><a href="index-1.html">A</a> <a href="index-2.html">B</a> <a href="index-3.html">C</a> <a href="index-4.html">D</a> <a href="index-5.html">E</a> <a href="index-6.html">F</a> <a href="index-7.html">G</a> <a href="index-8.html">H</a> <a href="index-9.html">I</a> <a href="index-10.html">J</a> <a href="index-11.html">L</a> <a href="index-12.html">M</a> <a href="index-13.html">O</a> <a href="index-14.html">Q</a> <a href="index-15.html">R</a> <a href="index-16.html">S</a> <a href="index-17.html">T</a> <a href="index-18.html">U</a> <a href="index-19.html">V</a> <a name="I:A">
|
||||
<!-- -->
|
||||
</a>
|
||||
<h2 class="title">A</h2>
|
||||
<dl>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/EntityBuilder.html#add-java.lang.String-long-">add(String, long)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/EntityBuilder.html" title="class in org.mozilla.mentat">EntityBuilder</a></dt>
|
||||
<dd>
|
||||
<div class="block">Asserts the value of attribute `keyword` to be the provided `value`.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/EntityBuilder.html#add-java.lang.String-boolean-">add(String, boolean)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/EntityBuilder.html" title="class in org.mozilla.mentat">EntityBuilder</a></dt>
|
||||
<dd>
|
||||
<div class="block">Asserts the value of attribute `keyword` to be the provided `value`.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/EntityBuilder.html#add-java.lang.String-double-">add(String, double)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/EntityBuilder.html" title="class in org.mozilla.mentat">EntityBuilder</a></dt>
|
||||
<dd>
|
||||
<div class="block">Asserts the value of attribute `keyword` to be the provided `value`.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/EntityBuilder.html#add-java.lang.String-java.util.Date-">add(String, Date)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/EntityBuilder.html" title="class in org.mozilla.mentat">EntityBuilder</a></dt>
|
||||
<dd>
|
||||
<div class="block">Asserts the value of attribute `keyword` to be the provided `value`.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/EntityBuilder.html#add-java.lang.String-java.lang.String-">add(String, String)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/EntityBuilder.html" title="class in org.mozilla.mentat">EntityBuilder</a></dt>
|
||||
<dd>
|
||||
<div class="block">Asserts the value of attribute `keyword` to be the provided `value`.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/EntityBuilder.html#add-java.lang.String-java.util.UUID-">add(String, UUID)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/EntityBuilder.html" title="class in org.mozilla.mentat">EntityBuilder</a></dt>
|
||||
<dd>
|
||||
<div class="block">Asserts the value of attribute `keyword` to be the provided `value`.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/InProgressBuilder.html#add-long-java.lang.String-long-">add(long, String, long)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/InProgressBuilder.html" title="class in org.mozilla.mentat">InProgressBuilder</a></dt>
|
||||
<dd>
|
||||
<div class="block">Asserts the value of attribute `keyword` to be the provided `value`.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/InProgressBuilder.html#add-long-java.lang.String-boolean-">add(long, String, boolean)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/InProgressBuilder.html" title="class in org.mozilla.mentat">InProgressBuilder</a></dt>
|
||||
<dd>
|
||||
<div class="block">Asserts the value of attribute `keyword` to be the provided `value`.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/InProgressBuilder.html#add-long-java.lang.String-double-">add(long, String, double)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/InProgressBuilder.html" title="class in org.mozilla.mentat">InProgressBuilder</a></dt>
|
||||
<dd>
|
||||
<div class="block">Asserts the value of attribute `keyword` to be the provided `value`.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/InProgressBuilder.html#add-long-java.lang.String-java.util.Date-">add(long, String, Date)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/InProgressBuilder.html" title="class in org.mozilla.mentat">InProgressBuilder</a></dt>
|
||||
<dd>
|
||||
<div class="block">Asserts the value of attribute `keyword` to be the provided `value`.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/InProgressBuilder.html#add-long-java.lang.String-java.lang.String-">add(long, String, String)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/InProgressBuilder.html" title="class in org.mozilla.mentat">InProgressBuilder</a></dt>
|
||||
<dd>
|
||||
<div class="block">Asserts the value of attribute `keyword` to be the provided `value`.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/InProgressBuilder.html#add-long-java.lang.String-java.util.UUID-">add(long, String, UUID)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/InProgressBuilder.html" title="class in org.mozilla.mentat">InProgressBuilder</a></dt>
|
||||
<dd>
|
||||
<div class="block">Asserts the value of attribute `keyword` to be the provided `value`.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/EntityBuilder.html#addKeyword-java.lang.String-java.lang.String-">addKeyword(String, String)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/EntityBuilder.html" title="class in org.mozilla.mentat">EntityBuilder</a></dt>
|
||||
<dd>
|
||||
<div class="block">Asserts the value of attribute `keyword` to be the provided `value`.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/InProgressBuilder.html#addKeyword-long-java.lang.String-java.lang.String-">addKeyword(long, String, String)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/InProgressBuilder.html" title="class in org.mozilla.mentat">InProgressBuilder</a></dt>
|
||||
<dd>
|
||||
<div class="block">Asserts the value of attribute `keyword` to be the provided `value`.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/EntityBuilder.html#addRef-java.lang.String-long-">addRef(String, long)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/EntityBuilder.html" title="class in org.mozilla.mentat">EntityBuilder</a></dt>
|
||||
<dd>
|
||||
<div class="block">Asserts the value of attribute `keyword` to be the provided `value`.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/InProgressBuilder.html#addRef-long-java.lang.String-long-">addRef(long, String, long)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/InProgressBuilder.html" title="class in org.mozilla.mentat">InProgressBuilder</a></dt>
|
||||
<dd>
|
||||
<div class="block">Asserts the value of attribute `keyword` to be the provided `value`.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/ColResultIterator.html#advanceIterator--">advanceIterator()</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/ColResultIterator.html" title="class in org.mozilla.mentat">ColResultIterator</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/RelResultIterator.html#advanceIterator--">advanceIterator()</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/RelResultIterator.html" title="class in org.mozilla.mentat">RelResultIterator</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/TupleResult.html#asBool-java.lang.Integer-">asBool(Integer)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/TupleResult.html" title="class in org.mozilla.mentat">TupleResult</a></dt>
|
||||
<dd>
|
||||
<div class="block">Return the <a href="https://developer.android.com/reference/java/lang/Boolean.html?is-external=true" title="class or interface in java.lang"><code>Boolean</code></a> at the specified index.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/TypedValue.html#asBoolean--">asBoolean()</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/TypedValue.html" title="class in org.mozilla.mentat">TypedValue</a></dt>
|
||||
<dd>
|
||||
<div class="block">This value as a <a href="https://developer.android.com/reference/java/lang/Boolean.html?is-external=true" title="class or interface in java.lang"><code>Boolean</code></a>.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/TupleResult.html#asDate-java.lang.Integer-">asDate(Integer)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/TupleResult.html" title="class in org.mozilla.mentat">TupleResult</a></dt>
|
||||
<dd>
|
||||
<div class="block">Return the <a href="https://developer.android.com/reference/java/util/Date.html?is-external=true" title="class or interface in java.util"><code>Date</code></a> at the specified index.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/TypedValue.html#asDate--">asDate()</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/TypedValue.html" title="class in org.mozilla.mentat">TypedValue</a></dt>
|
||||
<dd>
|
||||
<div class="block">This value as a <a href="https://developer.android.com/reference/java/util/Date.html?is-external=true" title="class or interface in java.util"><code>Date</code></a>.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/TupleResult.html#asDouble-java.lang.Integer-">asDouble(Integer)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/TupleResult.html" title="class in org.mozilla.mentat">TupleResult</a></dt>
|
||||
<dd>
|
||||
<div class="block">Return the <a href="https://developer.android.com/reference/java/lang/Double.html?is-external=true" title="class or interface in java.lang"><code>Double</code></a> at the specified index.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/TypedValue.html#asDouble--">asDouble()</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/TypedValue.html" title="class in org.mozilla.mentat">TypedValue</a></dt>
|
||||
<dd>
|
||||
<div class="block">This value as a <a href="https://developer.android.com/reference/java/lang/Double.html?is-external=true" title="class or interface in java.lang"><code>Double</code></a>.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/TupleResult.html#asEntid-java.lang.Integer-">asEntid(Integer)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/TupleResult.html" title="class in org.mozilla.mentat">TupleResult</a></dt>
|
||||
<dd>
|
||||
<div class="block">Return the Entid at the specified index.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/TypedValue.html#asEntid--">asEntid()</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/TypedValue.html" title="class in org.mozilla.mentat">TypedValue</a></dt>
|
||||
<dd>
|
||||
<div class="block">This value as a Entid.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/TupleResult.html#asKeyword-java.lang.Integer-">asKeyword(Integer)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/TupleResult.html" title="class in org.mozilla.mentat">TupleResult</a></dt>
|
||||
<dd>
|
||||
<div class="block">Return the keyword <a href="https://developer.android.com/reference/java/lang/String.html?is-external=true" title="class or interface in java.lang"><code>String</code></a> at the specified index.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/TypedValue.html#asKeyword--">asKeyword()</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/TypedValue.html" title="class in org.mozilla.mentat">TypedValue</a></dt>
|
||||
<dd>
|
||||
<div class="block">This value as a keyword <a href="https://developer.android.com/reference/java/lang/String.html?is-external=true" title="class or interface in java.lang"><code>String</code></a>.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/TupleResult.html#asLong-java.lang.Integer-">asLong(Integer)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/TupleResult.html" title="class in org.mozilla.mentat">TupleResult</a></dt>
|
||||
<dd>
|
||||
<div class="block">Return the <a href="https://developer.android.com/reference/java/lang/Long.html?is-external=true" title="class or interface in java.lang"><code>Long</code></a> at the specified index.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/TypedValue.html#asLong--">asLong()</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/TypedValue.html" title="class in org.mozilla.mentat">TypedValue</a></dt>
|
||||
<dd>
|
||||
<div class="block">This value as a <a href="https://developer.android.com/reference/java/lang/Long.html?is-external=true" title="class or interface in java.lang"><code>Long</code></a>.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/TupleResult.html#asString-java.lang.Integer-">asString(Integer)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/TupleResult.html" title="class in org.mozilla.mentat">TupleResult</a></dt>
|
||||
<dd>
|
||||
<div class="block">Return the <a href="https://developer.android.com/reference/java/lang/String.html?is-external=true" title="class or interface in java.lang"><code>String</code></a> at the specified index.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/TypedValue.html#asString--">asString()</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/TypedValue.html" title="class in org.mozilla.mentat">TypedValue</a></dt>
|
||||
<dd>
|
||||
<div class="block">This value as a <a href="https://developer.android.com/reference/java/lang/String.html?is-external=true" title="class or interface in java.lang"><code>String</code></a>.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/TupleResult.html#asUUID-java.lang.Integer-">asUUID(Integer)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/TupleResult.html" title="class in org.mozilla.mentat">TupleResult</a></dt>
|
||||
<dd>
|
||||
<div class="block">Return the <a href="https://developer.android.com/reference/java/util/UUID.html?is-external=true" title="class or interface in java.util"><code>UUID</code></a> at the specified index.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/TypedValue.html#asUUID--">asUUID()</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/TypedValue.html" title="class in org.mozilla.mentat">TypedValue</a></dt>
|
||||
<dd>
|
||||
<div class="block">This value as a <a href="https://developer.android.com/reference/java/util/UUID.html?is-external=true" title="class or interface in java.util"><code>UUID</code></a>.</div>
|
||||
</dd>
|
||||
</dl>
|
||||
<a href="index-1.html">A</a> <a href="index-2.html">B</a> <a href="index-3.html">C</a> <a href="index-4.html">D</a> <a href="index-5.html">E</a> <a href="index-6.html">F</a> <a href="index-7.html">G</a> <a href="index-8.html">H</a> <a href="index-9.html">I</a> <a href="index-10.html">J</a> <a href="index-11.html">L</a> <a href="index-12.html">M</a> <a href="index-13.html">O</a> <a href="index-14.html">Q</a> <a href="index-15.html">R</a> <a href="index-16.html">S</a> <a href="index-17.html">T</a> <a href="index-18.html">U</a> <a href="index-19.html">V</a> </div>
|
||||
<!-- ======= START OF BOTTOM NAVBAR ====== -->
|
||||
<div class="bottomNav"><a name="navbar.bottom">
|
||||
<!-- -->
|
||||
</a>
|
||||
<div class="skipNav"><a href="#skip.navbar.bottom" title="Skip navigation links">Skip navigation links</a></div>
|
||||
<a name="navbar.bottom.firstrow">
|
||||
<!-- -->
|
||||
</a>
|
||||
<ul class="navList" title="Navigation">
|
||||
<li><a href="../org/mozilla/mentat/package-summary.html">Package</a></li>
|
||||
<li>Class</li>
|
||||
<li><a href="../overview-tree.html">Tree</a></li>
|
||||
<li><a href="../deprecated-list.html">Deprecated</a></li>
|
||||
<li class="navBarCell1Rev">Index</li>
|
||||
<li><a href="../help-doc.html">Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="subNav">
|
||||
<ul class="navList">
|
||||
<li>Prev Letter</li>
|
||||
<li><a href="index-2.html">Next Letter</a></li>
|
||||
</ul>
|
||||
<ul class="navList">
|
||||
<li><a href="../index.html?index-files/index-1.html" target="_top">Frames</a></li>
|
||||
<li><a href="index-1.html" target="_top">No Frames</a></li>
|
||||
</ul>
|
||||
<ul class="navList" id="allclasses_navbar_bottom">
|
||||
<li><a href="../allclasses-noframe.html">All Classes</a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<script type="text/javascript"><!--
|
||||
allClassesLink = document.getElementById("allclasses_navbar_bottom");
|
||||
if(window==top) {
|
||||
allClassesLink.style.display = "block";
|
||||
}
|
||||
else {
|
||||
allClassesLink.style.display = "none";
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
</div>
|
||||
<a name="skip.navbar.bottom">
|
||||
<!-- -->
|
||||
</a></div>
|
||||
<!-- ======== END OF BOTTOM NAVBAR ======= -->
|
||||
</body>
|
||||
</html>
|
154
docs/apis/0.7/java/index-files/index-10.html
Normal file
154
docs/apis/0.7/java/index-files/index-10.html
Normal file
|
@ -0,0 +1,154 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<!-- NewPage -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Generated by javadoc (1.8.0_152-release) on Thu Jun 28 11:01:15 BST 2018 -->
|
||||
<title>J-Index</title>
|
||||
<meta name="date" content="2018-06-28">
|
||||
<link rel="stylesheet" type="text/css" href="../stylesheet.css" title="Style">
|
||||
<script type="text/javascript" src="../script.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript"><!--
|
||||
try {
|
||||
if (location.href.indexOf('is-external=true') == -1) {
|
||||
parent.document.title="J-Index";
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
<noscript>
|
||||
<div>JavaScript is disabled on your browser.</div>
|
||||
</noscript>
|
||||
<!-- ========= START OF TOP NAVBAR ======= -->
|
||||
<div class="topNav"><a name="navbar.top">
|
||||
<!-- -->
|
||||
</a>
|
||||
<div class="skipNav"><a href="#skip.navbar.top" title="Skip navigation links">Skip navigation links</a></div>
|
||||
<a name="navbar.top.firstrow">
|
||||
<!-- -->
|
||||
</a>
|
||||
<ul class="navList" title="Navigation">
|
||||
<li><a href="../org/mozilla/mentat/package-summary.html">Package</a></li>
|
||||
<li>Class</li>
|
||||
<li><a href="../overview-tree.html">Tree</a></li>
|
||||
<li><a href="../deprecated-list.html">Deprecated</a></li>
|
||||
<li class="navBarCell1Rev">Index</li>
|
||||
<li><a href="../help-doc.html">Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="subNav">
|
||||
<ul class="navList">
|
||||
<li><a href="index-9.html">Prev Letter</a></li>
|
||||
<li><a href="index-11.html">Next Letter</a></li>
|
||||
</ul>
|
||||
<ul class="navList">
|
||||
<li><a href="../index.html?index-files/index-10.html" target="_top">Frames</a></li>
|
||||
<li><a href="index-10.html" target="_top">No Frames</a></li>
|
||||
</ul>
|
||||
<ul class="navList" id="allclasses_navbar_top">
|
||||
<li><a href="../allclasses-noframe.html">All Classes</a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<script type="text/javascript"><!--
|
||||
allClassesLink = document.getElementById("allclasses_navbar_top");
|
||||
if(window==top) {
|
||||
allClassesLink.style.display = "block";
|
||||
}
|
||||
else {
|
||||
allClassesLink.style.display = "none";
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
</div>
|
||||
<a name="skip.navbar.top">
|
||||
<!-- -->
|
||||
</a></div>
|
||||
<!-- ========= END OF TOP NAVBAR ========= -->
|
||||
<div class="contentContainer"><a href="index-1.html">A</a> <a href="index-2.html">B</a> <a href="index-3.html">C</a> <a href="index-4.html">D</a> <a href="index-5.html">E</a> <a href="index-6.html">F</a> <a href="index-7.html">G</a> <a href="index-8.html">H</a> <a href="index-9.html">I</a> <a href="index-10.html">J</a> <a href="index-11.html">L</a> <a href="index-12.html">M</a> <a href="index-13.html">O</a> <a href="index-14.html">Q</a> <a href="index-15.html">R</a> <a href="index-16.html">S</a> <a href="index-17.html">T</a> <a href="index-18.html">U</a> <a href="index-19.html">V</a> <a name="I:J">
|
||||
<!-- -->
|
||||
</a>
|
||||
<h2 class="title">J</h2>
|
||||
<dl>
|
||||
<dt><a href="../org/mozilla/mentat/JNA.html" title="interface in org.mozilla.mentat"><span class="typeNameLink">JNA</span></a> - Interface in <a href="../org/mozilla/mentat/package-summary.html">org.mozilla.mentat</a></dt>
|
||||
<dd>
|
||||
<div class="block">JNA interface for FFI to Mentat's Rust library
|
||||
Each function definition here link directly to a function in Mentat's FFI crate.</div>
|
||||
</dd>
|
||||
<dt><a href="../org/mozilla/mentat/JNA.EntityBuilder.html" title="class in org.mozilla.mentat"><span class="typeNameLink">JNA.EntityBuilder</span></a> - Class in <a href="../org/mozilla/mentat/package-summary.html">org.mozilla.mentat</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><a href="../org/mozilla/mentat/JNA.InProgress.html" title="class in org.mozilla.mentat"><span class="typeNameLink">JNA.InProgress</span></a> - Class in <a href="../org/mozilla/mentat/package-summary.html">org.mozilla.mentat</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><a href="../org/mozilla/mentat/JNA.InProgressBuilder.html" title="class in org.mozilla.mentat"><span class="typeNameLink">JNA.InProgressBuilder</span></a> - Class in <a href="../org/mozilla/mentat/package-summary.html">org.mozilla.mentat</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><a href="../org/mozilla/mentat/JNA.QueryBuilder.html" title="class in org.mozilla.mentat"><span class="typeNameLink">JNA.QueryBuilder</span></a> - Class in <a href="../org/mozilla/mentat/package-summary.html">org.mozilla.mentat</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><a href="../org/mozilla/mentat/JNA.RelResult.html" title="class in org.mozilla.mentat"><span class="typeNameLink">JNA.RelResult</span></a> - Class in <a href="../org/mozilla/mentat/package-summary.html">org.mozilla.mentat</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><a href="../org/mozilla/mentat/JNA.RelResultIter.html" title="class in org.mozilla.mentat"><span class="typeNameLink">JNA.RelResultIter</span></a> - Class in <a href="../org/mozilla/mentat/package-summary.html">org.mozilla.mentat</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><a href="../org/mozilla/mentat/JNA.Store.html" title="class in org.mozilla.mentat"><span class="typeNameLink">JNA.Store</span></a> - Class in <a href="../org/mozilla/mentat/package-summary.html">org.mozilla.mentat</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><a href="../org/mozilla/mentat/JNA.TxReport.html" title="class in org.mozilla.mentat"><span class="typeNameLink">JNA.TxReport</span></a> - Class in <a href="../org/mozilla/mentat/package-summary.html">org.mozilla.mentat</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><a href="../org/mozilla/mentat/JNA.TypedValue.html" title="class in org.mozilla.mentat"><span class="typeNameLink">JNA.TypedValue</span></a> - Class in <a href="../org/mozilla/mentat/package-summary.html">org.mozilla.mentat</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><a href="../org/mozilla/mentat/JNA.TypedValueList.html" title="class in org.mozilla.mentat"><span class="typeNameLink">JNA.TypedValueList</span></a> - Class in <a href="../org/mozilla/mentat/package-summary.html">org.mozilla.mentat</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><a href="../org/mozilla/mentat/JNA.TypedValueListIter.html" title="class in org.mozilla.mentat"><span class="typeNameLink">JNA.TypedValueListIter</span></a> - Class in <a href="../org/mozilla/mentat/package-summary.html">org.mozilla.mentat</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/JNA.html#JNA_LIBRARY_NAME">JNA_LIBRARY_NAME</a></span> - Static variable in interface org.mozilla.mentat.<a href="../org/mozilla/mentat/JNA.html" title="interface in org.mozilla.mentat">JNA</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/JNA.html#JNA_NATIVE_LIB">JNA_NATIVE_LIB</a></span> - Static variable in interface org.mozilla.mentat.<a href="../org/mozilla/mentat/JNA.html" title="interface in org.mozilla.mentat">JNA</a></dt>
|
||||
<dd> </dd>
|
||||
</dl>
|
||||
<a href="index-1.html">A</a> <a href="index-2.html">B</a> <a href="index-3.html">C</a> <a href="index-4.html">D</a> <a href="index-5.html">E</a> <a href="index-6.html">F</a> <a href="index-7.html">G</a> <a href="index-8.html">H</a> <a href="index-9.html">I</a> <a href="index-10.html">J</a> <a href="index-11.html">L</a> <a href="index-12.html">M</a> <a href="index-13.html">O</a> <a href="index-14.html">Q</a> <a href="index-15.html">R</a> <a href="index-16.html">S</a> <a href="index-17.html">T</a> <a href="index-18.html">U</a> <a href="index-19.html">V</a> </div>
|
||||
<!-- ======= START OF BOTTOM NAVBAR ====== -->
|
||||
<div class="bottomNav"><a name="navbar.bottom">
|
||||
<!-- -->
|
||||
</a>
|
||||
<div class="skipNav"><a href="#skip.navbar.bottom" title="Skip navigation links">Skip navigation links</a></div>
|
||||
<a name="navbar.bottom.firstrow">
|
||||
<!-- -->
|
||||
</a>
|
||||
<ul class="navList" title="Navigation">
|
||||
<li><a href="../org/mozilla/mentat/package-summary.html">Package</a></li>
|
||||
<li>Class</li>
|
||||
<li><a href="../overview-tree.html">Tree</a></li>
|
||||
<li><a href="../deprecated-list.html">Deprecated</a></li>
|
||||
<li class="navBarCell1Rev">Index</li>
|
||||
<li><a href="../help-doc.html">Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="subNav">
|
||||
<ul class="navList">
|
||||
<li><a href="index-9.html">Prev Letter</a></li>
|
||||
<li><a href="index-11.html">Next Letter</a></li>
|
||||
</ul>
|
||||
<ul class="navList">
|
||||
<li><a href="../index.html?index-files/index-10.html" target="_top">Frames</a></li>
|
||||
<li><a href="index-10.html" target="_top">No Frames</a></li>
|
||||
</ul>
|
||||
<ul class="navList" id="allclasses_navbar_bottom">
|
||||
<li><a href="../allclasses-noframe.html">All Classes</a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<script type="text/javascript"><!--
|
||||
allClassesLink = document.getElementById("allclasses_navbar_bottom");
|
||||
if(window==top) {
|
||||
allClassesLink.style.display = "block";
|
||||
}
|
||||
else {
|
||||
allClassesLink.style.display = "none";
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
</div>
|
||||
<a name="skip.navbar.bottom">
|
||||
<!-- -->
|
||||
</a></div>
|
||||
<!-- ======== END OF BOTTOM NAVBAR ======= -->
|
||||
</body>
|
||||
</html>
|
125
docs/apis/0.7/java/index-files/index-11.html
Normal file
125
docs/apis/0.7/java/index-files/index-11.html
Normal file
|
@ -0,0 +1,125 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<!-- NewPage -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Generated by javadoc (1.8.0_152-release) on Thu Jun 28 11:01:15 BST 2018 -->
|
||||
<title>L-Index</title>
|
||||
<meta name="date" content="2018-06-28">
|
||||
<link rel="stylesheet" type="text/css" href="../stylesheet.css" title="Style">
|
||||
<script type="text/javascript" src="../script.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript"><!--
|
||||
try {
|
||||
if (location.href.indexOf('is-external=true') == -1) {
|
||||
parent.document.title="L-Index";
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
<noscript>
|
||||
<div>JavaScript is disabled on your browser.</div>
|
||||
</noscript>
|
||||
<!-- ========= START OF TOP NAVBAR ======= -->
|
||||
<div class="topNav"><a name="navbar.top">
|
||||
<!-- -->
|
||||
</a>
|
||||
<div class="skipNav"><a href="#skip.navbar.top" title="Skip navigation links">Skip navigation links</a></div>
|
||||
<a name="navbar.top.firstrow">
|
||||
<!-- -->
|
||||
</a>
|
||||
<ul class="navList" title="Navigation">
|
||||
<li><a href="../org/mozilla/mentat/package-summary.html">Package</a></li>
|
||||
<li>Class</li>
|
||||
<li><a href="../overview-tree.html">Tree</a></li>
|
||||
<li><a href="../deprecated-list.html">Deprecated</a></li>
|
||||
<li class="navBarCell1Rev">Index</li>
|
||||
<li><a href="../help-doc.html">Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="subNav">
|
||||
<ul class="navList">
|
||||
<li><a href="index-10.html">Prev Letter</a></li>
|
||||
<li><a href="index-12.html">Next Letter</a></li>
|
||||
</ul>
|
||||
<ul class="navList">
|
||||
<li><a href="../index.html?index-files/index-11.html" target="_top">Frames</a></li>
|
||||
<li><a href="index-11.html" target="_top">No Frames</a></li>
|
||||
</ul>
|
||||
<ul class="navList" id="allclasses_navbar_top">
|
||||
<li><a href="../allclasses-noframe.html">All Classes</a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<script type="text/javascript"><!--
|
||||
allClassesLink = document.getElementById("allclasses_navbar_top");
|
||||
if(window==top) {
|
||||
allClassesLink.style.display = "block";
|
||||
}
|
||||
else {
|
||||
allClassesLink.style.display = "none";
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
</div>
|
||||
<a name="skip.navbar.top">
|
||||
<!-- -->
|
||||
</a></div>
|
||||
<!-- ========= END OF TOP NAVBAR ========= -->
|
||||
<div class="contentContainer"><a href="index-1.html">A</a> <a href="index-2.html">B</a> <a href="index-3.html">C</a> <a href="index-4.html">D</a> <a href="index-5.html">E</a> <a href="index-6.html">F</a> <a href="index-7.html">G</a> <a href="index-8.html">H</a> <a href="index-9.html">I</a> <a href="index-10.html">J</a> <a href="index-11.html">L</a> <a href="index-12.html">M</a> <a href="index-13.html">O</a> <a href="index-14.html">Q</a> <a href="index-15.html">R</a> <a href="index-16.html">S</a> <a href="index-17.html">T</a> <a href="index-18.html">U</a> <a href="index-19.html">V</a> <a name="I:L">
|
||||
<!-- -->
|
||||
</a>
|
||||
<h2 class="title">L</h2>
|
||||
<dl>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/TxChangeList.html#len">len</a></span> - Variable in class org.mozilla.mentat.<a href="../org/mozilla/mentat/TxChangeList.html" title="class in org.mozilla.mentat">TxChangeList</a></dt>
|
||||
<dd> </dd>
|
||||
</dl>
|
||||
<a href="index-1.html">A</a> <a href="index-2.html">B</a> <a href="index-3.html">C</a> <a href="index-4.html">D</a> <a href="index-5.html">E</a> <a href="index-6.html">F</a> <a href="index-7.html">G</a> <a href="index-8.html">H</a> <a href="index-9.html">I</a> <a href="index-10.html">J</a> <a href="index-11.html">L</a> <a href="index-12.html">M</a> <a href="index-13.html">O</a> <a href="index-14.html">Q</a> <a href="index-15.html">R</a> <a href="index-16.html">S</a> <a href="index-17.html">T</a> <a href="index-18.html">U</a> <a href="index-19.html">V</a> </div>
|
||||
<!-- ======= START OF BOTTOM NAVBAR ====== -->
|
||||
<div class="bottomNav"><a name="navbar.bottom">
|
||||
<!-- -->
|
||||
</a>
|
||||
<div class="skipNav"><a href="#skip.navbar.bottom" title="Skip navigation links">Skip navigation links</a></div>
|
||||
<a name="navbar.bottom.firstrow">
|
||||
<!-- -->
|
||||
</a>
|
||||
<ul class="navList" title="Navigation">
|
||||
<li><a href="../org/mozilla/mentat/package-summary.html">Package</a></li>
|
||||
<li>Class</li>
|
||||
<li><a href="../overview-tree.html">Tree</a></li>
|
||||
<li><a href="../deprecated-list.html">Deprecated</a></li>
|
||||
<li class="navBarCell1Rev">Index</li>
|
||||
<li><a href="../help-doc.html">Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="subNav">
|
||||
<ul class="navList">
|
||||
<li><a href="index-10.html">Prev Letter</a></li>
|
||||
<li><a href="index-12.html">Next Letter</a></li>
|
||||
</ul>
|
||||
<ul class="navList">
|
||||
<li><a href="../index.html?index-files/index-11.html" target="_top">Frames</a></li>
|
||||
<li><a href="index-11.html" target="_top">No Frames</a></li>
|
||||
</ul>
|
||||
<ul class="navList" id="allclasses_navbar_bottom">
|
||||
<li><a href="../allclasses-noframe.html">All Classes</a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<script type="text/javascript"><!--
|
||||
allClassesLink = document.getElementById("allclasses_navbar_bottom");
|
||||
if(window==top) {
|
||||
allClassesLink.style.display = "block";
|
||||
}
|
||||
else {
|
||||
allClassesLink.style.display = "none";
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
</div>
|
||||
<a name="skip.navbar.bottom">
|
||||
<!-- -->
|
||||
</a></div>
|
||||
<!-- ======== END OF BOTTOM NAVBAR ======= -->
|
||||
</body>
|
||||
</html>
|
144
docs/apis/0.7/java/index-files/index-12.html
Normal file
144
docs/apis/0.7/java/index-files/index-12.html
Normal file
|
@ -0,0 +1,144 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<!-- NewPage -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Generated by javadoc (1.8.0_152-release) on Thu Jun 28 11:01:15 BST 2018 -->
|
||||
<title>M-Index</title>
|
||||
<meta name="date" content="2018-06-28">
|
||||
<link rel="stylesheet" type="text/css" href="../stylesheet.css" title="Style">
|
||||
<script type="text/javascript" src="../script.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript"><!--
|
||||
try {
|
||||
if (location.href.indexOf('is-external=true') == -1) {
|
||||
parent.document.title="M-Index";
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
<noscript>
|
||||
<div>JavaScript is disabled on your browser.</div>
|
||||
</noscript>
|
||||
<!-- ========= START OF TOP NAVBAR ======= -->
|
||||
<div class="topNav"><a name="navbar.top">
|
||||
<!-- -->
|
||||
</a>
|
||||
<div class="skipNav"><a href="#skip.navbar.top" title="Skip navigation links">Skip navigation links</a></div>
|
||||
<a name="navbar.top.firstrow">
|
||||
<!-- -->
|
||||
</a>
|
||||
<ul class="navList" title="Navigation">
|
||||
<li><a href="../org/mozilla/mentat/package-summary.html">Package</a></li>
|
||||
<li>Class</li>
|
||||
<li><a href="../overview-tree.html">Tree</a></li>
|
||||
<li><a href="../deprecated-list.html">Deprecated</a></li>
|
||||
<li class="navBarCell1Rev">Index</li>
|
||||
<li><a href="../help-doc.html">Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="subNav">
|
||||
<ul class="navList">
|
||||
<li><a href="index-11.html">Prev Letter</a></li>
|
||||
<li><a href="index-13.html">Next Letter</a></li>
|
||||
</ul>
|
||||
<ul class="navList">
|
||||
<li><a href="../index.html?index-files/index-12.html" target="_top">Frames</a></li>
|
||||
<li><a href="index-12.html" target="_top">No Frames</a></li>
|
||||
</ul>
|
||||
<ul class="navList" id="allclasses_navbar_top">
|
||||
<li><a href="../allclasses-noframe.html">All Classes</a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<script type="text/javascript"><!--
|
||||
allClassesLink = document.getElementById("allclasses_navbar_top");
|
||||
if(window==top) {
|
||||
allClassesLink.style.display = "block";
|
||||
}
|
||||
else {
|
||||
allClassesLink.style.display = "none";
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
</div>
|
||||
<a name="skip.navbar.top">
|
||||
<!-- -->
|
||||
</a></div>
|
||||
<!-- ========= END OF TOP NAVBAR ========= -->
|
||||
<div class="contentContainer"><a href="index-1.html">A</a> <a href="index-2.html">B</a> <a href="index-3.html">C</a> <a href="index-4.html">D</a> <a href="index-5.html">E</a> <a href="index-6.html">F</a> <a href="index-7.html">G</a> <a href="index-8.html">H</a> <a href="index-9.html">I</a> <a href="index-10.html">J</a> <a href="index-11.html">L</a> <a href="index-12.html">M</a> <a href="index-13.html">O</a> <a href="index-14.html">Q</a> <a href="index-15.html">R</a> <a href="index-16.html">S</a> <a href="index-17.html">T</a> <a href="index-18.html">U</a> <a href="index-19.html">V</a> <a name="I:M">
|
||||
<!-- -->
|
||||
</a>
|
||||
<h2 class="title">M</h2>
|
||||
<dl>
|
||||
<dt><a href="../org/mozilla/mentat/Mentat.html" title="class in org.mozilla.mentat"><span class="typeNameLink">Mentat</span></a> - Class in <a href="../org/mozilla/mentat/package-summary.html">org.mozilla.mentat</a></dt>
|
||||
<dd>
|
||||
<div class="block">The primary class for accessing Mentat's API.<br/>
|
||||
This class provides all of the basic API that can be found in Mentat's Store struct.<br/>
|
||||
The raw pointer it holds is a pointer to a Store.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/Mentat.html#Mentat-java.lang.String-">Mentat(String)</a></span> - Constructor for class org.mozilla.mentat.<a href="../org/mozilla/mentat/Mentat.html" title="class in org.mozilla.mentat">Mentat</a></dt>
|
||||
<dd>
|
||||
<div class="block">Open a connection to a Store in a given location.<br/>
|
||||
If the store does not already exist, one will be created.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/Mentat.html#Mentat--">Mentat()</a></span> - Constructor for class org.mozilla.mentat.<a href="../org/mozilla/mentat/Mentat.html" title="class in org.mozilla.mentat">Mentat</a></dt>
|
||||
<dd>
|
||||
<div class="block">Open a connection to an in-memory Store.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/Mentat.html#Mentat-org.mozilla.mentat.JNA.Store-">Mentat(JNA.Store)</a></span> - Constructor for class org.mozilla.mentat.<a href="../org/mozilla/mentat/Mentat.html" title="class in org.mozilla.mentat">Mentat</a></dt>
|
||||
<dd>
|
||||
<div class="block">Create a new Mentat with the provided pointer to a Mentat Store</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/RustError.html#message">message</a></span> - Variable in class org.mozilla.mentat.<a href="../org/mozilla/mentat/RustError.html" title="class in org.mozilla.mentat">RustError</a></dt>
|
||||
<dd> </dd>
|
||||
</dl>
|
||||
<a href="index-1.html">A</a> <a href="index-2.html">B</a> <a href="index-3.html">C</a> <a href="index-4.html">D</a> <a href="index-5.html">E</a> <a href="index-6.html">F</a> <a href="index-7.html">G</a> <a href="index-8.html">H</a> <a href="index-9.html">I</a> <a href="index-10.html">J</a> <a href="index-11.html">L</a> <a href="index-12.html">M</a> <a href="index-13.html">O</a> <a href="index-14.html">Q</a> <a href="index-15.html">R</a> <a href="index-16.html">S</a> <a href="index-17.html">T</a> <a href="index-18.html">U</a> <a href="index-19.html">V</a> </div>
|
||||
<!-- ======= START OF BOTTOM NAVBAR ====== -->
|
||||
<div class="bottomNav"><a name="navbar.bottom">
|
||||
<!-- -->
|
||||
</a>
|
||||
<div class="skipNav"><a href="#skip.navbar.bottom" title="Skip navigation links">Skip navigation links</a></div>
|
||||
<a name="navbar.bottom.firstrow">
|
||||
<!-- -->
|
||||
</a>
|
||||
<ul class="navList" title="Navigation">
|
||||
<li><a href="../org/mozilla/mentat/package-summary.html">Package</a></li>
|
||||
<li>Class</li>
|
||||
<li><a href="../overview-tree.html">Tree</a></li>
|
||||
<li><a href="../deprecated-list.html">Deprecated</a></li>
|
||||
<li class="navBarCell1Rev">Index</li>
|
||||
<li><a href="../help-doc.html">Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="subNav">
|
||||
<ul class="navList">
|
||||
<li><a href="index-11.html">Prev Letter</a></li>
|
||||
<li><a href="index-13.html">Next Letter</a></li>
|
||||
</ul>
|
||||
<ul class="navList">
|
||||
<li><a href="../index.html?index-files/index-12.html" target="_top">Frames</a></li>
|
||||
<li><a href="index-12.html" target="_top">No Frames</a></li>
|
||||
</ul>
|
||||
<ul class="navList" id="allclasses_navbar_bottom">
|
||||
<li><a href="../allclasses-noframe.html">All Classes</a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<script type="text/javascript"><!--
|
||||
allClassesLink = document.getElementById("allclasses_navbar_bottom");
|
||||
if(window==top) {
|
||||
allClassesLink.style.display = "block";
|
||||
}
|
||||
else {
|
||||
allClassesLink.style.display = "none";
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
</div>
|
||||
<a name="skip.navbar.bottom">
|
||||
<!-- -->
|
||||
</a></div>
|
||||
<!-- ======== END OF BOTTOM NAVBAR ======= -->
|
||||
</body>
|
||||
</html>
|
125
docs/apis/0.7/java/index-files/index-13.html
Normal file
125
docs/apis/0.7/java/index-files/index-13.html
Normal file
|
@ -0,0 +1,125 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<!-- NewPage -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Generated by javadoc (1.8.0_152-release) on Thu Jun 28 11:01:15 BST 2018 -->
|
||||
<title>O-Index</title>
|
||||
<meta name="date" content="2018-06-28">
|
||||
<link rel="stylesheet" type="text/css" href="../stylesheet.css" title="Style">
|
||||
<script type="text/javascript" src="../script.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript"><!--
|
||||
try {
|
||||
if (location.href.indexOf('is-external=true') == -1) {
|
||||
parent.document.title="O-Index";
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
<noscript>
|
||||
<div>JavaScript is disabled on your browser.</div>
|
||||
</noscript>
|
||||
<!-- ========= START OF TOP NAVBAR ======= -->
|
||||
<div class="topNav"><a name="navbar.top">
|
||||
<!-- -->
|
||||
</a>
|
||||
<div class="skipNav"><a href="#skip.navbar.top" title="Skip navigation links">Skip navigation links</a></div>
|
||||
<a name="navbar.top.firstrow">
|
||||
<!-- -->
|
||||
</a>
|
||||
<ul class="navList" title="Navigation">
|
||||
<li><a href="../org/mozilla/mentat/package-summary.html">Package</a></li>
|
||||
<li>Class</li>
|
||||
<li><a href="../overview-tree.html">Tree</a></li>
|
||||
<li><a href="../deprecated-list.html">Deprecated</a></li>
|
||||
<li class="navBarCell1Rev">Index</li>
|
||||
<li><a href="../help-doc.html">Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="subNav">
|
||||
<ul class="navList">
|
||||
<li><a href="index-12.html">Prev Letter</a></li>
|
||||
<li><a href="index-14.html">Next Letter</a></li>
|
||||
</ul>
|
||||
<ul class="navList">
|
||||
<li><a href="../index.html?index-files/index-13.html" target="_top">Frames</a></li>
|
||||
<li><a href="index-13.html" target="_top">No Frames</a></li>
|
||||
</ul>
|
||||
<ul class="navList" id="allclasses_navbar_top">
|
||||
<li><a href="../allclasses-noframe.html">All Classes</a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<script type="text/javascript"><!--
|
||||
allClassesLink = document.getElementById("allclasses_navbar_top");
|
||||
if(window==top) {
|
||||
allClassesLink.style.display = "block";
|
||||
}
|
||||
else {
|
||||
allClassesLink.style.display = "none";
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
</div>
|
||||
<a name="skip.navbar.top">
|
||||
<!-- -->
|
||||
</a></div>
|
||||
<!-- ========= END OF TOP NAVBAR ========= -->
|
||||
<div class="contentContainer"><a href="index-1.html">A</a> <a href="index-2.html">B</a> <a href="index-3.html">C</a> <a href="index-4.html">D</a> <a href="index-5.html">E</a> <a href="index-6.html">F</a> <a href="index-7.html">G</a> <a href="index-8.html">H</a> <a href="index-9.html">I</a> <a href="index-10.html">J</a> <a href="index-11.html">L</a> <a href="index-12.html">M</a> <a href="index-13.html">O</a> <a href="index-14.html">Q</a> <a href="index-15.html">R</a> <a href="index-16.html">S</a> <a href="index-17.html">T</a> <a href="index-18.html">U</a> <a href="index-19.html">V</a> <a name="I:O">
|
||||
<!-- -->
|
||||
</a>
|
||||
<h2 class="title">O</h2>
|
||||
<dl>
|
||||
<dt><a href="../org/mozilla/mentat/package-summary.html">org.mozilla.mentat</a> - package org.mozilla.mentat</dt>
|
||||
<dd> </dd>
|
||||
</dl>
|
||||
<a href="index-1.html">A</a> <a href="index-2.html">B</a> <a href="index-3.html">C</a> <a href="index-4.html">D</a> <a href="index-5.html">E</a> <a href="index-6.html">F</a> <a href="index-7.html">G</a> <a href="index-8.html">H</a> <a href="index-9.html">I</a> <a href="index-10.html">J</a> <a href="index-11.html">L</a> <a href="index-12.html">M</a> <a href="index-13.html">O</a> <a href="index-14.html">Q</a> <a href="index-15.html">R</a> <a href="index-16.html">S</a> <a href="index-17.html">T</a> <a href="index-18.html">U</a> <a href="index-19.html">V</a> </div>
|
||||
<!-- ======= START OF BOTTOM NAVBAR ====== -->
|
||||
<div class="bottomNav"><a name="navbar.bottom">
|
||||
<!-- -->
|
||||
</a>
|
||||
<div class="skipNav"><a href="#skip.navbar.bottom" title="Skip navigation links">Skip navigation links</a></div>
|
||||
<a name="navbar.bottom.firstrow">
|
||||
<!-- -->
|
||||
</a>
|
||||
<ul class="navList" title="Navigation">
|
||||
<li><a href="../org/mozilla/mentat/package-summary.html">Package</a></li>
|
||||
<li>Class</li>
|
||||
<li><a href="../overview-tree.html">Tree</a></li>
|
||||
<li><a href="../deprecated-list.html">Deprecated</a></li>
|
||||
<li class="navBarCell1Rev">Index</li>
|
||||
<li><a href="../help-doc.html">Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="subNav">
|
||||
<ul class="navList">
|
||||
<li><a href="index-12.html">Prev Letter</a></li>
|
||||
<li><a href="index-14.html">Next Letter</a></li>
|
||||
</ul>
|
||||
<ul class="navList">
|
||||
<li><a href="../index.html?index-files/index-13.html" target="_top">Frames</a></li>
|
||||
<li><a href="index-13.html" target="_top">No Frames</a></li>
|
||||
</ul>
|
||||
<ul class="navList" id="allclasses_navbar_bottom">
|
||||
<li><a href="../allclasses-noframe.html">All Classes</a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<script type="text/javascript"><!--
|
||||
allClassesLink = document.getElementById("allclasses_navbar_bottom");
|
||||
if(window==top) {
|
||||
allClassesLink.style.display = "block";
|
||||
}
|
||||
else {
|
||||
allClassesLink.style.display = "none";
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
</div>
|
||||
<a name="skip.navbar.bottom">
|
||||
<!-- -->
|
||||
</a></div>
|
||||
<!-- ======== END OF BOTTOM NAVBAR ======= -->
|
||||
</body>
|
||||
</html>
|
163
docs/apis/0.7/java/index-files/index-14.html
Normal file
163
docs/apis/0.7/java/index-files/index-14.html
Normal file
|
@ -0,0 +1,163 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<!-- NewPage -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Generated by javadoc (1.8.0_152-release) on Thu Jun 28 11:01:15 BST 2018 -->
|
||||
<title>Q-Index</title>
|
||||
<meta name="date" content="2018-06-28">
|
||||
<link rel="stylesheet" type="text/css" href="../stylesheet.css" title="Style">
|
||||
<script type="text/javascript" src="../script.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript"><!--
|
||||
try {
|
||||
if (location.href.indexOf('is-external=true') == -1) {
|
||||
parent.document.title="Q-Index";
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
<noscript>
|
||||
<div>JavaScript is disabled on your browser.</div>
|
||||
</noscript>
|
||||
<!-- ========= START OF TOP NAVBAR ======= -->
|
||||
<div class="topNav"><a name="navbar.top">
|
||||
<!-- -->
|
||||
</a>
|
||||
<div class="skipNav"><a href="#skip.navbar.top" title="Skip navigation links">Skip navigation links</a></div>
|
||||
<a name="navbar.top.firstrow">
|
||||
<!-- -->
|
||||
</a>
|
||||
<ul class="navList" title="Navigation">
|
||||
<li><a href="../org/mozilla/mentat/package-summary.html">Package</a></li>
|
||||
<li>Class</li>
|
||||
<li><a href="../overview-tree.html">Tree</a></li>
|
||||
<li><a href="../deprecated-list.html">Deprecated</a></li>
|
||||
<li class="navBarCell1Rev">Index</li>
|
||||
<li><a href="../help-doc.html">Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="subNav">
|
||||
<ul class="navList">
|
||||
<li><a href="index-13.html">Prev Letter</a></li>
|
||||
<li><a href="index-15.html">Next Letter</a></li>
|
||||
</ul>
|
||||
<ul class="navList">
|
||||
<li><a href="../index.html?index-files/index-14.html" target="_top">Frames</a></li>
|
||||
<li><a href="index-14.html" target="_top">No Frames</a></li>
|
||||
</ul>
|
||||
<ul class="navList" id="allclasses_navbar_top">
|
||||
<li><a href="../allclasses-noframe.html">All Classes</a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<script type="text/javascript"><!--
|
||||
allClassesLink = document.getElementById("allclasses_navbar_top");
|
||||
if(window==top) {
|
||||
allClassesLink.style.display = "block";
|
||||
}
|
||||
else {
|
||||
allClassesLink.style.display = "none";
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
</div>
|
||||
<a name="skip.navbar.top">
|
||||
<!-- -->
|
||||
</a></div>
|
||||
<!-- ========= END OF TOP NAVBAR ========= -->
|
||||
<div class="contentContainer"><a href="index-1.html">A</a> <a href="index-2.html">B</a> <a href="index-3.html">C</a> <a href="index-4.html">D</a> <a href="index-5.html">E</a> <a href="index-6.html">F</a> <a href="index-7.html">G</a> <a href="index-8.html">H</a> <a href="index-9.html">I</a> <a href="index-10.html">J</a> <a href="index-11.html">L</a> <a href="index-12.html">M</a> <a href="index-13.html">O</a> <a href="index-14.html">Q</a> <a href="index-15.html">R</a> <a href="index-16.html">S</a> <a href="index-17.html">T</a> <a href="index-18.html">U</a> <a href="index-19.html">V</a> <a name="I:Q">
|
||||
<!-- -->
|
||||
</a>
|
||||
<h2 class="title">Q</h2>
|
||||
<dl>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/Mentat.html#query-java.lang.String-">query(String)</a></span> - Method in class org.mozilla.mentat.<a href="../org/mozilla/mentat/Mentat.html" title="class in org.mozilla.mentat">Mentat</a></dt>
|
||||
<dd>
|
||||
<div class="block">Start a query.</div>
|
||||
</dd>
|
||||
<dt><a href="../org/mozilla/mentat/Query.html" title="class in org.mozilla.mentat"><span class="typeNameLink">Query</span></a> - Class in <a href="../org/mozilla/mentat/package-summary.html">org.mozilla.mentat</a></dt>
|
||||
<dd>
|
||||
<div class="block">This class allows you to construct a query, bind values to variables and run those queries against a mentat DB.</div>
|
||||
</dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/Query.html#Query-org.mozilla.mentat.JNA.QueryBuilder-">Query(JNA.QueryBuilder)</a></span> - Constructor for class org.mozilla.mentat.<a href="../org/mozilla/mentat/Query.html" title="class in org.mozilla.mentat">Query</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/JNA.html#query_builder_bind_boolean-org.mozilla.mentat.JNA.QueryBuilder-java.lang.String-int-">query_builder_bind_boolean(JNA.QueryBuilder, String, int)</a></span> - Method in interface org.mozilla.mentat.<a href="../org/mozilla/mentat/JNA.html" title="interface in org.mozilla.mentat">JNA</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/JNA.html#query_builder_bind_double-org.mozilla.mentat.JNA.QueryBuilder-java.lang.String-double-">query_builder_bind_double(JNA.QueryBuilder, String, double)</a></span> - Method in interface org.mozilla.mentat.<a href="../org/mozilla/mentat/JNA.html" title="interface in org.mozilla.mentat">JNA</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/JNA.html#query_builder_bind_kw-org.mozilla.mentat.JNA.QueryBuilder-java.lang.String-java.lang.String-">query_builder_bind_kw(JNA.QueryBuilder, String, String)</a></span> - Method in interface org.mozilla.mentat.<a href="../org/mozilla/mentat/JNA.html" title="interface in org.mozilla.mentat">JNA</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/JNA.html#query_builder_bind_long-org.mozilla.mentat.JNA.QueryBuilder-java.lang.String-long-">query_builder_bind_long(JNA.QueryBuilder, String, long)</a></span> - Method in interface org.mozilla.mentat.<a href="../org/mozilla/mentat/JNA.html" title="interface in org.mozilla.mentat">JNA</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/JNA.html#query_builder_bind_ref-org.mozilla.mentat.JNA.QueryBuilder-java.lang.String-long-">query_builder_bind_ref(JNA.QueryBuilder, String, long)</a></span> - Method in interface org.mozilla.mentat.<a href="../org/mozilla/mentat/JNA.html" title="interface in org.mozilla.mentat">JNA</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/JNA.html#query_builder_bind_ref_kw-org.mozilla.mentat.JNA.QueryBuilder-java.lang.String-java.lang.String-">query_builder_bind_ref_kw(JNA.QueryBuilder, String, String)</a></span> - Method in interface org.mozilla.mentat.<a href="../org/mozilla/mentat/JNA.html" title="interface in org.mozilla.mentat">JNA</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/JNA.html#query_builder_bind_string-org.mozilla.mentat.JNA.QueryBuilder-java.lang.String-java.lang.String-">query_builder_bind_string(JNA.QueryBuilder, String, String)</a></span> - Method in interface org.mozilla.mentat.<a href="../org/mozilla/mentat/JNA.html" title="interface in org.mozilla.mentat">JNA</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/JNA.html#query_builder_bind_timestamp-org.mozilla.mentat.JNA.QueryBuilder-java.lang.String-long-">query_builder_bind_timestamp(JNA.QueryBuilder, String, long)</a></span> - Method in interface org.mozilla.mentat.<a href="../org/mozilla/mentat/JNA.html" title="interface in org.mozilla.mentat">JNA</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/JNA.html#query_builder_bind_uuid-org.mozilla.mentat.JNA.QueryBuilder-java.lang.String-com.sun.jna.Pointer-">query_builder_bind_uuid(JNA.QueryBuilder, String, Pointer)</a></span> - Method in interface org.mozilla.mentat.<a href="../org/mozilla/mentat/JNA.html" title="interface in org.mozilla.mentat">JNA</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/JNA.html#query_builder_destroy-org.mozilla.mentat.JNA.QueryBuilder-">query_builder_destroy(JNA.QueryBuilder)</a></span> - Method in interface org.mozilla.mentat.<a href="../org/mozilla/mentat/JNA.html" title="interface in org.mozilla.mentat">JNA</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/JNA.html#query_builder_execute-org.mozilla.mentat.JNA.QueryBuilder-org.mozilla.mentat.RustError.ByReference-">query_builder_execute(JNA.QueryBuilder, RustError.ByReference)</a></span> - Method in interface org.mozilla.mentat.<a href="../org/mozilla/mentat/JNA.html" title="interface in org.mozilla.mentat">JNA</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/JNA.html#query_builder_execute_coll-org.mozilla.mentat.JNA.QueryBuilder-org.mozilla.mentat.RustError.ByReference-">query_builder_execute_coll(JNA.QueryBuilder, RustError.ByReference)</a></span> - Method in interface org.mozilla.mentat.<a href="../org/mozilla/mentat/JNA.html" title="interface in org.mozilla.mentat">JNA</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/JNA.html#query_builder_execute_scalar-org.mozilla.mentat.JNA.QueryBuilder-org.mozilla.mentat.RustError.ByReference-">query_builder_execute_scalar(JNA.QueryBuilder, RustError.ByReference)</a></span> - Method in interface org.mozilla.mentat.<a href="../org/mozilla/mentat/JNA.html" title="interface in org.mozilla.mentat">JNA</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/JNA.html#query_builder_execute_tuple-org.mozilla.mentat.JNA.QueryBuilder-org.mozilla.mentat.RustError.ByReference-">query_builder_execute_tuple(JNA.QueryBuilder, RustError.ByReference)</a></span> - Method in interface org.mozilla.mentat.<a href="../org/mozilla/mentat/JNA.html" title="interface in org.mozilla.mentat">JNA</a></dt>
|
||||
<dd> </dd>
|
||||
<dt><span class="memberNameLink"><a href="../org/mozilla/mentat/JNA.QueryBuilder.html#QueryBuilder--">QueryBuilder()</a></span> - Constructor for class org.mozilla.mentat.<a href="../org/mozilla/mentat/JNA.QueryBuilder.html" title="class in org.mozilla.mentat">JNA.QueryBuilder</a></dt>
|
||||
<dd> </dd>
|
||||
</dl>
|
||||
<a href="index-1.html">A</a> <a href="index-2.html">B</a> <a href="index-3.html">C</a> <a href="index-4.html">D</a> <a href="index-5.html">E</a> <a href="index-6.html">F</a> <a href="index-7.html">G</a> <a href="index-8.html">H</a> <a href="index-9.html">I</a> <a href="index-10.html">J</a> <a href="index-11.html">L</a> <a href="index-12.html">M</a> <a href="index-13.html">O</a> <a href="index-14.html">Q</a> <a href="index-15.html">R</a> <a href="index-16.html">S</a> <a href="index-17.html">T</a> <a href="index-18.html">U</a> <a href="index-19.html">V</a> </div>
|
||||
<!-- ======= START OF BOTTOM NAVBAR ====== -->
|
||||
<div class="bottomNav"><a name="navbar.bottom">
|
||||
<!-- -->
|
||||
</a>
|
||||
<div class="skipNav"><a href="#skip.navbar.bottom" title="Skip navigation links">Skip navigation links</a></div>
|
||||
<a name="navbar.bottom.firstrow">
|
||||
<!-- -->
|
||||
</a>
|
||||
<ul class="navList" title="Navigation">
|
||||
<li><a href="../org/mozilla/mentat/package-summary.html">Package</a></li>
|
||||
<li>Class</li>
|
||||
<li><a href="../overview-tree.html">Tree</a></li>
|
||||
<li><a href="../deprecated-list.html">Deprecated</a></li>
|
||||
<li class="navBarCell1Rev">Index</li>
|
||||
<li><a href="../help-doc.html">Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="subNav">
|
||||
<ul class="navList">
|
||||
<li><a href="index-13.html">Prev Letter</a></li>
|
||||
<li><a href="index-15.html">Next Letter</a></li>
|
||||
</ul>
|
||||
<ul class="navList">
|
||||
<li><a href="../index.html?index-files/index-14.html" target="_top">Frames</a></li>
|
||||
<li><a href="index-14.html" target="_top">No Frames</a></li>
|
||||
</ul>
|
||||
<ul class="navList" id="allclasses_navbar_bottom">
|
||||
<li><a href="../allclasses-noframe.html">All Classes</a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<script type="text/javascript"><!--
|
||||
allClassesLink = document.getElementById("allclasses_navbar_bottom");
|
||||
if(window==top) {
|
||||
allClassesLink.style.display = "block";
|
||||
}
|
||||
else {
|
||||
allClassesLink.style.display = "none";
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
</div>
|
||||
<a name="skip.navbar.bottom">
|
||||
<!-- -->
|
||||
</a></div>
|
||||
<!-- ======== END OF BOTTOM NAVBAR ======= -->
|
||||
</body>
|
||||
</html>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue