Compare commits
770 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f1f6a0bba1 | |||
| 3f5717f192 | |||
| 4892d20b31 | |||
| 0a8b9b7884 | |||
| 9da6f3ab50 | |||
| 4eeb32ce18 | |||
| 213543b554 | |||
| 5baa631cea | |||
| 6541f01805 | |||
| f256437e5e | |||
| 58c3d20baf | |||
| 995f96f8e1 | |||
| e174dba739 | |||
| af885d9fc4 | |||
| a4709f27d4 | |||
| 2bab8774ba | |||
| 44a430b33c | |||
| a2a4c3f14a | |||
| 675342a5a8 | |||
| 44e24e9640 | |||
| 310700228b | |||
| 216524f858 | |||
| da7662a4b0 | |||
| ba481c3756 | |||
| 3404b452a4 | |||
| 114ad2f86f | |||
| 78ad745ab9 | |||
| 4d8eb16f07 | |||
| d5a9d522fb | |||
| f0eabde9a4 | |||
| 2cffc5be2a | |||
| 2e0ce58d2c | |||
| e15b35d5a6 | |||
| fab6882fd6 | |||
| 699803e114 | |||
| 58c44132d1 | |||
| 81ef0f8e89 | |||
| 36a9f5c24c | |||
| 936f7deee4 | |||
| 7335f75daf | |||
| 0d83c71b34 | |||
| 7309b50fbc | |||
| af8980bdce | |||
| fe18355267 | |||
| ff9ff54801 | |||
| c131675401 | |||
| f05630fb1c | |||
| baa574ef32 | |||
| 8c5611a169 | |||
| b93760e1e7 | |||
| 00eee07911 | |||
| f0c1ea9e86 | |||
| db9a708ee7 | |||
| 9541fbc283 | |||
| 1f72b53461 | |||
| 592ae2823c | |||
| 07ef60350d | |||
| 8a6888c19e | |||
| 202651fe86 | |||
| 54402964b0 | |||
| d8a94dbb36 | |||
| 46451fa8b5 | |||
| 890a9ede3b | |||
| 7c036fabe5 | |||
| a6f09a0193 | |||
| 68522e1627 | |||
| 5b3a754360 | |||
| c216df75c6 | |||
| 613390680d | |||
| 238bea2ae6 | |||
| 6c6badd2ef | |||
| 48dcdd3b74 | |||
| a6746456d9 | |||
| 783f5b5aa2 | |||
| 2a28cff2e7 | |||
| 16c8c96745 | |||
| 7009c43eaf | |||
| c3b7e1c4e8 | |||
| b949244ea1 | |||
| 7e13152952 | |||
| 59d01de837 | |||
| b8a0fc52a2 | |||
| 91c0092917 | |||
| a0423e0fc1 | |||
| 5c9f5e4022 | |||
| 0c08c8c208 | |||
| a52b8c8845 | |||
| 89e1caf294 | |||
| 2ee01b0c9a | |||
| bb51af68f4 | |||
| 6de6121e36 | |||
| a22a2d266f | |||
| 92f699d384 | |||
| 1528014703 | |||
| 61186d1243 | |||
| e51fae4134 | |||
| 0a8cc1f634 | |||
| 34fac74362 | |||
| ec8298fbd1 | |||
| 63fb588d30 | |||
| 6a61a19f9a | |||
| f703dfa381 | |||
| eda67031de | |||
| 0c6df29cba | |||
| 2a91065145 | |||
| 58c2ca4483 | |||
| 50a17a1b3a | |||
| 3a0e45d0af | |||
| 84c1a7b49f | |||
| e35a703b76 | |||
| 35af4c5ff1 | |||
| bef0369ff4 | |||
| e41c09ac8c | |||
| e0fb44090b | |||
| 7f66a3eeb2 | |||
| 16e620e07f | |||
| 5d2a618340 | |||
| 583fc5b963 | |||
| 589857b965 | |||
| bd87174bae | |||
| 65c7226e31 | |||
| 5b400552be | |||
| aa6b35cc80 | |||
| 4340f27e17 | |||
| 5158a9a9a7 | |||
| ff6b15fbb2 | |||
| aec9aa9289 | |||
| 283e87146c | |||
| e6c29da777 | |||
| 23a35c2145 | |||
| 41f9449016 | |||
| 78fc4ae4d9 | |||
| c783fd36dc | |||
| 4d3aa08f67 | |||
| c15dce9f92 | |||
| af825cd50b | |||
| 746bb422ea | |||
| 80c438613a | |||
| 73cdbeb632 | |||
| bff14ecfcc | |||
| 358c531e9b | |||
| 671757f4d4 | |||
| aa29140637 | |||
| 5f4e94722b | |||
| 405dffd591 | |||
| ec9b37b33e | |||
| c693bd59f7 | |||
| aeae7904a5 | |||
| 58eebdaa25 | |||
| ec8edf3295 | |||
| e9ed375876 | |||
| b7f75b7bb2 | |||
| fdeaee5f57 | |||
| 70679d135d | |||
| 3897a882f7 | |||
| 786a9dcf5d | |||
| be3ab76bfb | |||
| c7f7b45a15 | |||
| be7aef1f85 | |||
| becf29a36c | |||
| 7c26811c2f | |||
| 5d48fb1784 | |||
| 005baf67d5 | |||
| 8af781fc4d | |||
| 60745265e1 | |||
| d85f9bf6d5 | |||
| af90dcf324 | |||
| 1a2563a950 | |||
| 5558624b43 | |||
| 9c4c16c80d | |||
| 6a9364a518 | |||
| 57645b67fa | |||
| 1bce6b468e | |||
| 153288dc89 | |||
| 28e95830ad | |||
| 67214a9594 | |||
| 27cb7d6a1d | |||
| 47c5db7f77 | |||
| fdeb2c8121 | |||
| 695d0b698a | |||
| 4f07be39b7 | |||
| ec71e555c4 | |||
| 977b6919f2 | |||
| 0c179dd5b4 | |||
| 195611d8e6 | |||
| cc8202b646 | |||
| c7795cfd47 | |||
| b87278f414 | |||
| 5e208f712f | |||
| b15223bce6 | |||
| b383744835 | |||
| c0952c11a7 | |||
| e133a0ec05 | |||
| a24769454e | |||
| 444424dab8 | |||
| 1050f1d2a1 | |||
| e98fa205f8 | |||
| 86d77fd95d | |||
| 038ac625af | |||
| 9b1334b7d3 | |||
| ed86e0a0a7 | |||
| decc1425a3 | |||
| c874d60353 | |||
| d3229ce23e | |||
| b1dcebfea3 | |||
| 26cf4af5d7 | |||
| aa250db66a | |||
| 5e82c06cf8 | |||
| 4178849736 | |||
| cbaef88980 | |||
| 2877c43e6c | |||
| 887f6e5082 | |||
| a08d363f67 | |||
| c1a07d3bab | |||
| ed77e13f05 | |||
| e209d144c3 | |||
| 0bc72d2b91 | |||
| b9894f88b8 | |||
| d192b4441d | |||
| 00d635dda4 | |||
| 98ab970bac | |||
| 1f097a2f29 | |||
| c8c6763782 | |||
| db6f0bef91 | |||
| e3e9bdcd0b | |||
| 8f714e21a0 | |||
| ede8900912 | |||
| e2e696d11f | |||
| 452108b4fc | |||
| 46c46e400a | |||
| 41a7ac637a | |||
| 9c1c6da930 | |||
| 09947d0aad | |||
| 91710673e7 | |||
| 72bb29283f | |||
| 13b059025f | |||
| 502a5c2e26 | |||
| 45fe10d02b | |||
| 9c113744d1 | |||
| 70576de5e0 | |||
| fecad503a9 | |||
| b162c68d62 | |||
| 00c0c5883a | |||
| f52de0fdaf | |||
| ca379859c9 | |||
| f0c701c410 | |||
| b47e1c7866 | |||
| e809d84618 | |||
| 808cf29b55 | |||
| fb546c0bf4 | |||
| 5d800cdb6e | |||
| 0c6b869263 | |||
| 104e225074 | |||
| 3e053c50b4 | |||
| 9a897cb0de | |||
| 33c8e9d233 | |||
| 58b5921ca4 | |||
| 888e14f551 | |||
| 58bf53d573 | |||
| f69084aaf7 | |||
| 8a056d486f | |||
| 859dd4fc2f | |||
| ab12b750de | |||
| b1926192fa | |||
| c7f602497b | |||
| 069029011e | |||
| f326577619 | |||
| 09c2640fe3 | |||
| 75d3b1b5a2 | |||
| 7d207bf483 | |||
| 48a8352a39 | |||
| 93269a9e41 | |||
| caf7e26c4f | |||
| 8ba5d79c35 | |||
| fd022a23a5 | |||
| 1a95091b49 | |||
| fd250c2cb5 | |||
| 6bc4baf313 | |||
| eead0ffef8 | |||
| ea6bccaa44 | |||
| dff3443939 | |||
| 594f5e24c4 | |||
| 9cf8961557 | |||
| 95daf355a7 | |||
| 9fcca3d27c | |||
| 4cfd54256d | |||
| 93853a4846 | |||
| e94e132994 | |||
| f7a164f345 | |||
| 3f3dc2f5d8 | |||
| beb3fb6904 | |||
| 5e22dfb6a5 | |||
| 5028dd89ab | |||
| a1016b4275 | |||
| 9095063e14 | |||
| 7286c59896 | |||
| 2b57197066 | |||
| 7725370ff4 | |||
| 8c7ee455ff | |||
| aef4fbf203 | |||
| 9a184daad3 | |||
| 3ba6914121 | |||
| a757026c5e | |||
| 02e683e467 | |||
| 44f53b9647 | |||
| 0f200bc580 | |||
| 59b8e666f6 | |||
| 440e696d5e | |||
| 0c39b13e46 | |||
| cbf4a0fa8e | |||
| 5326953e57 | |||
| 3f874b58fb | |||
| 4bca124845 | |||
| f02cc9a324 | |||
| 039d494d5f | |||
| 322a325703 | |||
| b715fe9d80 | |||
| 3d2a559ecb | |||
| 27553579c0 | |||
| 24730de8d1 | |||
| 9125d5ab6c | |||
| c76a8862a7 | |||
| 5202b7ca75 | |||
| 7d70571142 | |||
| 479cfd5af3 | |||
| 2ecadd8869 | |||
| 2159292ce0 | |||
| 5d03b1610f | |||
| 61a5b6e3bc | |||
| b2ee272075 | |||
| 21f22f9088 | |||
| 330e36c2c6 | |||
| 4e0c157d02 | |||
| 89f07b1276 | |||
| 028acd974f | |||
| 569700a72e | |||
| eee8687a9f | |||
| 7a2280fe65 | |||
| c8f4fec2de | |||
| bfe6dd0100 | |||
| 3068d6622e | |||
| 35325a37ce | |||
| 3a51f8c307 | |||
| d6f5aa824e | |||
| 51d7869096 | |||
| 0b3791b537 | |||
| 0b518b8d65 | |||
| f9bfc32b73 | |||
| 93b3c1f398 | |||
| 5e4d77fa88 | |||
| d1fbaa071b | |||
| 55f318e53f | |||
| 96cb7007a9 | |||
| 50834d8b74 | |||
| 8376ae7295 | |||
| 7a816e771a | |||
| eafb4418a0 | |||
| cd302a5e32 | |||
| 47079b19df | |||
| 37fd50df1f | |||
| 780bc4c557 | |||
| 9246c85b61 | |||
| 6a9be2cbc7 | |||
| d05bf0fe61 | |||
| aed5abcc0f | |||
| 0649a1a05d | |||
| b0dd9d427f | |||
| 3f47e7551c | |||
| d1db37a2f3 | |||
| a5b1f34125 | |||
| d69e6e6efb | |||
| cb1d6b997e | |||
| 263a8e8c0a | |||
| dec4cea24c | |||
| 2cccced140 | |||
| 55e0fd4a9d | |||
| 24433bea19 | |||
| 99d4a2db46 | |||
| ed640943c4 | |||
| 2bf2ff605f | |||
| 039081123c | |||
| 8d67d501e3 | |||
| 1ff2903785 | |||
| d703d25a35 | |||
| a34f27daed | |||
| 24873723f4 | |||
| da6a42a921 | |||
| d62c464541 | |||
| aa1b2a80ee | |||
| 8449dacbd4 | |||
| d0ae50411f | |||
| 28d46cce0b | |||
| 38d1db9b79 | |||
| 0dac350f3c | |||
| e393f6eff0 | |||
| eadabc6513 | |||
| bb65feb869 | |||
| 88af76f62d | |||
| 100bc4429a | |||
| bee3213145 | |||
| 92bf6fd4f6 | |||
| 50c61825fc | |||
| a02d6d8237 | |||
| 8ba619b5f5 | |||
| f4449dc0ba | |||
| d655290c99 | |||
| bb9549e0be | |||
| d51cefd1bb | |||
| bc6d949aa3 | |||
| cc336321f8 | |||
| d41d454fc1 | |||
| 0f0e5700ba | |||
| 01bdff7a1c | |||
| 81602595a0 | |||
| 5da49ff562 | |||
| a5512fb179 | |||
| fc158d3870 | |||
| 9f6c0d0027 | |||
| 9faa3bbdbb | |||
| b3b64179df | |||
| a17b04f840 | |||
| b184c91076 | |||
| b92669db73 | |||
| 8a1104940d | |||
| d23f210377 | |||
| 66422b57f9 | |||
| 6788c7b7c0 | |||
| c0b6112d04 | |||
| 7994d9dab7 | |||
| 5d3cc0c09b | |||
| cc9380f68d | |||
| 4c40b7b819 | |||
| bc532ae15f | |||
| 50ba4e3efd | |||
| afef7fc136 | |||
| bae91082dd | |||
| 7d0103bb06 | |||
| 68c88ea04f | |||
| ff96718b52 | |||
| 3e1e7aad3d | |||
| 59f13119cf | |||
| 26cd8bc1af | |||
| 45d7562843 | |||
| 3748b7329f | |||
| efd0cf87df | |||
| 728650ecb3 | |||
| e68899f1e4 | |||
| 85ca6d7ace | |||
| 1790ccb98b | |||
| 6aa5a42beb | |||
| 7623795018 | |||
| aa4356da68 | |||
| 930e4167bd | |||
| 521f20e3d3 | |||
| c9f240cea4 | |||
| 1b3c6e727e | |||
| 0478d1d88a | |||
| b884158434 | |||
| 841e596ad0 | |||
| bc2b65832e | |||
| 5270d7424d | |||
| 37b188fbca | |||
| c8885d42fe | |||
| c27bda0b79 | |||
| fecdff4354 | |||
| 453be55b85 | |||
| 17c20bb15e | |||
| 9ee9444a0a | |||
| 42d042e602 | |||
| 3f073f9ef8 | |||
| aebeabacff | |||
| 002330dc48 | |||
| f8be243fef | |||
| e6d7ae24e3 | |||
| aec0aa83a9 | |||
| a1b0ff1735 | |||
| 24b368da8d | |||
| 4cd3a59a79 | |||
| 07d76f7b95 | |||
| 1bfe92a691 | |||
| 48114d08cf | |||
| dd8b12a6c5 | |||
| 60f41362cf | |||
| 2ddfa6a360 | |||
| ec3e57fa92 | |||
| 500ca334bd | |||
| 0b1a1e8c37 | |||
| a75011a891 | |||
| cd88a74115 | |||
| 9ea7632cca | |||
| 7f5fe1faf3 | |||
| 8fbb335d9f | |||
| 60783f47b2 | |||
| d847c73153 | |||
| 12b198f1a2 | |||
| 502c618f01 | |||
| b9561384d0 | |||
| d2b9cdd592 | |||
| cc19bed8b1 | |||
| 077efc2730 | |||
| 7f9dcd664d | |||
| d6c671e626 | |||
| d4170ac0eb | |||
| 71aac8763f | |||
| 3a202be6f1 | |||
| 40ee72947e | |||
| c439b9e0c6 | |||
| f6e04b6e29 | |||
| bcc76bd419 | |||
| f82c8cb0d5 | |||
| 1e24dc987a | |||
| 1be61ad278 | |||
| 8e5cef7bca | |||
| 0587a1a8bb | |||
| 56a410741b | |||
| 2e4d526996 | |||
| f025fc164d | |||
| 733ef7e8e6 | |||
| 0c04f2e576 | |||
| e30ecbfb84 | |||
| d34ce81867 | |||
| d97e0d0e38 | |||
| 16702ec69d | |||
| 997670f997 | |||
| d679f9e5d1 | |||
| 26d71f03fc | |||
| b102d62749 | |||
| 26e136bb9a | |||
| 04b92471f7 | |||
| 6bbc5b3730 | |||
| 7520116441 | |||
| 6e4ff8707e | |||
| 2f764eb4f4 | |||
| 8efe749ab3 | |||
| fd5d18045a | |||
| 4fa5952090 | |||
| 4eda84a456 | |||
| 55be21647e | |||
| c5a5a54c51 | |||
| 1d7c88bf98 | |||
| bc91738e66 | |||
| 25e87ed1ba | |||
| 4246a4bf14 | |||
| dc608d8a5f | |||
| c81dcaabe8 | |||
| 706047470e | |||
| 59714faa68 | |||
| 979715bb45 | |||
| 10ca116b3e | |||
| 63329ce6c7 | |||
| b3d7f1c2db | |||
| f1967a7018 | |||
| 038158e94a | |||
| a3f943942c | |||
| 4e8a0eb4e7 | |||
| 4ffcce88ad | |||
| 179c32cb57 | |||
| 39a7a1a9be | |||
| f96148c4c6 | |||
| e411b69537 | |||
| d8cf688469 | |||
| 29e70da4a4 | |||
| d3b54d624f | |||
| d8091fe979 | |||
| 8eb9c06e65 | |||
| 7d65f1bec0 | |||
| 73d806dc1f | |||
| e32fef1400 | |||
| cc4bfd9623 | |||
| 30bb082d3a | |||
| 36a5f1e90c | |||
| 6abbef89e6 | |||
| 1936a1933c | |||
| 0cbc2143b9 | |||
| 4308b88dcd | |||
| 870fe69c72 | |||
| 39bd6a0d46 | |||
| 4e8e33ba84 | |||
| 583a5cf5d2 | |||
| ddf6b27579 | |||
| 3195b88b79 | |||
| 9d31508a54 | |||
| d294792d1a | |||
| 0503cd6d11 | |||
| b838a229be | |||
| a6ab50bc42 | |||
| 02be13cea6 | |||
| a382d236da | |||
| fe19414331 | |||
| 7fe416fae1 | |||
| 416aeb16ee | |||
| 2492b6c0fb | |||
| 8e2fd6eae3 | |||
| 1cb4e2e196 | |||
| 2c6c98dbf0 | |||
| a03be7987b | |||
| 653882ed8c | |||
| 60fd5c34ab | |||
| 9c9b33cbaf | |||
| 32a5feb53e | |||
| 010bd47b98 | |||
| ea1b2fa034 | |||
| 73ab708b5c | |||
| 89efa89778 | |||
| 89caab8147 | |||
| e680b6dcf7 | |||
| aeb064c069 | |||
| ff4e771c0f | |||
| 4042d2b424 | |||
| 307db965c6 | |||
| b579db6d5c | |||
| eb4455e4e4 | |||
| fa746af4a3 | |||
| b8d689b666 | |||
| 5e65cd1caa | |||
| fd0791587b | |||
| ea51c09a8b | |||
| 273c2a9425 | |||
| 59b6a2bcbe | |||
| 83be5586a0 | |||
| 4f3dc93978 | |||
| fb74019a14 | |||
| 69bc8c70f9 | |||
| 26a3e56649 | |||
| 61791c6f4c | |||
| 6b429dc11b | |||
| 78b404d976 | |||
| 5ca40d84ad | |||
| 418d212308 | |||
| 0c7bcf4c0e | |||
| 161f742856 | |||
| 28f4e7b286 | |||
| 0051a56dec | |||
| 5af043c2e7 | |||
| d733ee5cca | |||
| 88a99437b9 | |||
| 5fbb2adf7d | |||
| d9be387361 | |||
| bbd0ae2e87 | |||
| addc7e0e5b | |||
| c42bbfc550 | |||
| 6d841fdc89 | |||
| 28f32bedd1 | |||
| 4e73863fd4 | |||
| 77eb3e305c | |||
| ed96436f23 | |||
| 3e49355e17 | |||
| e030619109 | |||
| fb18d81038 | |||
| cad6aae45e | |||
| 0498caec89 | |||
| d2bef86709 | |||
| 88813681c2 | |||
| 6242c9386f | |||
| e8468098a0 | |||
| c1e40a3a02 | |||
| 62b28db5c7 | |||
| 3f23eb6813 | |||
| 3442306989 | |||
| dda2833a4e | |||
| 69e2422973 | |||
| 6539977cfd | |||
| b4824b055c | |||
| ce2aa8bebe | |||
| 681307dc80 | |||
| 790adfd8ec | |||
| 8777bdb78a | |||
| 164b332d80 | |||
| b504623802 | |||
| 9cf09b5672 | |||
| d8200de04d | |||
| 8594f02c5d | |||
| bdb40cb18f | |||
| d6b3a02d21 | |||
| 8c79ea8447 | |||
| 67a17779cb | |||
| 53ffe6e21d | |||
| f9b58eb4f8 | |||
| 0f97dc28b0 | |||
| 1f02cd6cd6 | |||
| 58d40363b9 | |||
| 59fe13e39a | |||
| abdcfcef83 | |||
| 5ecee3dae3 | |||
| 4cc6f869a4 | |||
| 043353483e | |||
| d45bbbb05c | |||
| b5fab68162 | |||
| 908dec7404 | |||
| e210c93903 | |||
| 5196d93505 | |||
| dae7ae359e | |||
| 4a398c317d | |||
| 7be1473ccb | |||
| 5a5cba0692 | |||
| e01e3b5b94 | |||
| 440f7b4833 | |||
| f8ae2d2a2a | |||
| 399875f6c5 | |||
| d76d954de3 | |||
| b608d7e2c6 | |||
| 29c2f0366d | |||
| e0a3e4bd51 | |||
| 64953f0fb8 | |||
| f176776bd7 | |||
| 7667f5f12f | |||
| 8f3fe2ead2 | |||
| 6e2de57963 | |||
| b0d46fcd72 | |||
| 8e7efe3764 | |||
| 272ae627c5 | |||
| 016f46666b | |||
| 1170fb52e1 | |||
| ea2529528e | |||
| 90f549a0cf | |||
| fb016bf257 | |||
| fe03773305 | |||
| 93b4c3beb8 | |||
| bfd28f9e01 | |||
| d5de1f39b5 | |||
| 52da6d2607 | |||
| 263b86c54c | |||
| cf5dce045f | |||
| ae31f48826 | |||
| 1371df2117 | |||
| 49d8e0ef97 | |||
| 47dd0e735d | |||
| 8ded164200 | |||
| dbcea23976 | |||
| 13f8a80d94 | |||
| ef3a162be6 | |||
| 0d8579120a | |||
| 2e225a35b4 | |||
| a549e7a527 | |||
| f59c01e24c | |||
| c5ee2d8e80 | |||
| 6a9ba9a8c2 | |||
| d924af2325 | |||
| f14b2e35fa | |||
| 5255102e04 | |||
| f62518f65d | |||
| ddc8b9ee0e | |||
| 02efa7be96 | |||
| 058f3dbb27 | |||
| 69d98d9f93 | |||
| 77a159212b | |||
| 0941ea8a62 | |||
| 38c2052767 | |||
| 21461407f6 | |||
| be5bada4d0 | |||
| 7eff609d8a | |||
| 655a1b7b58 | |||
| 92df71a360 | |||
| 022a042897 | |||
| 223c6b48a9 | |||
| 4d24eff8dc | |||
| 2f251f4929 | |||
| f661db3050 | |||
| 57114c256f | |||
| 11d5efde75 | |||
| 51f0c762f0 | |||
| 9023444fea | |||
| ee7c667a36 | |||
| 101a6b87c7 | |||
| 67ae593ffe | |||
| 5b6c3c8f7d | |||
| c6effdbfba | |||
| b15c63cbb9 | |||
| aecd7f9d75 | |||
| 8aa22ffe98 |
+43
-2
@@ -599,7 +599,7 @@ SHELL_OPT += -DSQLITE_ENABLE_DBPAGE_VTAB
|
||||
SHELL_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB
|
||||
SHELL_OPT += -DSQLITE_ENABLE_BYTECODE_VTAB
|
||||
SHELL_OPT += -DSQLITE_ENABLE_OFFSET_SQL_FUNC
|
||||
FUZZERSHELL_OPT =
|
||||
FUZZERSHELL_OPT =
|
||||
FUZZCHECK_OPT += -I$(TOP)/test
|
||||
FUZZCHECK_OPT += -I$(TOP)/ext/recover
|
||||
FUZZCHECK_OPT += \
|
||||
@@ -690,6 +690,12 @@ srcck1$(BEXE): $(TOP)/tool/srcck1.c
|
||||
sourcetest: srcck1$(BEXE) sqlite3.c
|
||||
./srcck1 sqlite3.c
|
||||
|
||||
src-verify: $(TOP)/tool/src-verify.c
|
||||
$(BCC) -o src-verify$(BEXE) $(TOP)/tool/src-verify.c
|
||||
|
||||
verify-source: ./src-verify
|
||||
./src-verify $(TOP)
|
||||
|
||||
fuzzershell$(TEXE): $(TOP)/tool/fuzzershell.c sqlite3.c sqlite3.h
|
||||
$(LTLINK) -o $@ $(FUZZERSHELL_OPT) \
|
||||
$(TOP)/tool/fuzzershell.c sqlite3.c $(TLIBS)
|
||||
@@ -775,7 +781,7 @@ mptest: mptester$(TEXE)
|
||||
cp fts5.c fts5.h tsrc
|
||||
touch .target_source
|
||||
|
||||
sqlite3.c: .target_source $(TOP)/tool/mksqlite3c.tcl
|
||||
sqlite3.c: .target_source $(TOP)/tool/mksqlite3c.tcl src-verify
|
||||
$(TCLSH_CMD) $(TOP)/tool/mksqlite3c.tcl $(AMALGAMATION_LINE_MACROS)
|
||||
cp tsrc/sqlite3ext.h .
|
||||
cp $(TOP)/ext/session/sqlite3session.h .
|
||||
@@ -1133,6 +1139,7 @@ SHELL_SRC = \
|
||||
$(TOP)/ext/expert/sqlite3expert.h \
|
||||
$(TOP)/ext/misc/zipfile.c \
|
||||
$(TOP)/ext/misc/memtrace.c \
|
||||
$(TOP)/ext/misc/pcachetrace.c \
|
||||
$(TOP)/ext/recover/dbdata.c \
|
||||
$(TOP)/ext/recover/sqlite3recover.c \
|
||||
$(TOP)/ext/recover/sqlite3recover.h \
|
||||
@@ -1307,6 +1314,12 @@ testrunner: testfixture$(TEXE)
|
||||
#
|
||||
devtest: testfixture$(TEXE) fuzztest testrunner
|
||||
|
||||
mdevtest:
|
||||
$(TCLSH_CMD) $(TOP)/test/testrunner.tcl mdevtest
|
||||
|
||||
sdevtest:
|
||||
$(TCLSH_CMD) $(TOP)/test/testrunner.tcl sdevtest
|
||||
|
||||
# Testing for a release
|
||||
#
|
||||
releasetest: testfixture$(TEXE)
|
||||
@@ -1506,6 +1519,7 @@ clean:
|
||||
rm -f LogEst$(TEXE) fts3view$(TEXE) rollback-test$(TEXE) showdb$(TEXE)
|
||||
rm -f showjournal$(TEXE) showstat4$(TEXE) showwal$(TEXE) speedtest1$(TEXE)
|
||||
rm -f wordcount$(TEXE) changeset$(TEXE)
|
||||
rm -f version-info$(TEXT)
|
||||
rm -f sqlite3.dll sqlite3.lib sqlite3.exp sqlite3.def
|
||||
rm -f sqlite3.c
|
||||
rm -f sqlite3rc.h
|
||||
@@ -1521,6 +1535,8 @@ clean:
|
||||
rm -f dbhash dbhash.exe
|
||||
rm -f fts5.* fts5parse.*
|
||||
rm -f threadtest5
|
||||
rm -f src-verify
|
||||
rm -f custom.rws
|
||||
|
||||
distclean: clean
|
||||
rm -f sqlite_cfg.h config.log config.status libtool Makefile sqlite3.pc \
|
||||
@@ -1549,3 +1565,28 @@ sqlite3.dll: $(REAL_LIBOBJ) sqlite3.def
|
||||
#
|
||||
fiddle: sqlite3.c shell.c
|
||||
make -C ext/wasm fiddle emcc_opt=-Os
|
||||
|
||||
#
|
||||
# Spell-checking for source comments
|
||||
# The sources checked are either C sources or C source templates.
|
||||
# Their comments are extracted and processed through aspell using
|
||||
# a custom dictionary that contains scads of odd identifiers that
|
||||
# find their way into the comments.
|
||||
#
|
||||
# Currently, this target is setup to be "made" in-tree only.
|
||||
# The output is ephemeral. Redirect it to guide spelling fixups,
|
||||
# either to correct spelling or add words to tool/custom.txt.
|
||||
#
|
||||
./custom.rws: ./tool/custom.txt
|
||||
@echo 'Updating custom dictionary from tool/custom.txt'
|
||||
aspell --lang=en create master ./custom.rws < $<
|
||||
|
||||
misspell: ./custom.rws
|
||||
$(TCLSH_CMD) ./tool/spellsift.tcl ./src/*.c ./src/*.h ./src/*.in
|
||||
|
||||
#
|
||||
# tool/version-info: a utility for emitting sqlite3 version info
|
||||
# in various forms.
|
||||
#
|
||||
version-info$(TEXE): $(TOP)/tool/version-info.c Makefile sqlite3.h
|
||||
$(LTLINK) $(ST_OPT) -o $@ $(TOP)/tool/version-info.c
|
||||
|
||||
@@ -22,12 +22,6 @@ TOP = ../sqlite
|
||||
BCC = gcc -g -O0
|
||||
#BCC = /opt/ancic/bin/c89 -0
|
||||
|
||||
#### If the target operating system supports the "usleep()" system
|
||||
# call, then define the HAVE_USLEEP macro for all C modules.
|
||||
#
|
||||
#USLEEP =
|
||||
USLEEP = -DHAVE_USLEEP=1
|
||||
|
||||
#### If you want the SQLite library to be safe for use within a
|
||||
# multi-threaded program, then define the following macro
|
||||
# appropriately:
|
||||
|
||||
+53
-15
@@ -52,6 +52,13 @@ MINIMAL_AMALGAMATION = 0
|
||||
USE_STDCALL = 0
|
||||
!ENDIF
|
||||
|
||||
# Set this non-0 to use structured exception handling (SEH) for WAL mode
|
||||
# in the core library.
|
||||
#
|
||||
!IFNDEF USE_SEH
|
||||
USE_SEH = 1
|
||||
!ENDIF
|
||||
|
||||
# Set this non-0 to have the shell executable link against the core dynamic
|
||||
# link library.
|
||||
#
|
||||
@@ -218,6 +225,12 @@ WIN32HEAP = 0
|
||||
OSTRACE = 0
|
||||
!ENDIF
|
||||
|
||||
# enable address sanitizer using ASAN=1 on the command-line.
|
||||
#
|
||||
!IFNDEF ASAN
|
||||
ASAN = 0
|
||||
!ENDIF
|
||||
|
||||
# Set this to one of the following values to enable various debugging
|
||||
# features. Each level includes the debugging options from the previous
|
||||
# levels. Currently, the recognized values for DEBUG are:
|
||||
@@ -389,6 +402,13 @@ OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_MATH_FUNCTIONS
|
||||
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RBU=1
|
||||
!ENDIF
|
||||
|
||||
# Should structured exception handling (SEH) be enabled for WAL mode in
|
||||
# the core library?
|
||||
#
|
||||
!IF $(USE_SEH)!=0
|
||||
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_USE_SEH=1
|
||||
!ENDIF
|
||||
|
||||
# These are the "extended" SQLite compilation options used when compiling for
|
||||
# the Windows 10 platform.
|
||||
#
|
||||
@@ -877,6 +897,13 @@ RCC = $(RCC) -DSQLITE_WIN32_MALLOC_VALIDATE=1
|
||||
!ENDIF
|
||||
!ENDIF
|
||||
|
||||
|
||||
# Address sanitizer if ASAN=1
|
||||
#
|
||||
!IF $(ASAN)>0
|
||||
TCC = $(TCC) /fsanitize=address
|
||||
!ENDIF
|
||||
|
||||
# <<mark>>
|
||||
# The locations of the Tcl header and library files. Also, the library that
|
||||
# non-stubs enabled programs using Tcl must link against. These variables
|
||||
@@ -1807,6 +1834,12 @@ srcck1.exe: $(TOP)\tool\srcck1.c
|
||||
sourcetest: srcck1.exe $(SQLITE3C)
|
||||
srcck1.exe $(SQLITE3C)
|
||||
|
||||
src-verify.exe: $(TOP)\tool\src-verify.c
|
||||
$(LTLINK) $(NO_WARN) $(TOP)\tool\src-verify.c
|
||||
|
||||
verify-source: src-verify.exe
|
||||
src-verify.exe $(TOP)
|
||||
|
||||
fuzzershell.exe: $(TOP)\tool\fuzzershell.c $(SQLITE3C) $(SQLITE3H)
|
||||
$(LTLINK) $(NO_WARN) $(FUZZERSHELL_COMPILE_OPTS) $(TOP)\tool\fuzzershell.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
|
||||
|
||||
@@ -1814,7 +1847,7 @@ dbfuzz.exe: $(TOP)\test\dbfuzz.c $(SQLITE3C) $(SQLITE3H)
|
||||
$(LTLINK) $(NO_WARN) $(DBFUZZ_COMPILE_OPTS) $(TOP)\test\dbfuzz.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
|
||||
|
||||
fuzzcheck.exe: $(FUZZCHECK_SRC) $(SQLITE3C) $(SQLITE3H)
|
||||
$(LTLINK) $(NO_WARN) $(FUZZCHECK_OPTS) $(FUZZCHECK_SRC) $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
|
||||
$(LTLINK) /F 8388608 $(NO_WARN) $(FUZZCHECK_OPTS) $(FUZZCHECK_SRC) $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
|
||||
|
||||
fuzzcheck-asan.exe: $(FUZZCHECK_SRC) $(SQLITE3C) $(SQLITE3H)
|
||||
$(LTLINK) $(NO_WARN) /fsanitize=address $(FUZZCHECK_OPTS) $(FUZZCHECK_SRC) $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
|
||||
@@ -1870,7 +1903,7 @@ mptest: mptester.exe
|
||||
move vdbe.new tsrc\vdbe.c
|
||||
echo > .target_source
|
||||
|
||||
sqlite3.c: .target_source sqlite3ext.h sqlite3session.h $(MKSQLITE3C_TOOL)
|
||||
sqlite3.c: .target_source sqlite3ext.h sqlite3session.h $(MKSQLITE3C_TOOL) src-verify.exe
|
||||
$(TCLSH_CMD) $(MKSQLITE3C_TOOL) $(MKSQLITE3C_ARGS)
|
||||
|
||||
sqlite3-all.c: sqlite3.c $(TOP)\tool\split-sqlite3c.tcl
|
||||
@@ -2226,8 +2259,8 @@ SHELL_SRC = \
|
||||
$(TOP)\src\shell.c.in \
|
||||
$(TOP)\ext\misc\appendvfs.c \
|
||||
$(TOP)\ext\misc\completion.c \
|
||||
$(TOP)\ext\misc\base64.c \
|
||||
$(TOP)\ext\misc\base85.c \
|
||||
$(TOP)\ext\misc\base64.c \
|
||||
$(TOP)\ext\misc\base85.c \
|
||||
$(TOP)\ext\misc\decimal.c \
|
||||
$(TOP)\ext\misc\fileio.c \
|
||||
$(TOP)\ext\misc\ieee754.c \
|
||||
@@ -2238,9 +2271,10 @@ SHELL_SRC = \
|
||||
$(TOP)\ext\expert\sqlite3expert.c \
|
||||
$(TOP)\ext\expert\sqlite3expert.h \
|
||||
$(TOP)\ext\misc\memtrace.c \
|
||||
$(TOP)/ext/recover/dbdata.c \
|
||||
$(TOP)/ext/recover/sqlite3recover.c \
|
||||
$(TOP)/ext/recover/sqlite3recover.h \
|
||||
$(TOP)\ext\misc\pcachetrace.c \
|
||||
$(TOP)\ext\recover\dbdata.c \
|
||||
$(TOP)\ext\recover\sqlite3recover.c \
|
||||
$(TOP)\ext\recover\sqlite3recover.h \
|
||||
$(TOP)\src\test_windirent.c
|
||||
|
||||
# If use of zlib is enabled, add the "zipfile.c" source file.
|
||||
@@ -2484,10 +2518,13 @@ testrunner: testfixture.exe
|
||||
#
|
||||
devtest: testfixture.exe fuzztest testrunner
|
||||
|
||||
mdevtest:
|
||||
$(TCLSH_CMD) $(TOP)\test\testrunner.tcl mdevtest
|
||||
|
||||
# Testing for a release
|
||||
#
|
||||
releasetest: testfixture.exe fuzztest
|
||||
testfixture.exe $(TOP)/test/testrunner.tcl release
|
||||
testfixture.exe $(TOP)\test\testrunner.tcl release
|
||||
|
||||
|
||||
smoketest: $(TESTPROGS)
|
||||
@@ -2515,14 +2552,14 @@ sqlite3_expert.exe: $(SQLITE3C) $(TOP)\ext\expert\sqlite3expert.h $(TOP)\ext\exp
|
||||
$(LTLINK) $(NO_WARN) $(TOP)\ext\expert\sqlite3expert.c $(TOP)\ext\expert\expert.c $(SQLITE3C) $(TLIBS)
|
||||
|
||||
CHECKER_DEPS =\
|
||||
$(TOP)/tool/mkccode.tcl \
|
||||
$(TOP)\tool\mkccode.tcl \
|
||||
sqlite3.c \
|
||||
$(TOP)/src/tclsqlite.c \
|
||||
$(TOP)/ext/repair/sqlite3_checker.tcl \
|
||||
$(TOP)/ext/repair/checkindex.c \
|
||||
$(TOP)/ext/repair/checkfreelist.c \
|
||||
$(TOP)/ext/misc/btreeinfo.c \
|
||||
$(TOP)/ext/repair/sqlite3_checker.c.in
|
||||
$(TOP)\src\tclsqlite.c \
|
||||
$(TOP)\ext\repair\sqlite3_checker.tcl \
|
||||
$(TOP)\ext\repair\checkindex.c \
|
||||
$(TOP)\ext\repair\checkfreelist.c \
|
||||
$(TOP)\ext\misc\btreeinfo.c \
|
||||
$(TOP)\ext\repair\sqlite3_checker.c.in
|
||||
|
||||
sqlite3_checker.c: $(CHECKER_DEPS)
|
||||
$(TCLSH_CMD) $(TOP)\tool\mkccode.tcl $(TOP)\ext\repair\sqlite3_checker.c.in > $@
|
||||
@@ -2661,4 +2698,5 @@ clean:
|
||||
del /Q showshm.exe sqlite3_checker.* sqlite3_expert.exe 2>NUL
|
||||
del /Q fts5.* fts5parse.* 2>NUL
|
||||
del /Q lsm.h lsm1.c 2>NUL
|
||||
del /q src-verify.exe 2>NUL
|
||||
# <</mark>>
|
||||
|
||||
@@ -25,7 +25,28 @@ If you pulled your SQLite source code from a secondary source and want to
|
||||
verify its integrity, there are hints on how to do that in the
|
||||
[Verifying Code Authenticity](#vauth) section below.
|
||||
|
||||
## Obtaining The Code
|
||||
## Contacting The SQLite Developers
|
||||
|
||||
The preferred way to ask questions or make comments about SQLite or to
|
||||
report bugs against SQLite is to visit the
|
||||
[SQLite Forum](https://sqlite.org/forum) at <https://sqlite.org/forum/>.
|
||||
Anonymous postings are permitted.
|
||||
|
||||
If you think you have found a bug that has security implications and
|
||||
you do not want to report it on the public forum, you can send a private
|
||||
email to drh at sqlite dot org.
|
||||
|
||||
## Public Domain
|
||||
|
||||
The SQLite source code is in the public domain. See
|
||||
<https://sqlite.org/copyright.html> for details.
|
||||
|
||||
Because SQLite is in the public domain, we do not normally accept pull
|
||||
requests, because if we did take a pull request, the changes in that
|
||||
pull request might carry a copyright and the SQLite source code would
|
||||
then no longer be fully in the public domain.
|
||||
|
||||
## Obtaining The SQLite Source Code
|
||||
|
||||
If you do not want to use Fossil, you can download tarballs or ZIP
|
||||
archives or [SQLite archives](https://sqlite.org/cli.html#sqlar) as follows:
|
||||
@@ -82,9 +103,9 @@ For example:
|
||||
mkdir bld ;# Build will occur in a sibling directory
|
||||
cd bld ;# Change to the build directory
|
||||
../sqlite/configure ;# Run the configure script
|
||||
make ;# Run the makefile.
|
||||
make ;# Builds the "sqlite3" command-line tool
|
||||
make sqlite3.c ;# Build the "amalgamation" source file
|
||||
make test ;# Run some tests (requires Tcl)
|
||||
make devtest ;# Run some tests (requires Tcl)
|
||||
|
||||
See the makefile for additional targets.
|
||||
|
||||
@@ -94,32 +115,33 @@ script does not work out for you, there is a generic makefile named
|
||||
can copy and edit to suit your needs. Comments on the generic makefile
|
||||
show what changes are needed.
|
||||
|
||||
## Using MSVC for Windows systems
|
||||
## Compiling for Windows Using MSVC
|
||||
|
||||
On Windows, all applicable build products can be compiled with MSVC.
|
||||
First open the command prompt window associated with the desired compiler
|
||||
version (e.g. "Developer Command Prompt for VS2013"). Next, use NMAKE
|
||||
with the provided "Makefile.msc" to build one of the supported targets.
|
||||
You will also need a working installation of TCL.
|
||||
See the [compile-for-windows.md](doc/compile-for-windows.md) document for
|
||||
additional information about how to install MSVC and TCL and configure your
|
||||
build environment.
|
||||
|
||||
For example, from the parent directory of the source subtree named "sqlite":
|
||||
If you want to run tests, you need to let SQLite know the location of your
|
||||
TCL library, using a command like this:
|
||||
|
||||
mkdir bld
|
||||
cd bld
|
||||
nmake /f ..\sqlite\Makefile.msc TOP=..\sqlite
|
||||
nmake /f ..\sqlite\Makefile.msc sqlite3.c TOP=..\sqlite
|
||||
nmake /f ..\sqlite\Makefile.msc sqlite3.dll TOP=..\sqlite
|
||||
nmake /f ..\sqlite\Makefile.msc sqlite3.exe TOP=..\sqlite
|
||||
nmake /f ..\sqlite\Makefile.msc test TOP=..\sqlite
|
||||
set TCLDIR=c:\Tcl
|
||||
|
||||
There are several build options that can be set via the NMAKE command
|
||||
line. For example, to build for WinRT, simply add "FOR_WINRT=1" argument
|
||||
to the "sqlite3.dll" command line above. When debugging into the SQLite
|
||||
code, adding the "DEBUG=1" argument to one of the above command lines is
|
||||
recommended.
|
||||
SQLite uses "tclsh.exe" as part of the build process, and so that utility
|
||||
program will need to be somewhere on your %PATH%. The finished SQLite library
|
||||
does not contain any TCL code, but it does use TCL to help with the build process
|
||||
and to run tests.
|
||||
|
||||
SQLite does not require [Tcl](http://www.tcl.tk/) to run, but a Tcl installation
|
||||
is required by the makefiles (including those for MSVC). SQLite contains
|
||||
a lot of generated code and Tcl is used to do much of that code generation.
|
||||
Build using Makefile.msc. Example:
|
||||
|
||||
nmake /f Makefile.msc
|
||||
nmake /f Makefile.msc sqlite3.c
|
||||
nmake /f Makefile.msc devtest
|
||||
nmake /f Makefile.msc releasetest
|
||||
|
||||
There are many other makefile targets. See comments in Makefile.msc for
|
||||
details.
|
||||
|
||||
## Source Code Tour
|
||||
|
||||
@@ -290,16 +312,13 @@ Key files:
|
||||
is not part of the core SQLite library. But as most of the tests in this
|
||||
repository are written in Tcl, the Tcl language bindings are important.
|
||||
|
||||
* **test*.c** - Files in the src/ folder that begin with "test" go into
|
||||
* **test\*.c** - Files in the src/ folder that begin with "test" go into
|
||||
building the "testfixture.exe" program. The testfixture.exe program is
|
||||
an enhanced Tcl shell. The testfixture.exe program runs scripts in the
|
||||
test/ folder to validate the core SQLite code. The testfixture program
|
||||
(and some other test programs too) is built and run when you type
|
||||
"make test".
|
||||
|
||||
* **ext/misc/json1.c** - This file implements the various JSON functions
|
||||
that are built into SQLite.
|
||||
|
||||
There are many other source files. Each has a succinct header comment that
|
||||
describes its purpose and role within the larger system.
|
||||
|
||||
@@ -307,8 +326,8 @@ describes its purpose and role within the larger system.
|
||||
## Verifying Code Authenticity
|
||||
|
||||
The `manifest` file at the root directory of the source tree
|
||||
contains either a SHA3-256 hash (for newer files) or a SHA1 hash (for
|
||||
older files) for every source file in the repository.
|
||||
contains either a SHA3-256 hash or a SHA1 hash
|
||||
for every source file in the repository.
|
||||
The name of the version of the entire source tree is just the
|
||||
SHA3-256 hash of the `manifest` file itself, possibly with the
|
||||
last line of that file omitted if the last line begins with
|
||||
@@ -316,14 +335,25 @@ last line of that file omitted if the last line begins with
|
||||
The `manifest.uuid` file should contain the SHA3-256 hash of the
|
||||
`manifest` file. If all of the above hash comparisons are correct, then
|
||||
you can be confident that your source tree is authentic and unadulterated.
|
||||
Details on the format for the `manifest` files are available
|
||||
[on the Fossil website](https://fossil-scm.org/fossil/doc/trunk/www/fileformat.wiki#manifest).
|
||||
|
||||
The format of the `manifest` file should be mostly self-explanatory, but
|
||||
if you want details, they are available
|
||||
[here](https://fossil-scm.org/fossil/doc/trunk/www/fileformat.wiki#manifest).
|
||||
The process of checking source code authenticity is automated by the
|
||||
makefile:
|
||||
|
||||
> make verify-source
|
||||
|
||||
Or on windows:
|
||||
|
||||
> nmake /f Makefile.msc verify-source
|
||||
|
||||
Using the makefile to verify source integrity is good for detecting
|
||||
accidental changes to the source tree, but malicious changes could be
|
||||
hidden by also modifying the makefiles.
|
||||
|
||||
## Contacts
|
||||
|
||||
The main SQLite website is [http://www.sqlite.org/](http://www.sqlite.org/)
|
||||
The main SQLite website is [http:/sqlite.org/](http://sqlite.org/)
|
||||
with geographically distributed backups at
|
||||
[http://www2.sqlite.org/](http://www2.sqlite.org) and
|
||||
[http://www3.sqlite.org/](http://www3.sqlite.org).
|
||||
|
||||
@@ -9,7 +9,7 @@ sqlite3_SOURCES = shell.c sqlite3.h
|
||||
EXTRA_sqlite3_SOURCES = sqlite3.c
|
||||
sqlite3_LDADD = @EXTRA_SHELL_OBJ@ @READLINE_LIBS@
|
||||
sqlite3_DEPENDENCIES = @EXTRA_SHELL_OBJ@
|
||||
sqlite3_CFLAGS = $(AM_CFLAGS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_ENABLE_STMTVTAB -DSQLITE_ENABLE_DBSTAT_VTAB $(SHELL_CFLAGS)
|
||||
sqlite3_CFLAGS = $(AM_CFLAGS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_DQS=0 -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_ENABLE_STMTVTAB -DSQLITE_ENABLE_DBSTAT_VTAB $(SHELL_CFLAGS)
|
||||
|
||||
include_HEADERS = sqlite3.h sqlite3ext.h
|
||||
|
||||
|
||||
@@ -52,6 +52,13 @@ MINIMAL_AMALGAMATION = 0
|
||||
USE_STDCALL = 0
|
||||
!ENDIF
|
||||
|
||||
# Set this non-0 to use structured exception handling (SEH) for WAL mode
|
||||
# in the core library.
|
||||
#
|
||||
!IFNDEF USE_SEH
|
||||
USE_SEH = 1
|
||||
!ENDIF
|
||||
|
||||
# Set this non-0 to have the shell executable link against the core dynamic
|
||||
# link library.
|
||||
#
|
||||
@@ -180,6 +187,12 @@ WIN32HEAP = 0
|
||||
OSTRACE = 0
|
||||
!ENDIF
|
||||
|
||||
# enable address sanitizer using ASAN=1 on the command-line.
|
||||
#
|
||||
!IFNDEF ASAN
|
||||
ASAN = 0
|
||||
!ENDIF
|
||||
|
||||
# Set this to one of the following values to enable various debugging
|
||||
# features. Each level includes the debugging options from the previous
|
||||
# levels. Currently, the recognized values for DEBUG are:
|
||||
@@ -311,6 +324,13 @@ OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_MATH_FUNCTIONS
|
||||
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RBU=1
|
||||
!ENDIF
|
||||
|
||||
# Should structured exception handling (SEH) be enabled for WAL mode in
|
||||
# the core library?
|
||||
#
|
||||
!IF $(USE_SEH)!=0
|
||||
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_USE_SEH=1
|
||||
!ENDIF
|
||||
|
||||
# These are the "extended" SQLite compilation options used when compiling for
|
||||
# the Windows 10 platform.
|
||||
#
|
||||
@@ -718,6 +738,13 @@ RCC = $(RCC) -DSQLITE_WIN32_MALLOC_VALIDATE=1
|
||||
!ENDIF
|
||||
|
||||
|
||||
# Address sanitizer if ASAN=1
|
||||
#
|
||||
!IF $(ASAN)>0
|
||||
TCC = $(TCC) /fsanitize=address
|
||||
!ENDIF
|
||||
|
||||
|
||||
# Compiler options needed for programs that use the readline() library.
|
||||
#
|
||||
!IFNDEF READLINE_FLAGS
|
||||
|
||||
@@ -19,7 +19,7 @@ dnl to configure the system for the local environment.
|
||||
# so that we create the export library with the dll.
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
AC_INIT([sqlite],[3.42.0])
|
||||
AC_INIT([sqlite],[3.43.0])
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# Call TEA_INIT as the first TEA_ macro to set up initial vars.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#! /bin/sh
|
||||
# Guess values for system-dependent variables and create Makefiles.
|
||||
# Generated by GNU Autoconf 2.69 for sqlite 3.42.0.
|
||||
# Generated by GNU Autoconf 2.69 for sqlite 3.43.1.
|
||||
#
|
||||
#
|
||||
# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
|
||||
@@ -726,8 +726,8 @@ MAKEFLAGS=
|
||||
# Identity of this package.
|
||||
PACKAGE_NAME='sqlite'
|
||||
PACKAGE_TARNAME='sqlite'
|
||||
PACKAGE_VERSION='3.42.0'
|
||||
PACKAGE_STRING='sqlite 3.42.0'
|
||||
PACKAGE_VERSION='3.43.1'
|
||||
PACKAGE_STRING='sqlite 3.43.1'
|
||||
PACKAGE_BUGREPORT=''
|
||||
PACKAGE_URL=''
|
||||
|
||||
@@ -1470,7 +1470,7 @@ if test "$ac_init_help" = "long"; then
|
||||
# Omit some internal or obsolete options to make the list less imposing.
|
||||
# This message is too long to be a string in the A/UX 3.1 sh.
|
||||
cat <<_ACEOF
|
||||
\`configure' configures sqlite 3.42.0 to adapt to many kinds of systems.
|
||||
\`configure' configures sqlite 3.43.1 to adapt to many kinds of systems.
|
||||
|
||||
Usage: $0 [OPTION]... [VAR=VALUE]...
|
||||
|
||||
@@ -1535,7 +1535,7 @@ fi
|
||||
|
||||
if test -n "$ac_init_help"; then
|
||||
case $ac_init_help in
|
||||
short | recursive ) echo "Configuration of sqlite 3.42.0:";;
|
||||
short | recursive ) echo "Configuration of sqlite 3.43.1:";;
|
||||
esac
|
||||
cat <<\_ACEOF
|
||||
|
||||
@@ -1665,7 +1665,7 @@ fi
|
||||
test -n "$ac_init_help" && exit $ac_status
|
||||
if $ac_init_version; then
|
||||
cat <<\_ACEOF
|
||||
sqlite configure 3.42.0
|
||||
sqlite configure 3.43.1
|
||||
generated by GNU Autoconf 2.69
|
||||
|
||||
Copyright (C) 2012 Free Software Foundation, Inc.
|
||||
@@ -2084,7 +2084,7 @@ cat >config.log <<_ACEOF
|
||||
This file contains any messages produced by compilers while
|
||||
running configure, to aid debugging if configure makes a mistake.
|
||||
|
||||
It was created by sqlite $as_me 3.42.0, which was
|
||||
It was created by sqlite $as_me 3.43.1, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
$ $0 $@
|
||||
@@ -12457,7 +12457,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
|
||||
# report actual input values of CONFIG_FILES etc. instead of their
|
||||
# values after options handling.
|
||||
ac_log="
|
||||
This file was extended by sqlite $as_me 3.42.0, which was
|
||||
This file was extended by sqlite $as_me 3.43.1, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
CONFIG_FILES = $CONFIG_FILES
|
||||
@@ -12523,7 +12523,7 @@ _ACEOF
|
||||
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
|
||||
ac_cs_version="\\
|
||||
sqlite config.status 3.42.0
|
||||
sqlite config.status 3.43.1
|
||||
configured by $0, generated by GNU Autoconf 2.69,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
# Notes On Compiling SQLite On Windows 11
|
||||
|
||||
Here are step-by-step instructions on how to build SQLite from
|
||||
canonical source on a new Windows 11 PC, as of 2023-08-16:
|
||||
|
||||
1. Install Microsoft Visual Studio. The free "community edition"
|
||||
will work fine. Do a standard install for C++ development.
|
||||
SQLite only needs the
|
||||
"cl" compiler and the "nmake" build tool.
|
||||
|
||||
2. Under the "Start" menu, find "All Apps" then go to "Visual Studio 20XX"
|
||||
and find "x64 Native Tools Command Prompt for VS 20XX". Pin that
|
||||
application to your task bar, as you will use it a lot. Bring up
|
||||
an instance of this command prompt and do all of the subsequent steps
|
||||
in that "x64 Native Tools" command prompt. (Or use "x86" if you want
|
||||
a 32-bit build.) The subsequent steps will not work in a vanilla
|
||||
DOS prompt. Nor will they work in PowerShell.
|
||||
|
||||
3. Install TCL development libraries. This note assumes that you wil
|
||||
install the TCL development libraries in the "`c:\Tcl`" directory.
|
||||
Make adjustments
|
||||
if you want TCL installed somewhere else. SQLite needs both the
|
||||
"tclsh.exe" command-line tool as part of the build process, and
|
||||
the "tcl86.lib" library in order to run tests. You will need
|
||||
TCL version 8.6 or later.
|
||||
<ol type="a">
|
||||
<li>Get the TCL source archive, perhaps from
|
||||
[https://www.tcl.tk/software/tcltk/download.html](https://www.tcl.tk/software/tcltk/download.html).
|
||||
<li>Untar or unzip the source archive. CD into the "win/" subfolder
|
||||
of the source tree.
|
||||
<li>Run: `nmake /f makefile.vc release`
|
||||
<li>Run: `nmake /f makefile.vc INSTALLDIR=c:\Tcl install`
|
||||
<li>CD to `c:\Tcl\lib`. In that subfolder make a copy of the
|
||||
"`tcl86t.lib`" file to the alternative name "`tcl86.lib`"
|
||||
(omitting the second 't'). Leave the copy in the same directory
|
||||
as the original.
|
||||
<li>CD to `c:\Tcl\bin`. Make a copy of the "`tclsh86t.exe`"
|
||||
file into "`tclsh.exe`" (without the "86t") in the same directory.
|
||||
<li>Add `c:\Tcl\bin` to your %PATH%. To do this, go to Settings
|
||||
and search for "path". Select "edit environment variables for
|
||||
your account" and modify your default PATH accordingly.
|
||||
You will need to close and reopen your command prompts after
|
||||
making this change.
|
||||
</ol>
|
||||
|
||||
4. Download the SQLite source tree and unpack it. CD into the
|
||||
toplevel directory of the source tree.
|
||||
|
||||
5. Set the TCLDIR environment variable to point to your TCL installation.
|
||||
Like this:
|
||||
<ul>
|
||||
<li> `set TCLDIR=c:\Tcl`
|
||||
</ul>
|
||||
|
||||
6. Run the "`Makefile.msc`" makefile with an appropriate target.
|
||||
Examples:
|
||||
<ul>
|
||||
<li> `nmake /f makefile.msc`
|
||||
<li> `nmake /f makefile.msc sqlite3.c`
|
||||
<li> `nmake /f makefile.msc devtest`
|
||||
<li> `nmake /f makefile.msc releasetest`
|
||||
</ul>
|
||||
|
||||
## 32-bit Builds
|
||||
|
||||
Doing a 32-bit build is just like doing a 64-bit build with the
|
||||
following minor changes:
|
||||
|
||||
1. Use the "x86 Native Tools Command Prompt" instead of
|
||||
"x64 Native Tools Command Prompt". "**x86**" instead of "**x64**".
|
||||
|
||||
2. Use a different installation directory for TCL.
|
||||
The recommended directory is `c:\tcl32`. Thus you end up
|
||||
with two TCL builds:
|
||||
<ul>
|
||||
<li> `c:\tcl` ← 64-bit (the default)
|
||||
<li> `c:\tcl32` ← 32-bit
|
||||
</ul>
|
||||
|
||||
3. Ensure that `c:\tcl32\bin` comes before `c:\tcl\bin` on
|
||||
your PATH environment variable. You can achieve this using
|
||||
a command like:
|
||||
<ul>
|
||||
<li> `set PATH=c:\tcl32\bin;%PATH%`
|
||||
</ul>
|
||||
+3
-3
@@ -322,7 +322,7 @@ These are the steps:
|
||||
<ul>
|
||||
<li> Declare a local variable of type "yyParser"
|
||||
<li> Initialize the variable using ParseInit()
|
||||
<li> Pass a pointer to the variable in calls ot Parse()
|
||||
<li> Pass a pointer to the variable in calls to Parse()
|
||||
<li> Deallocate substructure in the parse variable using ParseFinalize().
|
||||
</ul>
|
||||
|
||||
@@ -1229,8 +1229,8 @@ first syntax error, of course, if there are no instances of the
|
||||
|
||||
<p>Lemon was originally written by Richard Hipp sometime in the late
|
||||
1980s on a Sun4 Workstation using K&R C.
|
||||
There was a companion LL(1) parser generator program named "Lime", the
|
||||
source code to which as been lost.</p>
|
||||
There was a companion LL(1) parser generator program named "Lime".
|
||||
The Lime source code has been lost.</p>
|
||||
|
||||
<p>The lemon.c source file was originally many separate files that were
|
||||
compiled together to generate the "lemon" executable. Sometime in the
|
||||
|
||||
@@ -4347,6 +4347,7 @@ static int fts3IncrmergeLoad(
|
||||
|
||||
for(i=nHeight; i>=0 && rc==SQLITE_OK; i--){
|
||||
NodeReader reader;
|
||||
memset(&reader, 0, sizeof(reader));
|
||||
pNode = &pWriter->aNodeWriter[i];
|
||||
|
||||
if( pNode->block.a){
|
||||
@@ -5217,7 +5218,7 @@ static u64 fts3ChecksumIndex(
|
||||
int rc;
|
||||
u64 cksum = 0;
|
||||
|
||||
assert( *pRc==SQLITE_OK );
|
||||
if( *pRc ) return 0;
|
||||
|
||||
memset(&filter, 0, sizeof(filter));
|
||||
memset(&csr, 0, sizeof(csr));
|
||||
|
||||
+6
-6
@@ -263,7 +263,7 @@ struct Fts5PhraseIter {
|
||||
** See xPhraseFirstColumn above.
|
||||
*/
|
||||
struct Fts5ExtensionApi {
|
||||
int iVersion; /* Currently always set to 3 */
|
||||
int iVersion; /* Currently always set to 2 */
|
||||
|
||||
void *(*xUserData)(Fts5Context*);
|
||||
|
||||
@@ -492,8 +492,8 @@ struct Fts5ExtensionApi {
|
||||
** as separate queries of the FTS index are required for each synonym.
|
||||
**
|
||||
** When using methods (2) or (3), it is important that the tokenizer only
|
||||
** provide synonyms when tokenizing document text (method (2)) or query
|
||||
** text (method (3)), not both. Doing so will not cause any errors, but is
|
||||
** provide synonyms when tokenizing document text (method (3)) or query
|
||||
** text (method (2)), not both. Doing so will not cause any errors, but is
|
||||
** inefficient.
|
||||
*/
|
||||
typedef struct Fts5Tokenizer Fts5Tokenizer;
|
||||
@@ -541,7 +541,7 @@ struct fts5_api {
|
||||
int (*xCreateTokenizer)(
|
||||
fts5_api *pApi,
|
||||
const char *zName,
|
||||
void *pContext,
|
||||
void *pUserData,
|
||||
fts5_tokenizer *pTokenizer,
|
||||
void (*xDestroy)(void*)
|
||||
);
|
||||
@@ -550,7 +550,7 @@ struct fts5_api {
|
||||
int (*xFindTokenizer)(
|
||||
fts5_api *pApi,
|
||||
const char *zName,
|
||||
void **ppContext,
|
||||
void **ppUserData,
|
||||
fts5_tokenizer *pTokenizer
|
||||
);
|
||||
|
||||
@@ -558,7 +558,7 @@ struct fts5_api {
|
||||
int (*xCreateFunction)(
|
||||
fts5_api *pApi,
|
||||
const char *zName,
|
||||
void *pContext,
|
||||
void *pUserData,
|
||||
fts5_extension_function xFunction,
|
||||
void (*xDestroy)(void*)
|
||||
);
|
||||
|
||||
+16
-1
@@ -154,6 +154,10 @@ typedef struct Fts5Config Fts5Config;
|
||||
** attempt to merge together. A value of 1 sets the object to use the
|
||||
** compile time default. Zero disables auto-merge altogether.
|
||||
**
|
||||
** bContentlessDelete:
|
||||
** True if the contentless_delete option was present in the CREATE
|
||||
** VIRTUAL TABLE statement.
|
||||
**
|
||||
** zContent:
|
||||
**
|
||||
** zContentRowid:
|
||||
@@ -188,6 +192,7 @@ struct Fts5Config {
|
||||
int nPrefix; /* Number of prefix indexes */
|
||||
int *aPrefix; /* Sizes in bytes of nPrefix prefix indexes */
|
||||
int eContent; /* An FTS5_CONTENT value */
|
||||
int bContentlessDelete; /* "contentless_delete=" option (dflt==0) */
|
||||
char *zContent; /* content table */
|
||||
char *zContentRowid; /* "content_rowid=" option value */
|
||||
int bColumnsize; /* "columnsize=" option value (dflt==1) */
|
||||
@@ -209,6 +214,7 @@ struct Fts5Config {
|
||||
char *zRank; /* Name of rank function */
|
||||
char *zRankArgs; /* Arguments to rank function */
|
||||
int bSecureDelete; /* 'secure-delete' */
|
||||
int nDeleteMerge; /* 'deletemerge' */
|
||||
|
||||
/* If non-NULL, points to sqlite3_vtab.base.zErrmsg. Often NULL. */
|
||||
char **pzErrmsg;
|
||||
@@ -531,6 +537,9 @@ int sqlite3Fts5IndexReset(Fts5Index *p);
|
||||
|
||||
int sqlite3Fts5IndexLoadConfig(Fts5Index *p);
|
||||
|
||||
int sqlite3Fts5IndexGetOrigin(Fts5Index *p, i64 *piOrigin);
|
||||
int sqlite3Fts5IndexContentlessDelete(Fts5Index *p, i64 iOrigin, i64 iRowid);
|
||||
|
||||
/*
|
||||
** End of interface to code in fts5_index.c.
|
||||
**************************************************************************/
|
||||
@@ -543,7 +552,7 @@ int sqlite3Fts5GetVarintLen(u32 iVal);
|
||||
u8 sqlite3Fts5GetVarint(const unsigned char*, u64*);
|
||||
int sqlite3Fts5PutVarint(unsigned char *p, u64 v);
|
||||
|
||||
#define fts5GetVarint32(a,b) sqlite3Fts5GetVarint32(a,(u32*)&b)
|
||||
#define fts5GetVarint32(a,b) sqlite3Fts5GetVarint32(a,(u32*)&(b))
|
||||
#define fts5GetVarint sqlite3Fts5GetVarint
|
||||
|
||||
#define fts5FastGetVarint32(a, iOff, nVal) { \
|
||||
@@ -615,6 +624,11 @@ int sqlite3Fts5HashWrite(
|
||||
*/
|
||||
void sqlite3Fts5HashClear(Fts5Hash*);
|
||||
|
||||
/*
|
||||
** Return true if the hash is empty, false otherwise.
|
||||
*/
|
||||
int sqlite3Fts5HashIsEmpty(Fts5Hash*);
|
||||
|
||||
int sqlite3Fts5HashQuery(
|
||||
Fts5Hash*, /* Hash table to query */
|
||||
int nPre,
|
||||
@@ -636,6 +650,7 @@ void sqlite3Fts5HashScanEntry(Fts5Hash *,
|
||||
);
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** End of interface to code in fts5_hash.c.
|
||||
**************************************************************************/
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#define FTS5_DEFAULT_CRISISMERGE 16
|
||||
#define FTS5_DEFAULT_HASHSIZE (1024*1024)
|
||||
|
||||
#define FTS5_DEFAULT_DELETE_AUTOMERGE 10 /* default 10% */
|
||||
|
||||
/* Maximum allowed page size */
|
||||
#define FTS5_MAX_PAGE_SIZE (64*1024)
|
||||
|
||||
@@ -352,6 +354,16 @@ static int fts5ConfigParseSpecial(
|
||||
return rc;
|
||||
}
|
||||
|
||||
if( sqlite3_strnicmp("contentless_delete", zCmd, nCmd)==0 ){
|
||||
if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1]!='\0' ){
|
||||
*pzErr = sqlite3_mprintf("malformed contentless_delete=... directive");
|
||||
rc = SQLITE_ERROR;
|
||||
}else{
|
||||
pConfig->bContentlessDelete = (zArg[0]=='1');
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
if( sqlite3_strnicmp("content_rowid", zCmd, nCmd)==0 ){
|
||||
if( pConfig->zContentRowid ){
|
||||
*pzErr = sqlite3_mprintf("multiple content_rowid=... directives");
|
||||
@@ -550,6 +562,7 @@ int sqlite3Fts5ConfigParse(
|
||||
rc = SQLITE_ERROR;
|
||||
}
|
||||
|
||||
assert( (pRet->abUnindexed && pRet->azCol) || rc!=SQLITE_OK );
|
||||
for(i=3; rc==SQLITE_OK && i<nArg; i++){
|
||||
const char *zOrig = azArg[i];
|
||||
const char *z;
|
||||
@@ -595,6 +608,28 @@ int sqlite3Fts5ConfigParse(
|
||||
sqlite3_free(zTwo);
|
||||
}
|
||||
|
||||
/* We only allow contentless_delete=1 if the table is indeed contentless. */
|
||||
if( rc==SQLITE_OK
|
||||
&& pRet->bContentlessDelete
|
||||
&& pRet->eContent!=FTS5_CONTENT_NONE
|
||||
){
|
||||
*pzErr = sqlite3_mprintf(
|
||||
"contentless_delete=1 requires a contentless table"
|
||||
);
|
||||
rc = SQLITE_ERROR;
|
||||
}
|
||||
|
||||
/* We only allow contentless_delete=1 if columnsize=0 is not present.
|
||||
**
|
||||
** This restriction may be removed at some point.
|
||||
*/
|
||||
if( rc==SQLITE_OK && pRet->bContentlessDelete && pRet->bColumnsize==0 ){
|
||||
*pzErr = sqlite3_mprintf(
|
||||
"contentless_delete=1 is incompatible with columnsize=0"
|
||||
);
|
||||
rc = SQLITE_ERROR;
|
||||
}
|
||||
|
||||
/* If a tokenizer= option was successfully parsed, the tokenizer has
|
||||
** already been allocated. Otherwise, allocate an instance of the default
|
||||
** tokenizer (unicode61) now. */
|
||||
@@ -889,6 +924,18 @@ int sqlite3Fts5ConfigSetValue(
|
||||
}
|
||||
}
|
||||
|
||||
else if( 0==sqlite3_stricmp(zKey, "deletemerge") ){
|
||||
int nVal = -1;
|
||||
if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
|
||||
nVal = sqlite3_value_int(pVal);
|
||||
}else{
|
||||
*pbBadkey = 1;
|
||||
}
|
||||
if( nVal<0 ) nVal = FTS5_DEFAULT_DELETE_AUTOMERGE;
|
||||
if( nVal>100 ) nVal = 0;
|
||||
pConfig->nDeleteMerge = nVal;
|
||||
}
|
||||
|
||||
else if( 0==sqlite3_stricmp(zKey, "rank") ){
|
||||
const char *zIn = (const char*)sqlite3_value_text(pVal);
|
||||
char *zRank;
|
||||
@@ -937,6 +984,7 @@ int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){
|
||||
pConfig->nUsermerge = FTS5_DEFAULT_USERMERGE;
|
||||
pConfig->nCrisisMerge = FTS5_DEFAULT_CRISISMERGE;
|
||||
pConfig->nHashSize = FTS5_DEFAULT_HASHSIZE;
|
||||
pConfig->nDeleteMerge = FTS5_DEFAULT_DELETE_AUTOMERGE;
|
||||
|
||||
zSql = sqlite3Fts5Mprintf(&rc, zSelect, pConfig->zDb, pConfig->zName);
|
||||
if( zSql ){
|
||||
|
||||
+57
-5
@@ -17,6 +17,10 @@
|
||||
#include "fts5Int.h"
|
||||
#include "fts5parse.h"
|
||||
|
||||
#ifndef SQLITE_FTS5_MAX_EXPR_DEPTH
|
||||
# define SQLITE_FTS5_MAX_EXPR_DEPTH 256
|
||||
#endif
|
||||
|
||||
/*
|
||||
** All token types in the generated fts5parse.h file are greater than 0.
|
||||
*/
|
||||
@@ -57,11 +61,17 @@ struct Fts5Expr {
|
||||
** FTS5_NOT (nChild, apChild valid)
|
||||
** FTS5_STRING (pNear valid)
|
||||
** FTS5_TERM (pNear valid)
|
||||
**
|
||||
** iHeight:
|
||||
** Distance from this node to furthest leaf. This is always 0 for nodes
|
||||
** of type FTS5_STRING and FTS5_TERM. For all other nodes it is one
|
||||
** greater than the largest child value.
|
||||
*/
|
||||
struct Fts5ExprNode {
|
||||
int eType; /* Node type */
|
||||
int bEof; /* True at EOF */
|
||||
int bNomatch; /* True if entry is not a match */
|
||||
int iHeight; /* Distance to tree leaf nodes */
|
||||
|
||||
/* Next method for this node. */
|
||||
int (*xNext)(Fts5Expr*, Fts5ExprNode*, int, i64);
|
||||
@@ -131,6 +141,31 @@ struct Fts5Parse {
|
||||
int bPhraseToAnd; /* Convert "a+b" to "a AND b" */
|
||||
};
|
||||
|
||||
/*
|
||||
** Check that the Fts5ExprNode.iHeight variables are set correctly in
|
||||
** the expression tree passed as the only argument.
|
||||
*/
|
||||
#ifndef NDEBUG
|
||||
static void assert_expr_depth_ok(int rc, Fts5ExprNode *p){
|
||||
if( rc==SQLITE_OK ){
|
||||
if( p->eType==FTS5_TERM || p->eType==FTS5_STRING || p->eType==0 ){
|
||||
assert( p->iHeight==0 );
|
||||
}else{
|
||||
int ii;
|
||||
int iMaxChild = 0;
|
||||
for(ii=0; ii<p->nChild; ii++){
|
||||
Fts5ExprNode *pChild = p->apChild[ii];
|
||||
iMaxChild = MAX(iMaxChild, pChild->iHeight);
|
||||
assert_expr_depth_ok(SQLITE_OK, pChild);
|
||||
}
|
||||
assert( p->iHeight==iMaxChild+1 );
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
# define assert_expr_depth_ok(rc, p)
|
||||
#endif
|
||||
|
||||
void sqlite3Fts5ParseError(Fts5Parse *pParse, const char *zFmt, ...){
|
||||
va_list ap;
|
||||
va_start(ap, zFmt);
|
||||
@@ -245,6 +280,8 @@ int sqlite3Fts5ExprNew(
|
||||
}while( sParse.rc==SQLITE_OK && t!=FTS5_EOF );
|
||||
sqlite3Fts5ParserFree(pEngine, fts5ParseFree);
|
||||
|
||||
assert_expr_depth_ok(sParse.rc, sParse.pExpr);
|
||||
|
||||
/* If the LHS of the MATCH expression was a user column, apply the
|
||||
** implicit column-filter. */
|
||||
if( iCol<pConfig->nCol && sParse.pExpr && sParse.rc==SQLITE_OK ){
|
||||
@@ -407,7 +444,7 @@ int sqlite3Fts5ExprAnd(Fts5Expr **pp1, Fts5Expr *p2){
|
||||
Fts5Parse sParse;
|
||||
memset(&sParse, 0, sizeof(sParse));
|
||||
|
||||
if( *pp1 ){
|
||||
if( *pp1 && p2 ){
|
||||
Fts5Expr *p1 = *pp1;
|
||||
int nPhrase = p1->nPhrase + p2->nPhrase;
|
||||
|
||||
@@ -432,7 +469,7 @@ int sqlite3Fts5ExprAnd(Fts5Expr **pp1, Fts5Expr *p2){
|
||||
}
|
||||
sqlite3_free(p2->apExprPhrase);
|
||||
sqlite3_free(p2);
|
||||
}else{
|
||||
}else if( p2 ){
|
||||
*pp1 = p2;
|
||||
}
|
||||
|
||||
@@ -2206,6 +2243,7 @@ static void fts5ExprAssignXNext(Fts5ExprNode *pNode){
|
||||
}
|
||||
|
||||
static void fts5ExprAddChildren(Fts5ExprNode *p, Fts5ExprNode *pSub){
|
||||
int ii = p->nChild;
|
||||
if( p->eType!=FTS5_NOT && pSub->eType==p->eType ){
|
||||
int nByte = sizeof(Fts5ExprNode*) * pSub->nChild;
|
||||
memcpy(&p->apChild[p->nChild], pSub->apChild, nByte);
|
||||
@@ -2214,6 +2252,9 @@ static void fts5ExprAddChildren(Fts5ExprNode *p, Fts5ExprNode *pSub){
|
||||
}else{
|
||||
p->apChild[p->nChild++] = pSub;
|
||||
}
|
||||
for( ; ii<p->nChild; ii++){
|
||||
p->iHeight = MAX(p->iHeight, p->apChild[ii]->iHeight + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2244,6 +2285,7 @@ static Fts5ExprNode *fts5ParsePhraseToAnd(
|
||||
if( pRet ){
|
||||
pRet->eType = FTS5_AND;
|
||||
pRet->nChild = nTerm;
|
||||
pRet->iHeight = 1;
|
||||
fts5ExprAssignXNext(pRet);
|
||||
pParse->nPhrase--;
|
||||
for(ii=0; ii<nTerm; ii++){
|
||||
@@ -2349,6 +2391,14 @@ Fts5ExprNode *sqlite3Fts5ParseNode(
|
||||
}else{
|
||||
fts5ExprAddChildren(pRet, pLeft);
|
||||
fts5ExprAddChildren(pRet, pRight);
|
||||
if( pRet->iHeight>SQLITE_FTS5_MAX_EXPR_DEPTH ){
|
||||
sqlite3Fts5ParseError(pParse,
|
||||
"fts5 expression tree is too large (maximum depth %d)",
|
||||
SQLITE_FTS5_MAX_EXPR_DEPTH
|
||||
);
|
||||
sqlite3_free(pRet);
|
||||
pRet = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2427,7 +2477,7 @@ Fts5ExprNode *sqlite3Fts5ParseImplicitAnd(
|
||||
return pRet;
|
||||
}
|
||||
|
||||
#ifdef SQLITE_TEST
|
||||
#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
|
||||
static char *fts5ExprTermPrint(Fts5ExprTerm *pTerm){
|
||||
sqlite3_int64 nByte = 0;
|
||||
Fts5ExprTerm *p;
|
||||
@@ -2533,6 +2583,8 @@ static char *fts5ExprPrintTcl(
|
||||
if( zRet==0 ) return 0;
|
||||
}
|
||||
|
||||
}else if( pExpr->eType==0 ){
|
||||
zRet = sqlite3_mprintf("{}");
|
||||
}else{
|
||||
char const *zOp = 0;
|
||||
int i;
|
||||
@@ -2794,14 +2846,14 @@ static void fts5ExprFold(
|
||||
sqlite3_result_int(pCtx, sqlite3Fts5UnicodeFold(iCode, bRemoveDiacritics));
|
||||
}
|
||||
}
|
||||
#endif /* ifdef SQLITE_TEST */
|
||||
#endif /* if SQLITE_TEST || SQLITE_FTS5_DEBUG */
|
||||
|
||||
/*
|
||||
** This is called during initialization to register the fts5_expr() scalar
|
||||
** UDF with the SQLite handle passed as the only argument.
|
||||
*/
|
||||
int sqlite3Fts5ExprInit(Fts5Global *pGlobal, sqlite3 *db){
|
||||
#ifdef SQLITE_TEST
|
||||
#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
|
||||
struct Fts5ExprFunc {
|
||||
const char *z;
|
||||
void (*x)(sqlite3_context*,int,sqlite3_value**);
|
||||
|
||||
+22
-1
@@ -475,7 +475,6 @@ static int fts5HashEntrySort(
|
||||
pList = fts5HashEntryMerge(pList, ap[i]);
|
||||
}
|
||||
|
||||
pHash->nEntry = 0;
|
||||
sqlite3_free(ap);
|
||||
*ppSorted = pList;
|
||||
return SQLITE_OK;
|
||||
@@ -529,6 +528,28 @@ int sqlite3Fts5HashScanInit(
|
||||
return fts5HashEntrySort(p, pTerm, nTerm, &p->pScan);
|
||||
}
|
||||
|
||||
#ifdef SQLITE_DEBUG
|
||||
static int fts5HashCount(Fts5Hash *pHash){
|
||||
int nEntry = 0;
|
||||
int ii;
|
||||
for(ii=0; ii<pHash->nSlot; ii++){
|
||||
Fts5HashEntry *p = 0;
|
||||
for(p=pHash->aSlot[ii]; p; p=p->pHashNext){
|
||||
nEntry++;
|
||||
}
|
||||
}
|
||||
return nEntry;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Return true if the hash table is empty, false otherwise.
|
||||
*/
|
||||
int sqlite3Fts5HashIsEmpty(Fts5Hash *pHash){
|
||||
assert( pHash->nEntry==fts5HashCount(pHash) );
|
||||
return pHash->nEntry==0;
|
||||
}
|
||||
|
||||
void sqlite3Fts5HashScanNext(Fts5Hash *p){
|
||||
assert( !sqlite3Fts5HashScanEof(p) );
|
||||
p->pScan = p->pScan->pScanNext;
|
||||
|
||||
+1310
-295
File diff suppressed because it is too large
Load Diff
+22
-7
@@ -1624,7 +1624,6 @@ static int fts5UpdateMethod(
|
||||
int eType0; /* value_type() of apVal[0] */
|
||||
int rc = SQLITE_OK; /* Return code */
|
||||
int bUpdateOrDelete = 0;
|
||||
|
||||
|
||||
/* A transaction must be open when this is called. */
|
||||
assert( pTab->ts.eState==1 || pTab->ts.eState==2 );
|
||||
@@ -1654,7 +1653,14 @@ static int fts5UpdateMethod(
|
||||
if( pConfig->eContent!=FTS5_CONTENT_NORMAL
|
||||
&& 0==sqlite3_stricmp("delete", z)
|
||||
){
|
||||
rc = fts5SpecialDelete(pTab, apVal);
|
||||
if( pConfig->bContentlessDelete ){
|
||||
fts5SetVtabError(pTab,
|
||||
"'delete' may not be used with a contentless_delete=1 table"
|
||||
);
|
||||
rc = SQLITE_ERROR;
|
||||
}else{
|
||||
rc = fts5SpecialDelete(pTab, apVal);
|
||||
}
|
||||
}else{
|
||||
rc = fts5SpecialInsert(pTab, z, apVal[2 + pConfig->nCol + 1]);
|
||||
}
|
||||
@@ -1671,7 +1677,7 @@ static int fts5UpdateMethod(
|
||||
** Cases 3 and 4 may violate the rowid constraint.
|
||||
*/
|
||||
int eConflict = SQLITE_ABORT;
|
||||
if( pConfig->eContent==FTS5_CONTENT_NORMAL ){
|
||||
if( pConfig->eContent==FTS5_CONTENT_NORMAL || pConfig->bContentlessDelete ){
|
||||
eConflict = sqlite3_vtab_on_conflict(pConfig->db);
|
||||
}
|
||||
|
||||
@@ -1679,8 +1685,12 @@ static int fts5UpdateMethod(
|
||||
assert( nArg!=1 || eType0==SQLITE_INTEGER );
|
||||
|
||||
/* Filter out attempts to run UPDATE or DELETE on contentless tables.
|
||||
** This is not suported. */
|
||||
if( eType0==SQLITE_INTEGER && fts5IsContentless(pTab) ){
|
||||
** This is not suported. Except - DELETE is supported if the CREATE
|
||||
** VIRTUAL TABLE statement contained "contentless_delete=1". */
|
||||
if( eType0==SQLITE_INTEGER
|
||||
&& pConfig->eContent==FTS5_CONTENT_NONE
|
||||
&& pConfig->bContentlessDelete==0
|
||||
){
|
||||
pTab->p.base.zErrMsg = sqlite3_mprintf(
|
||||
"cannot %s contentless fts5 table: %s",
|
||||
(nArg>1 ? "UPDATE" : "DELETE from"), pConfig->zName
|
||||
@@ -1767,8 +1777,7 @@ static int fts5SyncMethod(sqlite3_vtab *pVtab){
|
||||
Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
|
||||
fts5CheckTransactionState(pTab, FTS5_SYNC, 0);
|
||||
pTab->p.pConfig->pzErrmsg = &pTab->p.base.zErrMsg;
|
||||
fts5TripCursors(pTab);
|
||||
rc = sqlite3Fts5StorageSync(pTab->pStorage);
|
||||
rc = sqlite3Fts5FlushToDisk(&pTab->p);
|
||||
pTab->p.pConfig->pzErrmsg = 0;
|
||||
return rc;
|
||||
}
|
||||
@@ -2535,6 +2544,12 @@ static int fts5ColumnMethod(
|
||||
sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pStmt, iCol+1));
|
||||
}
|
||||
pConfig->pzErrmsg = 0;
|
||||
}else if( pConfig->bContentlessDelete && sqlite3_vtab_nochange(pCtx) ){
|
||||
char *zErr = sqlite3_mprintf("cannot UPDATE a subset of "
|
||||
"columns on fts5 contentless-delete table: %s", pConfig->zName
|
||||
);
|
||||
sqlite3_result_error(pCtx, zErr, -1);
|
||||
sqlite3_free(zErr);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
+72
-12
@@ -77,10 +77,10 @@ static int fts5StorageGetStmt(
|
||||
"INSERT INTO %Q.'%q_content' VALUES(%s)", /* INSERT_CONTENT */
|
||||
"REPLACE INTO %Q.'%q_content' VALUES(%s)", /* REPLACE_CONTENT */
|
||||
"DELETE FROM %Q.'%q_content' WHERE id=?", /* DELETE_CONTENT */
|
||||
"REPLACE INTO %Q.'%q_docsize' VALUES(?,?)", /* REPLACE_DOCSIZE */
|
||||
"REPLACE INTO %Q.'%q_docsize' VALUES(?,?%s)", /* REPLACE_DOCSIZE */
|
||||
"DELETE FROM %Q.'%q_docsize' WHERE id=?", /* DELETE_DOCSIZE */
|
||||
|
||||
"SELECT sz FROM %Q.'%q_docsize' WHERE id=?", /* LOOKUP_DOCSIZE */
|
||||
"SELECT sz%s FROM %Q.'%q_docsize' WHERE id=?", /* LOOKUP_DOCSIZE */
|
||||
|
||||
"REPLACE INTO %Q.'%q_config' VALUES(?,?)", /* REPLACE_CONFIG */
|
||||
"SELECT %s FROM %s AS T", /* SCAN */
|
||||
@@ -128,6 +128,19 @@ static int fts5StorageGetStmt(
|
||||
break;
|
||||
}
|
||||
|
||||
case FTS5_STMT_REPLACE_DOCSIZE:
|
||||
zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName,
|
||||
(pC->bContentlessDelete ? ",?" : "")
|
||||
);
|
||||
break;
|
||||
|
||||
case FTS5_STMT_LOOKUP_DOCSIZE:
|
||||
zSql = sqlite3_mprintf(azStmt[eStmt],
|
||||
(pC->bContentlessDelete ? ",origin" : ""),
|
||||
pC->zDb, pC->zName
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName);
|
||||
break;
|
||||
@@ -317,9 +330,11 @@ int sqlite3Fts5StorageOpen(
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK && pConfig->bColumnsize ){
|
||||
rc = sqlite3Fts5CreateTable(
|
||||
pConfig, "docsize", "id INTEGER PRIMARY KEY, sz BLOB", 0, pzErr
|
||||
);
|
||||
const char *zCols = "id INTEGER PRIMARY KEY, sz BLOB";
|
||||
if( pConfig->bContentlessDelete ){
|
||||
zCols = "id INTEGER PRIMARY KEY, sz BLOB, origin INTEGER";
|
||||
}
|
||||
rc = sqlite3Fts5CreateTable(pConfig, "docsize", zCols, 0, pzErr);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3Fts5CreateTable(
|
||||
@@ -396,7 +411,7 @@ static int fts5StorageDeleteFromIndex(
|
||||
){
|
||||
Fts5Config *pConfig = p->pConfig;
|
||||
sqlite3_stmt *pSeek = 0; /* SELECT to read row iDel from %_data */
|
||||
int rc; /* Return code */
|
||||
int rc = SQLITE_OK; /* Return code */
|
||||
int rc2; /* sqlite3_reset() return code */
|
||||
int iCol;
|
||||
Fts5InsertCtx ctx;
|
||||
@@ -412,7 +427,6 @@ static int fts5StorageDeleteFromIndex(
|
||||
|
||||
ctx.pStorage = p;
|
||||
ctx.iCol = -1;
|
||||
rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel);
|
||||
for(iCol=1; rc==SQLITE_OK && iCol<=pConfig->nCol; iCol++){
|
||||
if( pConfig->abUnindexed[iCol-1]==0 ){
|
||||
const char *zText;
|
||||
@@ -449,6 +463,37 @@ static int fts5StorageDeleteFromIndex(
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is called to process a DELETE on a contentless_delete=1
|
||||
** table. It adds the tombstone required to delete the entry with rowid
|
||||
** iDel. If successful, SQLITE_OK is returned. Or, if an error occurs,
|
||||
** an SQLite error code.
|
||||
*/
|
||||
static int fts5StorageContentlessDelete(Fts5Storage *p, i64 iDel){
|
||||
i64 iOrigin = 0;
|
||||
sqlite3_stmt *pLookup = 0;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
assert( p->pConfig->bContentlessDelete );
|
||||
assert( p->pConfig->eContent==FTS5_CONTENT_NONE );
|
||||
|
||||
/* Look up the origin of the document in the %_docsize table. Store
|
||||
** this in stack variable iOrigin. */
|
||||
rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP_DOCSIZE, &pLookup, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3_bind_int64(pLookup, 1, iDel);
|
||||
if( SQLITE_ROW==sqlite3_step(pLookup) ){
|
||||
iOrigin = sqlite3_column_int64(pLookup, 1);
|
||||
}
|
||||
rc = sqlite3_reset(pLookup);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK && iOrigin!=0 ){
|
||||
rc = sqlite3Fts5IndexContentlessDelete(p->pIndex, iOrigin, iDel);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Insert a record into the %_docsize table. Specifically, do:
|
||||
@@ -469,10 +514,17 @@ static int fts5StorageInsertDocsize(
|
||||
rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_DOCSIZE, &pReplace, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3_bind_int64(pReplace, 1, iRowid);
|
||||
sqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC);
|
||||
sqlite3_step(pReplace);
|
||||
rc = sqlite3_reset(pReplace);
|
||||
sqlite3_bind_null(pReplace, 2);
|
||||
if( p->pConfig->bContentlessDelete ){
|
||||
i64 iOrigin = 0;
|
||||
rc = sqlite3Fts5IndexGetOrigin(p->pIndex, &iOrigin);
|
||||
sqlite3_bind_int64(pReplace, 3, iOrigin);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC);
|
||||
sqlite3_step(pReplace);
|
||||
rc = sqlite3_reset(pReplace);
|
||||
sqlite3_bind_null(pReplace, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
@@ -536,7 +588,15 @@ int sqlite3Fts5StorageDelete(Fts5Storage *p, i64 iDel, sqlite3_value **apVal){
|
||||
|
||||
/* Delete the index records */
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = fts5StorageDeleteFromIndex(p, iDel, apVal);
|
||||
rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
if( p->pConfig->bContentlessDelete ){
|
||||
rc = fts5StorageContentlessDelete(p, iDel);
|
||||
}else{
|
||||
rc = fts5StorageDeleteFromIndex(p, iDel, apVal);
|
||||
}
|
||||
}
|
||||
|
||||
/* Delete the %_docsize record */
|
||||
|
||||
@@ -180,7 +180,11 @@ if {[detail_is_full]} {
|
||||
} {1 2}
|
||||
}
|
||||
|
||||
do_execsql_test 4.5 {
|
||||
do_execsql_test 4.5.1 {
|
||||
SELECT rowid FROM s1 WHERE s1 MATCH 'a AND x'
|
||||
} {1 2}
|
||||
|
||||
do_execsql_test 4.5.2 {
|
||||
SELECT rowid FROM s1 WHERE s1 MATCH 'a x'
|
||||
} {1 2}
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# 2023 May 28
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#*************************************************************************
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
set testprefix fts5bigid
|
||||
|
||||
# If SQLITE_ENABLE_FTS5 is not defined, omit this file.
|
||||
ifcapable !fts5 {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
set nRow 20000
|
||||
|
||||
proc do_ascdesc_test {tn query} {
|
||||
set ::lAsc [db eval { SELECT rowid FROM x1($query) }]
|
||||
set ::lDesc [db eval { SELECT rowid FROM x1($query) ORDER BY rowid DESC }]
|
||||
do_test $tn.1 { lsort -integer $::lAsc } $::lAsc
|
||||
do_test $tn.2 { lsort -integer -decr $::lDesc } $::lDesc
|
||||
do_test $tn.3 { lsort -integer $::lDesc } $::lAsc
|
||||
}
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE x1 USING fts5(a);
|
||||
}
|
||||
|
||||
do_test 1.1 {
|
||||
for {set ii 0} {$ii < $nRow} {incr ii} {
|
||||
db eval {
|
||||
REPLACE INTO x1(rowid, a) VALUES(random(), 'movement at the station');
|
||||
}
|
||||
}
|
||||
} {}
|
||||
|
||||
do_ascdesc_test 1.2 "the"
|
||||
|
||||
do_execsql_test 1.3 {
|
||||
DELETE FROM x1
|
||||
}
|
||||
|
||||
do_test 1.4 {
|
||||
for {set ii 0} {$ii < $nRow} {incr ii} {
|
||||
db eval {
|
||||
INSERT INTO x1(rowid, a) VALUES(
|
||||
$ii + 0x6FFFFFFFFFFFFFFF, 'movement at the station'
|
||||
);
|
||||
}
|
||||
}
|
||||
} {}
|
||||
|
||||
do_ascdesc_test 1.5 "movement"
|
||||
|
||||
finish_test
|
||||
@@ -0,0 +1,271 @@
|
||||
# 2014 Dec 20
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# This file contains tests for the content= and content_rowid= options.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
set testprefix fts5contentless
|
||||
|
||||
# If SQLITE_ENABLE_FTS5 is defined, omit this file.
|
||||
ifcapable !fts5 {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
# Check that it is not possible to specify "contentless_delete=1" for
|
||||
# anything other than a contentless table.
|
||||
#
|
||||
set res(0) {0 {}}
|
||||
set res(1) {1 {contentless_delete=1 requires a contentless table}}
|
||||
foreach {tn sql bError} {
|
||||
1 "(a, b, contentless_delete=1)" 1
|
||||
2 "(a, b, contentless_delete=1, content=abc)" 1
|
||||
3 "(a, b, contentless_delete=1, content=)" 0
|
||||
4 "(content=, contentless_delete=1, a)" 0
|
||||
5 "(content='', contentless_delete=1, hello)" 0
|
||||
} {
|
||||
execsql { BEGIN }
|
||||
do_catchsql_test 1.$tn "CREATE VIRTUAL TABLE t1 USING fts5 $sql" $res($bError)
|
||||
execsql { ROLLBACK }
|
||||
}
|
||||
|
||||
# Check that it is not possible to specify "contentless_delete=1"
|
||||
# along with columnsize=1.
|
||||
#
|
||||
set res(0) {0 {}}
|
||||
set res(1) {1 {contentless_delete=1 is incompatible with columnsize=0}}
|
||||
foreach {tn sql bError} {
|
||||
2 "(a, b, content='', contentless_delete=1, columnsize=0)" 1
|
||||
} {
|
||||
execsql { BEGIN }
|
||||
do_catchsql_test 1.$tn "CREATE VIRTUAL TABLE t1 USING fts5 $sql" $res($bError)
|
||||
execsql { ROLLBACK }
|
||||
}
|
||||
|
||||
# Check that if contentless_delete=1 is specified, then the "origin"
|
||||
# column is added to the %_docsize table.
|
||||
reset_db
|
||||
do_execsql_test 3.0 {
|
||||
CREATE VIRTUAL TABLE x1 USING fts5(c, content='');
|
||||
CREATE VIRTUAL TABLE x2 USING fts5(c, content='', contentless_delete=1);
|
||||
}
|
||||
do_execsql_test 3.1 {
|
||||
SELECT sql FROM sqlite_schema WHERE name IN ('x1_docsize', 'x2_docsize');
|
||||
} {
|
||||
{CREATE TABLE 'x1_docsize'(id INTEGER PRIMARY KEY, sz BLOB)}
|
||||
{CREATE TABLE 'x2_docsize'(id INTEGER PRIMARY KEY, sz BLOB, origin INTEGER)}
|
||||
}
|
||||
|
||||
do_execsql_test 3.2.1 {
|
||||
SELECT hex(block) FROM x1_data WHERE id=10
|
||||
} {00000000000000}
|
||||
do_execsql_test 3.2.2 {
|
||||
SELECT hex(block) FROM x2_data WHERE id=10
|
||||
} {00000000FF000001000000}
|
||||
|
||||
do_execsql_test 3.3 {
|
||||
INSERT INTO x2 VALUES('first text');
|
||||
INSERT INTO x2 VALUES('second text');
|
||||
}
|
||||
do_execsql_test 3.4 {
|
||||
SELECT id, origin FROM x2_docsize
|
||||
} {1 1 2 2}
|
||||
do_execsql_test 3.5 {
|
||||
SELECT level, segment, loc1, loc2 FROM fts5_structure(
|
||||
(SELECT block FROM x2_data WHERE id=10)
|
||||
)
|
||||
} {
|
||||
0 0 1 1
|
||||
0 1 2 2
|
||||
}
|
||||
do_execsql_test 3.6 {
|
||||
INSERT INTO x2(x2) VALUES('optimize');
|
||||
}
|
||||
do_execsql_test 3.7 {
|
||||
SELECT level, segment, loc1, loc2 FROM fts5_structure(
|
||||
(SELECT block FROM x2_data WHERE id=10)
|
||||
)
|
||||
} {
|
||||
1 0 1 2
|
||||
}
|
||||
|
||||
do_execsql_test 3.8 {
|
||||
DELETE FROM x2 WHERE rowid=2;
|
||||
}
|
||||
|
||||
do_execsql_test 3.9 {
|
||||
SELECT rowid FROM x2('text')
|
||||
} {1}
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
reset_db
|
||||
proc document {n} {
|
||||
set vocab [list A B C D E F G H I J K L M N O P Q R S T U V W X Y Z]
|
||||
set ret [list]
|
||||
for {set ii 0} {$ii < $n} {incr ii} {
|
||||
lappend ret [lindex $vocab [expr int(rand()*[llength $vocab])]]
|
||||
}
|
||||
set ret
|
||||
}
|
||||
|
||||
set nRow 1000
|
||||
|
||||
do_execsql_test 4.0 {
|
||||
CREATE TABLE t1(x);
|
||||
CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1);
|
||||
INSERT INTO ft(ft, rank) VALUES('pgsz', 100);
|
||||
}
|
||||
do_test 4.1 {
|
||||
for {set ii 0} {$ii < $nRow} {incr ii} {
|
||||
set doc [document 6]
|
||||
execsql {
|
||||
INSERT INTO t1 VALUES($doc);
|
||||
INSERT INTO ft VALUES($doc);
|
||||
}
|
||||
}
|
||||
} {}
|
||||
|
||||
foreach v {A B C D E F G H I J K L M N O P Q R S T U V W X Y Z} {
|
||||
set L1 [execsql {SELECT rowid FROM t1 WHERE x LIKE '%'||$v||'%'}]
|
||||
set L2 [execsql {SELECT rowid FROM ft($v)}]
|
||||
do_test 4.2.$v { set L1 } $L2
|
||||
}
|
||||
|
||||
do_test 4.3 {
|
||||
for {set ii 1} {$ii < $nRow} {incr ii 2} {
|
||||
execsql {
|
||||
DELETE FROM ft WHERE rowid=$ii;
|
||||
DELETE FROM t1 WHERE rowid=$ii;
|
||||
}
|
||||
}
|
||||
} {}
|
||||
|
||||
foreach v {A B C D E F G H I J K L M N O P Q R S T U V W X Y Z} {
|
||||
set L1 [execsql {SELECT rowid FROM t1 WHERE x LIKE '%'||$v||'%'}]
|
||||
set L2 [execsql {SELECT rowid FROM ft($v)}]
|
||||
do_test 4.4.$v { set L1 } $L2
|
||||
}
|
||||
|
||||
do_execsql_test 4.5 {
|
||||
INSERT INTO ft(ft) VALUES('optimize');
|
||||
} {}
|
||||
|
||||
foreach v {A B C D E F G H I J K L M N O P Q R S T U V W X Y Z} {
|
||||
set L1 [execsql {SELECT rowid FROM t1 WHERE x LIKE '%'||$v||'%'}]
|
||||
set L2 [execsql {SELECT rowid FROM ft($v)}]
|
||||
do_test 4.6.$v { set L1 } $L2
|
||||
}
|
||||
|
||||
#execsql_pp { SELECT fts5_decode(id, block) FROM ft_data }
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 5.0 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1);
|
||||
INSERT INTO ft(rowid, x) VALUES(1, 'one two three');
|
||||
INSERT INTO ft(rowid, x) VALUES(2, 'one two four');
|
||||
INSERT INTO ft(rowid, x) VALUES(3, 'one two five');
|
||||
INSERT INTO ft(rowid, x) VALUES(4, 'one two seven');
|
||||
INSERT INTO ft(rowid, x) VALUES(5, 'one two eight');
|
||||
}
|
||||
|
||||
do_execsql_test 5.1 {
|
||||
DELETE FROM ft WHERE rowid=2
|
||||
}
|
||||
|
||||
do_execsql_test 5.2 {
|
||||
SELECT rowid FROM ft
|
||||
} {1 3 4 5}
|
||||
|
||||
do_catchsql_test 5.3 {
|
||||
UPDATE ft SET x='four six' WHERE rowid=3
|
||||
} {0 {}}
|
||||
|
||||
do_execsql_test 5.4 {
|
||||
SELECT rowid FROM ft('one');
|
||||
} {1 4 5}
|
||||
|
||||
do_execsql_test 5.5 {
|
||||
REPLACE INTO ft(rowid, x) VALUES(3, 'four six');
|
||||
SELECT rowid FROM ft('one');
|
||||
} {1 4 5}
|
||||
|
||||
do_execsql_test 5.6 {
|
||||
REPLACE INTO ft(rowid, x) VALUES(6, 'one two eleven');
|
||||
SELECT rowid FROM ft('one');
|
||||
} {1 4 5 6}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 6.0 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1);
|
||||
INSERT INTO ft(rowid, x) VALUES(1, 'one two three');
|
||||
INSERT INTO ft(rowid, x) VALUES(2, 'one two four');
|
||||
}
|
||||
|
||||
do_test 6.1 {
|
||||
db eval { SELECT rowid FROM ft('one two') } {
|
||||
if {$rowid==1} {
|
||||
db eval { INSERT INTO ft(rowid, x) VALUES(3, 'one two four') }
|
||||
}
|
||||
}
|
||||
} {}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 7.0 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1);
|
||||
}
|
||||
|
||||
set lRowid [list -450 0 1 2 42]
|
||||
|
||||
do_test 7.1 {
|
||||
execsql BEGIN
|
||||
foreach r $lRowid {
|
||||
execsql { INSERT INTO ft(rowid, x) VALUES($r, 'one one one'); }
|
||||
}
|
||||
execsql COMMIT
|
||||
} {}
|
||||
|
||||
do_test 7.2 {
|
||||
execsql BEGIN
|
||||
foreach r $lRowid {
|
||||
execsql { REPLACE INTO ft(rowid, x) VALUES($r, 'two two two'); }
|
||||
}
|
||||
execsql COMMIT
|
||||
} {}
|
||||
|
||||
do_execsql_test 7.3 { SELECT rowid FROM ft('one'); } {}
|
||||
do_execsql_test 7.4 { SELECT rowid FROM ft('two'); } $lRowid
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 8.0 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1);
|
||||
INSERT INTO ft VALUES('hello world');
|
||||
INSERT INTO ft VALUES('one two three');
|
||||
}
|
||||
|
||||
do_catchsql_test 8.1 {
|
||||
INSERT INTO ft(ft, rowid, x) VALUES('delete', 1, 'hello world');
|
||||
} {1 {'delete' may not be used with a contentless_delete=1 table}}
|
||||
|
||||
do_execsql_test 8.2 {
|
||||
BEGIN;
|
||||
INSERT INTO ft(rowid, x) VALUES(3, 'four four four');
|
||||
DELETE FROM ft WHERE rowid=3;
|
||||
COMMIT;
|
||||
SELECT rowid FROM ft('four');
|
||||
} {}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
# 2023 July 19
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# This file contains tests for the content= and content_rowid= options.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
set testprefix fts5contentless2
|
||||
|
||||
# If SQLITE_ENABLE_FTS5 is defined, omit this file.
|
||||
ifcapable !fts5 {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
proc vocab {} {
|
||||
list aaa bbb ccc ddd eee fff ggg hhh iii jjj kkk lll mmm nnn ooo ppp
|
||||
}
|
||||
|
||||
proc document {nToken} {
|
||||
set doc [list]
|
||||
set vocab [vocab]
|
||||
for {set ii 0} {$ii < $nToken} {incr ii} {
|
||||
lappend doc [lindex $vocab [expr int(rand()*[llength $vocab])]]
|
||||
}
|
||||
set doc
|
||||
}
|
||||
db func document document
|
||||
|
||||
proc contains {doc token} {
|
||||
expr {[lsearch $doc $token]>=0}
|
||||
}
|
||||
db func contains contains
|
||||
|
||||
proc do_compare_tables_test {tn} {
|
||||
uplevel [list do_test $tn {
|
||||
foreach v [vocab] {
|
||||
set l1 [execsql { SELECT rowid FROM t1 WHERE contains(doc, $v) }]
|
||||
set l2 [execsql { SELECT rowid FROM t2($v) }]
|
||||
if {$l1!=$l2} { error "1: query mismatch ($l1) ($l2)" }
|
||||
|
||||
set w "[string range $v 0 1]*"
|
||||
set l1 [execsql { SELECT rowid FROM t1 WHERE contains(doc, $w) }]
|
||||
set l2 [execsql { SELECT rowid FROM t2($w) }]
|
||||
if {$l1!=$l2} { error "2: query mismatch ($l1) ($l2)" }
|
||||
|
||||
set w "[string range $v 0 0]*"
|
||||
set l1 [execsql { SELECT rowid FROM t1 WHERE contains(doc, $w) }]
|
||||
set l2 [execsql { SELECT rowid FROM t2($w) }]
|
||||
if {$l1!=$l2} { error "2: query mismatch ($l1) ($l2)" }
|
||||
|
||||
set l1 [execsql {
|
||||
SELECT rowid FROM t1 WHERE contains(doc, $v) ORDER BY rowid DESC
|
||||
}]
|
||||
set l2 [execsql { SELECT rowid FROM t2($v) ORDER BY rowid DESC }]
|
||||
if {$l1!=$l2} { error "1: query mismatch ($l1) ($l2)" }
|
||||
}
|
||||
set {} {}
|
||||
} {}]
|
||||
}
|
||||
|
||||
proc lshuffle {in} {
|
||||
set L [list]
|
||||
set ret [list]
|
||||
foreach elem $in { lappend L [list [expr rand()] $elem] }
|
||||
foreach pair [lsort -index 0 $L] { lappend ret [lindex $pair 1] }
|
||||
set ret
|
||||
}
|
||||
|
||||
expr srand(0)
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE t2 USING fts5(
|
||||
doc, prefix=2, content=, contentless_delete=1
|
||||
);
|
||||
|
||||
CREATE TABLE t1(doc);
|
||||
CREATE TRIGGER tr1 AFTER DELETE ON t1 BEGIN
|
||||
DELETE FROM t2 WHERE rowid = old.rowid;
|
||||
END;
|
||||
}
|
||||
|
||||
set SMALLEST64 -9223372036854775808
|
||||
set LARGEST64 9223372036854775807
|
||||
|
||||
foreach {tn r1 r2} {
|
||||
1 0 50
|
||||
2 $SMALLEST64 $SMALLEST64+50
|
||||
3 $LARGEST64-50 $LARGEST64
|
||||
4 -50 -1
|
||||
} {
|
||||
set r1 [expr $r1]
|
||||
set r2 [expr $r2]
|
||||
|
||||
do_test 1.1.$tn {
|
||||
execsql BEGIN
|
||||
for {set ii $r1} {$ii <= $r2} {incr ii} {
|
||||
execsql { INSERT INTO t1(rowid, doc) VALUES ($ii, document(8)); }
|
||||
}
|
||||
execsql COMMIT
|
||||
} {}
|
||||
}
|
||||
do_test 1.2 {
|
||||
db eval { SELECT rowid, doc FROM t1 } {
|
||||
execsql { INSERT INTO t2(rowid, doc) VALUES($rowid, $doc) }
|
||||
}
|
||||
} {}
|
||||
|
||||
foreach {tn rowid} {
|
||||
1 $SMALLEST64
|
||||
2 0
|
||||
3 -5
|
||||
4 -30
|
||||
5 $LARGEST64
|
||||
6 $LARGEST64-1
|
||||
} {
|
||||
set rowid [expr $rowid]
|
||||
do_execsql_test 1.3.$tn.1 {
|
||||
DELETE FROM t1 WHERE rowid=$rowid
|
||||
}
|
||||
do_compare_tables_test 1.3.$tn.2
|
||||
}
|
||||
|
||||
set iTest 1
|
||||
foreach r [lshuffle [execsql {SELECT rowid FROM t1}]] {
|
||||
if {($iTest % 50)==0} {
|
||||
execsql { INSERT INTO t2(t2) VALUES('optimize') }
|
||||
}
|
||||
if {($iTest % 5)==0} {
|
||||
execsql { INSERT INTO t2(t2, rank) VALUES('merge', 5) }
|
||||
}
|
||||
do_execsql_test 1.4.$iTest.1($r) {
|
||||
DELETE FROM t1 WHERE rowid=$r
|
||||
}
|
||||
do_compare_tables_test 1.4.$iTest.2
|
||||
incr iTest
|
||||
}
|
||||
|
||||
do_execsql_test 1.5 {
|
||||
SELECT * FROM t1
|
||||
} {}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
db func document document
|
||||
|
||||
do_execsql_test 2.0 {
|
||||
CREATE VIRTUAL TABLE t2 USING fts5(doc, content=, contentless_delete=1);
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000
|
||||
)
|
||||
INSERT INTO t2(rowid, doc) SELECT i, i || ' ' || i FROM s;
|
||||
}
|
||||
|
||||
do_execsql_test 2.1 {
|
||||
BEGIN;
|
||||
DELETE FROM t2 WHERE rowid=32;
|
||||
DELETE FROM t2 WHERE rowid=64;
|
||||
DELETE FROM t2 WHERE rowid=96;
|
||||
DELETE FROM t2 WHERE rowid=128;
|
||||
DELETE FROM t2 WHERE rowid=160;
|
||||
DELETE FROM t2 WHERE rowid=192;
|
||||
COMMIT;
|
||||
}
|
||||
|
||||
do_execsql_test 2.2 {
|
||||
SELECT * FROM t2('128');
|
||||
} {}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
foreach {tn step} {
|
||||
1 3
|
||||
2 7
|
||||
3 15
|
||||
} {
|
||||
set step [expr $step]
|
||||
|
||||
reset_db
|
||||
db func document document
|
||||
do_execsql_test 3.$tn.0 {
|
||||
CREATE VIRTUAL TABLE t2 USING fts5(doc, content=, contentless_delete=1);
|
||||
INSERT INTO t2(t2, rank) VALUES('pgsz', 100);
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000
|
||||
)
|
||||
INSERT INTO t2(rowid, doc) SELECT i, i || ' ' || i FROM s;
|
||||
}
|
||||
do_execsql_test 3.$tn.1 {
|
||||
DELETE FROM t2 WHERE (rowid % $step)==0
|
||||
}
|
||||
do_execsql_test 3.$tn.2 {
|
||||
SELECT * FROM t2( $step * 5 )
|
||||
} {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
# 2023 July 21
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# This file contains tests for the content= and content_rowid= options.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
set testprefix fts5contentless3
|
||||
|
||||
# If SQLITE_ENABLE_FTS5 is defined, omit this file.
|
||||
ifcapable !fts5 {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(x, content=, contentless_delete=1);
|
||||
BEGIN;
|
||||
INSERT INTO ft VALUES('one one one');
|
||||
INSERT INTO ft VALUES('two two two');
|
||||
INSERT INTO ft VALUES('three three three');
|
||||
INSERT INTO ft VALUES('four four four');
|
||||
INSERT INTO ft VALUES('five five five');
|
||||
INSERT INTO ft VALUES('six six six');
|
||||
INSERT INTO ft VALUES('seven seven seven');
|
||||
INSERT INTO ft VALUES('eight eight eight');
|
||||
INSERT INTO ft VALUES('nine nine nine');
|
||||
COMMIT;
|
||||
|
||||
DELETE FROM ft WHERE rowid=3;
|
||||
}
|
||||
|
||||
proc myhex {hex} { binary decode hex $hex }
|
||||
db func myhex myhex
|
||||
|
||||
do_execsql_test 1.1 {
|
||||
UPDATE ft_data SET block =
|
||||
myhex('04000000 00000001' ||
|
||||
'01020304 01020304 01020304 01020304' ||
|
||||
'01020304 01020304 01020304 01020304'
|
||||
)
|
||||
WHERE id = (SELECT max(id) FROM ft_data);
|
||||
}
|
||||
|
||||
do_execsql_test 1.2 {
|
||||
DELETE FROM ft WHERE rowid=1
|
||||
}
|
||||
|
||||
do_execsql_test 1.3 {
|
||||
SELECT rowid FROM ft('two');
|
||||
} {2}
|
||||
|
||||
do_execsql_test 1.3 {
|
||||
UPDATE ft_data SET block =
|
||||
myhex('08000000 00000001' ||
|
||||
'0000000001020304 0000000001020304 0000000001020304 0000000001020304' ||
|
||||
'0000000001020304 0000000001020304 0000000001020304 0000000001020304'
|
||||
)
|
||||
WHERE id = (SELECT max(id) FROM ft_data);
|
||||
}
|
||||
|
||||
do_execsql_test 1.4 {
|
||||
SELECT rowid FROM ft('two');
|
||||
} {2}
|
||||
|
||||
do_execsql_test 1.5 {
|
||||
DELETE FROM ft WHERE rowid=4
|
||||
}
|
||||
|
||||
do_execsql_test 1.6 {
|
||||
UPDATE ft_data SET block = myhex('04000000 00000000')
|
||||
WHERE id = (SELECT max(id) FROM ft_data);
|
||||
}
|
||||
do_execsql_test 1.7 {
|
||||
SELECT rowid FROM ft('two');
|
||||
} {2}
|
||||
|
||||
do_execsql_test 1.8 {
|
||||
UPDATE ft_data SET block = myhex('04000000 00000000')
|
||||
WHERE id = (SELECT max(id) FROM ft_data);
|
||||
}
|
||||
do_execsql_test 1.9 {
|
||||
DELETE FROM ft WHERE rowid=8
|
||||
} {}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 2.0 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(x, content=, contentless_delete=1);
|
||||
INSERT INTO ft VALUES('one one one');
|
||||
INSERT INTO ft VALUES('two two two');
|
||||
INSERT INTO ft VALUES('three three three');
|
||||
INSERT INTO ft VALUES('four four four');
|
||||
INSERT INTO ft VALUES('five five five');
|
||||
INSERT INTO ft VALUES('six six six');
|
||||
INSERT INTO ft VALUES('seven seven seven');
|
||||
INSERT INTO ft VALUES('eight eight eight');
|
||||
INSERT INTO ft VALUES('nine nine nine');
|
||||
}
|
||||
|
||||
do_execsql_test 2.1 {
|
||||
INSERT INTO ft(ft) VALUES('optimize');
|
||||
}
|
||||
do_execsql_test 2.2 {
|
||||
SELECT count(*) FROM ft_data
|
||||
} {3}
|
||||
do_execsql_test 2.3 {
|
||||
DELETE FROM ft WHERE rowid=5
|
||||
}
|
||||
do_execsql_test 2.4 {
|
||||
SELECT count(*) FROM ft_data
|
||||
} {4}
|
||||
|
||||
# Check that an 'optimize' works (rewrites the index) if there is a single
|
||||
# segment with one or more tombstone hash pages.
|
||||
do_execsql_test 2.5 {
|
||||
INSERT INTO ft(ft) VALUES('optimize');
|
||||
}
|
||||
do_execsql_test 2.6 {
|
||||
SELECT count(*) FROM ft_data
|
||||
} {3}
|
||||
|
||||
# Check that an 'optimize' is a no-op if there is a single segment
|
||||
# and no tombstone hash pages.
|
||||
do_execsql_test 2.7 {
|
||||
INSERT INTO ft(ft) VALUES('optimize');
|
||||
SELECT rowid FROM ft_data;
|
||||
} [db eval {SELECT rowid FROM ft_data}]
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 3.0 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(x, content=, contentless_delete=1);
|
||||
INSERT INTO ft(ft, rank) VALUES('pgsz', 64);
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000
|
||||
)
|
||||
INSERT INTO ft(rowid, x) SELECT i, i||' '||i||' '||i||' '||i FROM s;
|
||||
INSERT INTO ft(ft) VALUES('optimize');
|
||||
}
|
||||
|
||||
do_execsql_test 3.1 {
|
||||
SELECT count(*) FROM ft_data
|
||||
} {200}
|
||||
|
||||
do_execsql_test 3.2 {
|
||||
DELETE FROM ft WHERE (rowid % 50)==0;
|
||||
SELECT count(*) FROM ft_data;
|
||||
} {203}
|
||||
|
||||
do_execsql_test 3.3 {
|
||||
INSERT INTO ft(ft, rank) VALUES('merge', 500);
|
||||
SELECT rowid FROM ft_data;
|
||||
} [db eval {SELECT rowid FROM ft_data}]
|
||||
|
||||
do_execsql_test 3.4 {
|
||||
INSERT INTO ft(ft, rank) VALUES('merge', -1000);
|
||||
SELECT count(*) FROM ft_data;
|
||||
} {197}
|
||||
|
||||
do_execsql_test 3.5 {
|
||||
DELETE FROM ft WHERE (rowid % 50)==1;
|
||||
SELECT count(*) FROM ft_data;
|
||||
} {200}
|
||||
|
||||
do_execsql_test 3.6 {
|
||||
SELECT level, segment, npgtombstone FROM fts5_structure(
|
||||
(SELECT block FROM ft_data WHERE id=10)
|
||||
)
|
||||
} {1 0 3}
|
||||
|
||||
do_test 3.6 {
|
||||
while 1 {
|
||||
set nChange [db total_changes]
|
||||
execsql { INSERT INTO ft(ft, rank) VALUES('merge', -5) }
|
||||
if {([db total_changes] - $nChange)<2} break
|
||||
}
|
||||
} {}
|
||||
|
||||
do_execsql_test 3.7 {
|
||||
SELECT level, segment, npgtombstone FROM fts5_structure(
|
||||
(SELECT block FROM ft_data WHERE id=10)
|
||||
)
|
||||
} {2 0 0}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
# 2023 July 21
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# This file contains tests for the content= and content_rowid= options.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
set testprefix fts5contentless4
|
||||
|
||||
# If SQLITE_ENABLE_FTS5 is defined, omit this file.
|
||||
ifcapable !fts5 {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
proc document {n} {
|
||||
set vocab [list A B C D E F G H I J K L M N O P Q R S T U V W X Y Z]
|
||||
set ret [list]
|
||||
for {set ii 0} {$ii < $n} {incr ii} {
|
||||
lappend ret [lindex $vocab [expr int(rand()*[llength $vocab])]]
|
||||
}
|
||||
set ret
|
||||
}
|
||||
db func document document
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1);
|
||||
INSERT INTO ft(ft, rank) VALUES('pgsz', 240);
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000
|
||||
)
|
||||
INSERT INTO ft SELECT document(12) FROM s;
|
||||
}
|
||||
|
||||
do_execsql_test 1.1 {
|
||||
INSERT INTO ft(ft) VALUES('optimize');
|
||||
}
|
||||
|
||||
do_execsql_test 1.2 {
|
||||
SELECT level, segment, nentry, nentrytombstone FROM fts5_structure((
|
||||
SELECT block FROM ft_data WHERE id=10
|
||||
))
|
||||
} {0 0 1000 0}
|
||||
|
||||
do_execsql_test 1.3 {
|
||||
DELETE FROM ft WHERE rowid < 50
|
||||
}
|
||||
|
||||
do_execsql_test 1.4 {
|
||||
SELECT level, segment, nentry, nentrytombstone FROM fts5_structure((
|
||||
SELECT block FROM ft_data WHERE id=10
|
||||
))
|
||||
} {0 0 1000 49}
|
||||
|
||||
do_execsql_test 1.5 {
|
||||
DELETE FROM ft WHERE rowid < 1000
|
||||
}
|
||||
|
||||
do_execsql_test 1.6 {
|
||||
SELECT level, segment, nentry, nentrytombstone FROM fts5_structure((
|
||||
SELECT block FROM ft_data WHERE id=10
|
||||
))
|
||||
} {1 0 1 0}
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
reset_db
|
||||
db func document document
|
||||
|
||||
do_execsql_test 2.0 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1);
|
||||
}
|
||||
|
||||
do_test 2.1 {
|
||||
for {set ii 0} {$ii < 5000} {incr ii} {
|
||||
execsql { INSERT INTO ft VALUES( document(12) ) }
|
||||
}
|
||||
} {}
|
||||
|
||||
do_execsql_test 2.2 {
|
||||
SELECT sum(nentry) - sum(nentrytombstone) FROM fts5_structure((
|
||||
SELECT block FROM ft_data WHERE id=10
|
||||
))
|
||||
} {5000}
|
||||
|
||||
for {set ii 5000} {$ii >= 0} {incr ii -100} {
|
||||
do_execsql_test 2.3.$ii {
|
||||
DELETE FROM ft WHERE rowid > $ii
|
||||
}
|
||||
do_execsql_test 2.3.$ii.2 {
|
||||
SELECT
|
||||
CAST((total(nentry) - total(nentrytombstone)) AS integer)
|
||||
FROM
|
||||
fts5_structure( (SELECT block FROM ft_data WHERE id=10) )
|
||||
} $ii
|
||||
}
|
||||
|
||||
execsql_pp {
|
||||
SELECT * FROM fts5_structure((
|
||||
SELECT block FROM ft_data WHERE id=10
|
||||
))
|
||||
}
|
||||
|
||||
do_test 2.4 {
|
||||
for {set ii 0} {$ii < 5000} {incr ii} {
|
||||
execsql { INSERT INTO ft VALUES( document(12) ) }
|
||||
}
|
||||
} {}
|
||||
|
||||
for {set ii 1} {$ii <= 5000} {incr ii 10} {
|
||||
do_execsql_test 2.3.$ii {
|
||||
DELETE FROM ft WHERE rowid = $ii;
|
||||
INSERT INTO ft VALUES( document(12) );
|
||||
INSERT INTO ft(ft, rank) VALUES('merge', -10);
|
||||
}
|
||||
|
||||
do_execsql_test 2.3.$ii.2 {
|
||||
SELECT
|
||||
CAST((total(nentry) - total(nentrytombstone)) AS integer)
|
||||
FROM
|
||||
fts5_structure( (SELECT block FROM ft_data WHERE id=10) )
|
||||
} 5000
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
db func document document
|
||||
do_execsql_test 3.0 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1);
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100
|
||||
)
|
||||
INSERT INTO ft SELECT document(12) FROM s;
|
||||
}
|
||||
|
||||
do_catchsql_test 3.1 {
|
||||
INSERT INTO ft(ft, rank) VALUES('deletemerge', 'text');
|
||||
} {1 {SQL logic error}}
|
||||
do_catchsql_test 3.2 {
|
||||
INSERT INTO ft(ft, rank) VALUES('deletemerge', 50);
|
||||
} {0 {}}
|
||||
do_execsql_test 3.3 {
|
||||
SELECT * FROM ft_config WHERE k='deletemerge'
|
||||
} {deletemerge 50}
|
||||
do_catchsql_test 3.4 {
|
||||
INSERT INTO ft(ft, rank) VALUES('deletemerge', 101);
|
||||
} {0 {}}
|
||||
do_execsql_test 3.5 {
|
||||
SELECT * FROM ft_config WHERE k='deletemerge'
|
||||
} {deletemerge 101}
|
||||
|
||||
do_execsql_test 3.6 {
|
||||
DELETE FROM ft WHERE rowid<95
|
||||
}
|
||||
|
||||
do_execsql_test 3.7 {
|
||||
SELECT nentrytombstone, nentry FROM fts5_structure((
|
||||
SELECT block FROM ft_data WHERE id=10
|
||||
))
|
||||
} {94 100}
|
||||
|
||||
do_execsql_test 3.8 {
|
||||
DELETE FROM ft WHERE rowid=95
|
||||
}
|
||||
|
||||
do_execsql_test 3.9 {
|
||||
SELECT nentrytombstone, nentry FROM fts5_structure((
|
||||
SELECT block FROM ft_data WHERE id=10
|
||||
))
|
||||
} {95 100}
|
||||
|
||||
do_execsql_test 3.10 {
|
||||
DELETE FROM ft;
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100
|
||||
)
|
||||
INSERT INTO ft SELECT document(12) FROM s;
|
||||
INSERT INTO ft(ft, rank) VALUES('deletemerge', 50);
|
||||
}
|
||||
|
||||
do_execsql_test 3.11 {
|
||||
DELETE FROM ft WHERE rowid<95
|
||||
}
|
||||
|
||||
do_execsql_test 3.12 {
|
||||
SELECT nentrytombstone, nentry FROM fts5_structure((
|
||||
SELECT block FROM ft_data WHERE id=10
|
||||
))
|
||||
} {0 6}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
db func document document
|
||||
do_execsql_test 4.0 {
|
||||
CREATE VIRTUAL TABLE x1 USING fts5(x, content='', contentless_delete=1);
|
||||
INSERT INTO x1(x1, rank) VALUES('usermerge', 16);
|
||||
INSERT INTO x1(x1, rank) VALUES('deletemerge', 40);
|
||||
INSERT INTO x1 VALUES('one');
|
||||
INSERT INTO x1 VALUES('two');
|
||||
INSERT INTO x1 VALUES('three');
|
||||
INSERT INTO x1 VALUES('four');
|
||||
INSERT INTO x1 VALUES('five');
|
||||
INSERT INTO x1 VALUES('six');
|
||||
INSERT INTO x1 VALUES('seven');
|
||||
INSERT INTO x1 VALUES('eight');
|
||||
INSERT INTO x1 VALUES('nine');
|
||||
INSERT INTO x1 VALUES('ten');
|
||||
}
|
||||
|
||||
do_execsql_test 4.1 {
|
||||
SELECT level, segment FROM fts5_structure((
|
||||
SELECT block FROM x1_data WHERE id=10
|
||||
))
|
||||
} {
|
||||
0 0 0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0 9
|
||||
}
|
||||
|
||||
for {set ii 1} {$ii < 4} {incr ii} {
|
||||
do_execsql_test 4.2.$ii {
|
||||
DELETE FROM x1 WHERE rowid = $ii;
|
||||
INSERT INTO x1(x1, rank) VALUES('merge', 5);
|
||||
SELECT level, segment FROM fts5_structure((
|
||||
SELECT block FROM x1_data WHERE id=10
|
||||
))
|
||||
} {
|
||||
0 0 0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0 9
|
||||
}
|
||||
}
|
||||
|
||||
do_execsql_test 4.3 {
|
||||
DELETE FROM x1 WHERE rowid = $ii;
|
||||
INSERT INTO x1(x1, rank) VALUES('merge', 5);
|
||||
SELECT level, segment, nentry FROM fts5_structure((
|
||||
SELECT block FROM x1_data WHERE id=10
|
||||
))
|
||||
} {
|
||||
1 0 6
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# 2023 August 7
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# This file contains tests for the content= and content_rowid= options.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
set testprefix fts5contentless5
|
||||
|
||||
# If SQLITE_ENABLE_FTS5 is defined, omit this file.
|
||||
ifcapable !fts5 {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(a, b, c, content='', contentless_delete=1);
|
||||
INSERT INTO t1 VALUES('A', 'B', 'C');
|
||||
INSERT INTO t1 VALUES('D', 'E', 'F');
|
||||
INSERT INTO t1 VALUES('G', 'H', 'I');
|
||||
}
|
||||
|
||||
do_execsql_test 1.01 {
|
||||
CREATE TABLE t2(x, y);
|
||||
INSERT INTO t2 VALUES('x', 'y');
|
||||
}
|
||||
|
||||
# explain_i "UPDATE t1 SET a='a' WHERE t1.rowid=1"
|
||||
breakpoint
|
||||
explain_i "UPDATE t1 SET a='a' FROM t2 WHERE t1.rowid=1 AND b IS NULL"
|
||||
|
||||
#breakpoint
|
||||
#explain_i "UPDATE t1 SET a='a' WHERE b IS NULL AND rowid=?"
|
||||
|
||||
foreach {tn up err} {
|
||||
1 "UPDATE t1 SET a='a', b='b', c='c' WHERE rowid=1" 0
|
||||
2 "UPDATE t1 SET a='a', b='b' WHERE rowid=1" 1
|
||||
3 "UPDATE t1 SET b='b', c='c' WHERE rowid=1" 1
|
||||
4 "UPDATE t1 SET a='a', c='c' WHERE rowid=1" 1
|
||||
5 "UPDATE t1 SET a='a', c='c' WHERE t1.rowid=1 AND b IS NULL" 1
|
||||
6 "UPDATE t1 SET a='a' FROM t2 WHERE t1.rowid=1" 1
|
||||
7 "UPDATE t1 SET a='a', b='b', c='c' FROM t2 WHERE t1.rowid=1" 0
|
||||
} {
|
||||
|
||||
set res(0) {0 {}}
|
||||
set res(1) {1 {cannot UPDATE a subset of columns on fts5 contentless-delete table: t1}}
|
||||
do_catchsql_test 1.$tn $up $res($err)
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
set testprefix fts5corrupt3
|
||||
set testprefix fts5corrupt5
|
||||
|
||||
# If SQLITE_ENABLE_FTS5 is defined, omit this file.
|
||||
ifcapable !fts5 {
|
||||
@@ -793,6 +793,94 @@ do_catchsql_test 4.5 {
|
||||
REPLACE INTO t1(rowid,a,b,rowid) VALUES(200,1,2,3);
|
||||
} {1 {database disk image is malformed}}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_test 5.0 {
|
||||
sqlite3 db {}
|
||||
db deserialize [decode_hexdb {
|
||||
.open --hexdb
|
||||
| size 28672 pagesize 4096 filename crash-0c6d3451d11597.db
|
||||
| page 1 offset 0
|
||||
| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3.
|
||||
| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 07 .....@ ........
|
||||
| 32: 00 00 00 00 00 00 00 00 00 00 00 07 00 00 00 04 ................
|
||||
| 96: 00 00 00 00 0d 00 00 00 07 0d d2 00 0f c4 0f 6d ...............m
|
||||
| 112: 0f 02 0e ab 0e 4e 0d f6 0d d2 00 00 00 00 00 00 .....N..........
|
||||
| 3536: 00 00 22 07 06 17 11 11 01 31 74 61 62 6c 65 74 .........1tablet
|
||||
| 3552: 32 74 32 07 43 52 45 41 54 45 20 54 41 42 4c 45 2t2.CREATE TABLE
|
||||
| 3568: 20 74 32 28 78 29 56 06 06 17 1f 1f 01 7d 74 61 t2(x)V.......ta
|
||||
| 3584: 62 6c 65 74 31 5f 63 6f 6e 66 69 67 74 31 5f 63 blet1_configt1_c
|
||||
| 3600: 6f 6e 66 69 67 06 43 52 45 41 54 45 20 54 41 42 onfig.CREATE TAB
|
||||
| 3616: 4c 45 20 27 74 31 5f 63 6f 6e 66 69 67 27 28 6b LE 't1_config'(k
|
||||
| 3632: 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 76 29 PRIMARY KEY, v)
|
||||
| 3648: 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 5b 05 WITHOUT ROWID[.
|
||||
| 3664: 07 17 21 21 01 81 01 74 61 62 6c 65 74 31 5f 64 ..!!...tablet1_d
|
||||
| 3680: 6f 63 73 69 7a 65 74 31 5f 64 6f 63 73 69 7a 65 ocsizet1_docsize
|
||||
| 3696: 05 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 .CREATE TABLE 't
|
||||
| 3712: 31 5f 64 6f 63 73 69 7a 65 27 28 69 64 20 49 4e 1_docsize'(id IN
|
||||
| 3728: 54 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 TEGER PRIMARY KE
|
||||
| 3744: 59 2c 20 73 7a 20 42 4c 4f 42 29 55 04 06 17 21 Y, sz BLOB)U...!
|
||||
| 3760: 21 01 77 74 61 62 6c 65 74 31 5f 63 6f 6e 74 65 !.wtablet1_conte
|
||||
| 3776: 6e 74 74 31 5f 63 6f 6e 74 65 6e 74 04 43 52 45 ntt1_content.CRE
|
||||
| 3792: 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 63 6f ATE TABLE 't1_co
|
||||
| 3808: 6e 74 65 6e 74 27 28 69 64 20 49 4e 54 45 47 45 ntent'(id INTEGE
|
||||
| 3824: 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 63 R PRIMARY KEY, c
|
||||
| 3840: 30 29 69 03 07 17 19 19 01 81 2d 74 61 62 6c 65 0)i.......-table
|
||||
| 3856: 74 31 5f 69 64 78 74 31 5f 69 64 78 03 43 52 45 t1_idxt1_idx.CRE
|
||||
| 3872: 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 69 64 ATE TABLE 't1_id
|
||||
| 3888: 78 27 28 73 65 67 69 64 2c 20 74 65 72 6d 2c 20 x'(segid, term,
|
||||
| 3904: 70 67 6e 6f 2c 20 50 52 49 4d 41 52 59 20 4b 45 pgno, PRIMARY KE
|
||||
| 3920: 59 28 73 65 67 69 64 2c 20 74 65 72 6d 29 29 20 Y(segid, term))
|
||||
| 3936: 57 49 54 48 4f 55 54 20 52 4f 57 49 44 55 02 07 WITHOUT ROWIDU..
|
||||
| 3952: 17 1b 1b 01 81 01 74 61 62 6c 65 74 31 5f 64 61 ......tablet1_da
|
||||
| 3968: 74 61 74 31 5f 64 61 74 61 02 43 52 45 41 54 45 tat1_data.CREATE
|
||||
| 3984: 20 54 41 42 4c 45 20 27 74 31 5f 64 61 74 61 27 TABLE 't1_data'
|
||||
| 4000: 28 69 64 20 49 4e 54 45 47 45 52 20 50 52 49 4d (id INTEGER PRIM
|
||||
| 4016: 41 52 b9 20 4b 45 59 2c 20 62 6c 6f 63 6b 20 42 AR. KEY, block B
|
||||
| 4032: 4c 4f 42 29 3a 01 06 17 11 11 08 63 74 61 62 6c LOB):......ctabl
|
||||
| 4048: 65 74 31 74 31 43 52 45 41 54 45 20 56 49 52 54 et1t1CREATE VIRT
|
||||
| 4064: 55 41 4c 20 54 41 42 4c 45 20 74 31 20 55 53 49 UAL TABLE t1 USI
|
||||
| 4080: 4e 47 20 66 74 73 35 28 63 6f 6e 74 65 6e 74 29 NG fts5(content)
|
||||
| page 2 offset 4096
|
||||
| 0: 0d 00 00 00 03 0f bd 00 0f e8 0f ef 0f bd 00 00 ................
|
||||
| 16: 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
| 4016: 00 00 00 00 00 00 00 00 00 00 00 00 00 24 84 80 .............$..
|
||||
| 4032: 80 80 80 01 03 00 4e 00 00 00 1e 06 30 61 62 61 ......N.....0aba
|
||||
| 4048: 63 6b 01 02 02 04 02 66 74 02 02 02 04 04 6e 64 ck.....ft.....nd
|
||||
| 4064: 6f 6e 03 02 02 04 0a 07 05 01 03 00 10 03 03 0f on..............
|
||||
| 4080: 0a 03 00 24 00 00 00 00 01 01 01 00 01 01 01 11 ...$............
|
||||
| page 3 offset 8192
|
||||
| 0: 0a 00 00 00 01 0f 00 00 00 00 00 00 00 00 00 00 ................
|
||||
| 4080: 00 00 00 00 00 00 00 00 00 00 05 04 09 0c 01 02 ................
|
||||
| page 4 offset 12288
|
||||
| 0: 0d 00 00 00 03 0f e0 00 0f f6 0f ec 0f e0 00 00 ................
|
||||
| 4064: 0a 03 03 00 1b 61 62 61 6e 64 6f 6e 08 02 03 00 .....abandon....
|
||||
| 4080: 17 61 62 61 66 74 08 01 03 00 17 61 62 61 63 6b .abaft.....aback
|
||||
| page 5 offset 16384
|
||||
| 0: 0d 00 00 00 03 0f ee 00 0f fa 0f f4 0f ee 00 00 ................
|
||||
| 16: 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
| 4064: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04 03 ................
|
||||
| 4080: 03 00 0e 01 04 02 03 00 0e 01 04 01 03 00 0e 01 ................
|
||||
| page 6 offset 20480
|
||||
| 0: 0a 00 00 00 01 0f f4 00 0f f4 00 00 00 00 00 00 ................
|
||||
| 4080: 00 00 00 00 0b 03 1b 01 76 65 72 73 69 6f 6e 04 ........version.
|
||||
| page 7 offset 24576
|
||||
| 0: 0d 00 00 10 03 0f d6 00 0f f4 10 e1 0f d6 00 00 ................
|
||||
| 16: 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
| 4048: 00 00 00 00 00 00 09 03 02 1b 72 65 62 75 69 6c ..........rebuil
|
||||
| 4064: 64 11 02 02 2b 69 6e 74 65 67 72 69 74 79 2d 63 d...+integrity-c
|
||||
| 4080: 68 65 63 6b 0a 01 02 1d 6f 70 74 69 6d 00 00 00 heck....optim...
|
||||
| end crash-0c6d3451d11597.db
|
||||
}]} {}
|
||||
|
||||
do_execsql_test 5.1 {
|
||||
INSERT INTO t1(t1,rank) VALUES('secure-delete',1);
|
||||
}
|
||||
do_catchsql_test 5.4 {
|
||||
UPDATE t1 SET content=randomblob(500);
|
||||
} {1 {database disk image is malformed}}
|
||||
|
||||
|
||||
sqlite3_fts5_may_be_corrupt 0
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
# 2023 April 30
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
set testprefix fts5corrupt7
|
||||
|
||||
# If SQLITE_ENABLE_FTS5 is defined, omit this file.
|
||||
ifcapable !fts5 {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
sqlite3_fts5_may_be_corrupt 1
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x);
|
||||
INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
|
||||
}
|
||||
|
||||
set doc [string repeat "a b " 30]
|
||||
|
||||
do_execsql_test 1.1 {
|
||||
BEGIN;
|
||||
INSERT INTO t1(rowid, x) VALUES(123, $doc);
|
||||
INSERT INTO t1(rowid, x) VALUES(124, $doc);
|
||||
COMMIT;
|
||||
}
|
||||
|
||||
execsql_pp {
|
||||
SELECT id, fts5_decode(id, block), quote(block) FROM t1_data
|
||||
}
|
||||
|
||||
set rows [db eval { SELECT rowid FROM t1_data }]
|
||||
db_save_and_close
|
||||
|
||||
foreach r $rows {
|
||||
db_restore_and_reopen
|
||||
|
||||
proc edit_block {b} {
|
||||
binary scan $b c* in
|
||||
set out [lreplace $in 0 1 255 255]
|
||||
binary format c* $out
|
||||
}
|
||||
db func edit_block edit_block
|
||||
|
||||
do_execsql_test 1.2.$r.1 {
|
||||
UPDATE t1_data SET block = edit_block(block) WHERE rowid=$r;
|
||||
}
|
||||
|
||||
do_execsql_test 1.2.$r.2 {
|
||||
INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
|
||||
}
|
||||
|
||||
do_test 1.2.$r.3 {
|
||||
catchsql { DELETE FROM t1 WHERE rowid=123; }
|
||||
catchsql { DELETE FROM t1 WHERE rowid=124; }
|
||||
set {} {}
|
||||
} {}
|
||||
|
||||
db close
|
||||
}
|
||||
|
||||
foreach r $rows {
|
||||
set r 137438953475
|
||||
db_restore_and_reopen
|
||||
|
||||
proc edit_block {b} {
|
||||
binary scan $b c* in
|
||||
set out [lreplace $in end end 127]
|
||||
binary format c* $out
|
||||
}
|
||||
db func edit_block edit_block
|
||||
|
||||
do_execsql_test 1.2.$r.1 {
|
||||
UPDATE t1_data SET block = edit_block(block) WHERE rowid=$r;
|
||||
}
|
||||
|
||||
do_execsql_test 1.2.$r.2 {
|
||||
INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
|
||||
}
|
||||
|
||||
do_test 1.2.$r.3 {
|
||||
catchsql { DELETE FROM t1 WHERE rowid=124; }
|
||||
catchsql { DELETE FROM t1 WHERE rowid=123; }
|
||||
set {} {}
|
||||
} {}
|
||||
|
||||
db close
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 2.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x);
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES('abc');
|
||||
INSERT INTO t1 VALUES('b d d d');
|
||||
COMMIT;
|
||||
INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
|
||||
}
|
||||
|
||||
execsql_pp {
|
||||
SELECT id, quote(block) FROM t1_data
|
||||
}
|
||||
|
||||
do_execsql_test 2.1 {
|
||||
SELECT quote(block) FROM t1_data WHERE id > 10;
|
||||
} {X'0000001A04306162630102020101620202020101640206030303040806'}
|
||||
|
||||
do_execsql_test 2.2 {
|
||||
UPDATE t1_data SET
|
||||
block=X'0000001A04306162630102025501620202020101640206030303040806'
|
||||
WHERE id>10
|
||||
}
|
||||
|
||||
do_catchsql_test 2.3 {
|
||||
DELETE FROM t1 WHERE rowid = 1
|
||||
} {1 {database disk image is malformed}}
|
||||
|
||||
finish_test
|
||||
@@ -95,6 +95,9 @@ do_execsql_test 3.3 {
|
||||
SELECT rowid, bm25(e1) FROM e1 WHERE e1 MATCH '"/" OR "just"' ORDER BY rank;
|
||||
} {1 -1e-06}
|
||||
|
||||
do_execsql_test 3.4 "
|
||||
SELECT fts5_expr_tcl('e AND \" \"');
|
||||
" {{AND [nearset -- {e}] [{}]}}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
# 2023 July 20
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#*************************************************************************
|
||||
#
|
||||
# This file is focused on OOM errors. Particularly those that may occur
|
||||
# when using contentless_delete=1 databases.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
source $testdir/malloc_common.tcl
|
||||
set testprefix fts5faultF
|
||||
|
||||
# If SQLITE_ENABLE_FTS5 is not defined, omit this file.
|
||||
ifcapable !fts5 {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
faultsim_save_and_close
|
||||
do_faultsim_test 1 -prep {
|
||||
faultsim_restore_and_reopen
|
||||
} -body {
|
||||
execsql {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x, y, content=, contentless_delete=1)
|
||||
}
|
||||
} -test {
|
||||
faultsim_test_result {0 {}} {1 {vtable constructor failed: t1}}
|
||||
}
|
||||
|
||||
reset_db
|
||||
do_execsql_test 2.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(doc, content=, contentless_delete=1);
|
||||
BEGIN;
|
||||
INSERT INTO t1(rowid, doc) VALUES(1, 'a b c d');
|
||||
INSERT INTO t1(rowid, doc) VALUES(2, 'a b c d');
|
||||
INSERT INTO t1(rowid, doc) VALUES(3, 'a b c d');
|
||||
INSERT INTO t1(rowid, doc) VALUES(4, 'a b c d');
|
||||
COMMIT;
|
||||
DELETE FROM t1 WHERE rowid IN (2, 4);
|
||||
}
|
||||
|
||||
do_faultsim_test 2 -prep {
|
||||
sqlite3 db test.db
|
||||
execsql { SELECT rowid FROM t1 }
|
||||
} -body {
|
||||
execsql {
|
||||
SELECT rowid FROM t1('b c');
|
||||
}
|
||||
} -test {
|
||||
faultsim_test_result {0 {1 3}}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 3.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(doc, content=, contentless_delete=1);
|
||||
BEGIN;
|
||||
INSERT INTO t1(rowid, doc) VALUES(1, 'a b c d');
|
||||
INSERT INTO t1(rowid, doc) VALUES(2, 'a b c d');
|
||||
INSERT INTO t1(rowid, doc) VALUES(3, 'a b c d');
|
||||
INSERT INTO t1(rowid, doc) VALUES(4, 'a b c d');
|
||||
COMMIT;
|
||||
}
|
||||
|
||||
faultsim_save_and_close
|
||||
do_faultsim_test 3 -prep {
|
||||
faultsim_restore_and_reopen
|
||||
execsql { SELECT rowid FROM t1 }
|
||||
} -body {
|
||||
execsql {
|
||||
INSERT INTO t1(rowid, doc) VALUES(5, 'a b c d');
|
||||
}
|
||||
} -test {
|
||||
faultsim_test_result {0 {}}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 4.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(doc, content=, contentless_delete=1);
|
||||
INSERT INTO t1(t1, rank) VALUES('pgsz', 64);
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000
|
||||
)
|
||||
INSERT INTO t1(rowid, doc) SELECT i, 'a b c d' FROM s;
|
||||
}
|
||||
|
||||
do_execsql_test 4.1 { DELETE FROM t1 WHERE rowid <= 25 }
|
||||
|
||||
faultsim_save_and_close
|
||||
do_faultsim_test 4 -faults oom-t* -prep {
|
||||
faultsim_restore_and_reopen
|
||||
execsql { SELECT rowid FROM t1 }
|
||||
} -body {
|
||||
execsql {
|
||||
DELETE FROM t1 WHERE rowid < 100
|
||||
}
|
||||
} -test {
|
||||
faultsim_test_result {0 {}}
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# 2023 May 16
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#*************************************************************************
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
set testprefix fts5limits
|
||||
return_if_no_fts5
|
||||
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(x);
|
||||
}
|
||||
|
||||
# Default limit for expression depth is 256
|
||||
#
|
||||
foreach {tn nRepeat op bErr} {
|
||||
1 200 AND 0
|
||||
2 200 NOT 0
|
||||
3 200 OR 0
|
||||
|
||||
4 260 AND 0
|
||||
5 260 NOT 1
|
||||
6 260 OR 0
|
||||
} {
|
||||
set L [string repeat "abc " $nRepeat]
|
||||
set Q [join $L " $op "]
|
||||
|
||||
set res {0 {}}
|
||||
if {$bErr} {
|
||||
set res "1 {fts5 expression tree is too large (maximum depth 256)}"
|
||||
}
|
||||
|
||||
do_catchsql_test 1.$tn {
|
||||
SELECT * FROM ft($Q)
|
||||
} $res
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -329,7 +329,7 @@ do_execsql_test 12.3 {
|
||||
reset_db
|
||||
sqlite3_db_config db DEFENSIVE 1
|
||||
|
||||
do_execsql_test 13.0 {
|
||||
do_execsql_test 13.1.0 {
|
||||
CREATE TABLE a (id INTEGER PRIMARY KEY, name TEXT);
|
||||
CREATE VIRTUAL TABLE b USING fts5(name);
|
||||
CREATE TRIGGER a_trigger AFTER INSERT ON a BEGIN
|
||||
@@ -337,18 +337,44 @@ do_execsql_test 13.0 {
|
||||
END;
|
||||
}
|
||||
|
||||
do_test 13.1 {
|
||||
do_test 13.1.1 {
|
||||
set ::STMT [
|
||||
sqlite3_prepare db "INSERT INTO a VALUES (1, 'foo') RETURNING id;" -1 dummy
|
||||
]
|
||||
sqlite3_step $::STMT
|
||||
} {SQLITE_ROW}
|
||||
|
||||
do_test 13.2 {
|
||||
do_test 13.1.2 {
|
||||
sqlite3_finalize $::STMT
|
||||
} {SQLITE_OK}
|
||||
|
||||
do_test 13.3 {
|
||||
do_test 13.1.3 {
|
||||
sqlite3_errmsg db
|
||||
} {not an error}
|
||||
|
||||
reset_db
|
||||
sqlite3_db_config db DEFENSIVE 1
|
||||
do_execsql_test 13.2.0 {
|
||||
BEGIN;
|
||||
CREATE TABLE a (id INTEGER PRIMARY KEY, name TEXT);
|
||||
CREATE VIRTUAL TABLE b USING fts5(name);
|
||||
CREATE TRIGGER a_trigger AFTER INSERT ON a BEGIN
|
||||
INSERT INTO b (name) VALUES ('foo');
|
||||
END;
|
||||
}
|
||||
|
||||
do_test 13.2.1 {
|
||||
set ::STMT [
|
||||
sqlite3_prepare db "INSERT INTO a VALUES (1, 'foo') RETURNING id;" -1 dummy
|
||||
]
|
||||
sqlite3_step $::STMT
|
||||
} {SQLITE_ROW}
|
||||
|
||||
do_test 13.2.2 {
|
||||
sqlite3_finalize $::STMT
|
||||
} {SQLITE_OK}
|
||||
|
||||
do_test 13.2.3 {
|
||||
sqlite3_errmsg db
|
||||
} {not an error}
|
||||
|
||||
@@ -443,5 +469,33 @@ do_execsql_test -db db2 16.6 {
|
||||
SELECT * FROM x1
|
||||
} {abc def}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 17.1 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(x, tokenize="unicode61 separators 'X'");
|
||||
}
|
||||
do_execsql_test 17.2 {
|
||||
SELECT 0 FROM ft WHERE ft MATCH 'X' AND ft MATCH 'X'
|
||||
}
|
||||
do_execsql_test 17.3 {
|
||||
SELECT 0 FROM ft('X')
|
||||
}
|
||||
|
||||
do_execsql_test 17.4 {
|
||||
CREATE VIRTUAL TABLE t0 USING fts5(c0, t="trigram");
|
||||
INSERT INTO t0 VALUES('assertionfaultproblem');
|
||||
}
|
||||
do_execsql_test 17.5 {
|
||||
SELECT 0 FROM t0(0) WHERE c0 GLOB 0;
|
||||
} {}
|
||||
|
||||
do_execsql_test 17.5 {
|
||||
SELECT c0 FROM t0 WHERE c0 GLOB '*f*';
|
||||
} {assertionfaultproblem}
|
||||
do_execsql_test 17.5 {
|
||||
SELECT c0 FROM t0 WHERE c0 GLOB '*faul*';
|
||||
} {assertionfaultproblem}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -273,6 +273,19 @@ do_execsql_test 5.3 {
|
||||
do_execsql_test 5.4 { SELECT rowid FROM t1('abc'); } 2
|
||||
do_execsql_test 5.5 { SELECT rowid FROM t1('aa'); } 2
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
|
||||
do_execsql_test 6.0 {
|
||||
CREATE VIRTUAL TABLE fts USING fts5(content);
|
||||
INSERT INTO fts(fts, rank) VALUES ('secure-delete', 1);
|
||||
INSERT INTO fts(rowid, content) VALUES
|
||||
(3407, 'profile profile profile profile profile profile profile profile pull pulling pulling really');
|
||||
DELETE FROM fts WHERE rowid IS 3407;
|
||||
INSERT INTO fts(fts) VALUES ('integrity-check');
|
||||
}
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -50,6 +50,25 @@ do_test 1.3 {
|
||||
expr $phc(1)*5 < $phc(2)
|
||||
} {1}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
|
||||
do_execsql_test 2.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x);
|
||||
INSERT INTO t1(t1, rank) VALUES('secure-delete', $sd)
|
||||
}
|
||||
|
||||
do_execsql_test 2.1 {
|
||||
BEGIN;
|
||||
INSERT INTO t1(rowid, x) VALUES(-100000, 'abc def ghi');
|
||||
INSERT INTO t1(rowid, x) VALUES(-99999, 'abc def ghi');
|
||||
INSERT INTO t1(rowid, x) VALUES(9223372036854775800, 'abc def ghi');
|
||||
COMMIT;
|
||||
}
|
||||
|
||||
do_execsql_test 2.2 {
|
||||
SELECT rowid FROM t1('def')
|
||||
} {-100000 -99999 9223372036854775800}
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -122,6 +122,9 @@ foreach {tn expr} {
|
||||
4.1 "NEAR(one two, 2)"
|
||||
4.2 "NEAR(one two three, 2)"
|
||||
4.3 "NEAR(eight nine, 1) OR NEAR(six seven, 1)"
|
||||
|
||||
5.1 "one + two"
|
||||
5.2 "1 + two"
|
||||
} {
|
||||
if {[fts5_expr_ok $expr ss]==0} {
|
||||
do_test 1.$tok.$tn.OMITTED { list } [list]
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
# Quick-and-dirty makefile to bootstrap the sqlite3-jni project. This
|
||||
# build assumes a Linux-like system.
|
||||
default: all
|
||||
|
||||
JAVA_HOME ?= $(HOME)/jdk/current
|
||||
# e.g. /usr/lib/jvm/default-javajava-19-openjdk-amd64
|
||||
JDK_HOME ?= $(JAVA_HOME)
|
||||
# ^^^ JDK_HOME is not as widely used as JAVA_HOME
|
||||
bin.javac := $(JDK_HOME)/bin/javac
|
||||
bin.java := $(JDK_HOME)/bin/java
|
||||
bin.jar := $(JDK_HOME)/bin/jar
|
||||
ifeq (,$(wildcard $(JDK_HOME)))
|
||||
$(error set JDK_HOME to the top-most dir of your JDK installation.)
|
||||
endif
|
||||
MAKEFILE := $(lastword $(MAKEFILE_LIST))
|
||||
$(MAKEFILE):
|
||||
|
||||
package.jar := sqlite3-jni.jar
|
||||
|
||||
dir.top := ../..
|
||||
dir.tool := ../../tool
|
||||
dir.jni := $(patsubst %/,%,$(dir $(MAKEFILE)))
|
||||
|
||||
dir.src := $(dir.jni)/src
|
||||
dir.src.c := $(dir.src)/c
|
||||
dir.bld := $(dir.jni)/bld
|
||||
dir.bld.c := $(dir.bld)
|
||||
dir.src.jni := $(dir.src)/org/sqlite/jni
|
||||
dir.src.jni.tester := $(dir.src.jni)/tester
|
||||
$(dir.bld.c):
|
||||
mkdir -p $@
|
||||
|
||||
classpath := $(dir.src)
|
||||
CLEAN_FILES := $(package.jar)
|
||||
DISTCLEAN_FILES := $(dir.jni)/*~ $(dir.src.c)/*~ $(dir.src.jni)/*~
|
||||
|
||||
sqlite3-jni.h := $(dir.src.c)/sqlite3-jni.h
|
||||
.NOTPARALLEL: $(sqlite3-jni.h)
|
||||
SQLite3Jni.java := src/org/sqlite/jni/SQLite3Jni.java
|
||||
SQLTester.java := src/org/sqlite/jni/tester/SQLTester.java
|
||||
SQLite3Jni.class := $(SQLite3Jni.java:.java=.class)
|
||||
SQLTester.class := $(SQLTester.java:.java=.class)
|
||||
|
||||
########################################################################
|
||||
# The future of FTS5 customization in this API is as yet unclear.
|
||||
# It would be a real doozy to bind to JNI.
|
||||
enable.fts5 ?= 1
|
||||
# If enable.tester is 0, the org/sqlite/jni/tester/* bits are elided.
|
||||
enable.tester ?= 1
|
||||
|
||||
# bin.version-info = binary to output various sqlite3 version info
|
||||
# building the distribution zip file.
|
||||
bin.version-info := $(dir.top)/version-info
|
||||
.NOTPARALLEL: $(bin.version-info)
|
||||
$(bin.version-info): $(dir.tool)/version-info.c $(sqlite3.h) $(dir.top)/Makefile
|
||||
$(MAKE) -C $(dir.top) version-info
|
||||
|
||||
# Be explicit about which Java files to compile so that we can work on
|
||||
# in-progress files without requiring them to be in a compilable statae.
|
||||
JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/%,\
|
||||
BusyHandler.java \
|
||||
Collation.java \
|
||||
CollationNeeded.java \
|
||||
CommitHook.java \
|
||||
NativePointerHolder.java \
|
||||
OutputPointer.java \
|
||||
ProgressHandler.java \
|
||||
ResultCode.java \
|
||||
RollbackHook.java \
|
||||
SQLFunction.java \
|
||||
sqlite3_context.java \
|
||||
sqlite3.java \
|
||||
SQLite3Jni.java \
|
||||
sqlite3_stmt.java \
|
||||
sqlite3_value.java \
|
||||
Tester1.java \
|
||||
Tracer.java \
|
||||
UpdateHook.java \
|
||||
ValueHolder.java \
|
||||
)
|
||||
ifeq (1,$(enable.fts5))
|
||||
JAVA_FILES.main += $(patsubst %,$(dir.src.jni)/%,\
|
||||
fts5_api.java \
|
||||
fts5_extension_function.java \
|
||||
fts5_tokenizer.java \
|
||||
Fts5.java \
|
||||
Fts5Context.java \
|
||||
Fts5ExtensionApi.java \
|
||||
Fts5Function.java \
|
||||
Fts5PhraseIter.java \
|
||||
Fts5Tokenizer.java \
|
||||
TesterFts5.java \
|
||||
)
|
||||
endif
|
||||
JAVA_FILES.tester := $(dir.src.jni.tester)/SQLTester.java
|
||||
|
||||
CLASS_FILES.main := $(JAVA_FILES.main:.java=.class)
|
||||
CLASS_FILES.tester := $(JAVA_FILES.tester:.java=.class)
|
||||
|
||||
JAVA_FILES += $(JAVA_FILES.main)
|
||||
ifeq (1,$(enable.tester))
|
||||
JAVA_FILES += $(JAVA_FILES.tester)
|
||||
endif
|
||||
|
||||
CLASS_FILES :=
|
||||
define DOTCLASS_DEPS
|
||||
$(1).class: $(1).java $(MAKEFILE)
|
||||
all: $(1).class
|
||||
CLASS_FILES += $(1).class
|
||||
endef
|
||||
$(foreach B,$(basename $(JAVA_FILES)),$(eval $(call DOTCLASS_DEPS,$(B))))
|
||||
$(CLASS_FILES.tester): $(CLASS_FILES.main)
|
||||
javac.flags ?= -Xlint:unchecked -Xlint:deprecation
|
||||
java.flags ?=
|
||||
jnicheck ?= 1
|
||||
ifeq (1,$(jnicheck))
|
||||
java.flags += -Xcheck:jni
|
||||
endif
|
||||
$(SQLite3Jni.class): $(JAVA_FILES)
|
||||
$(bin.javac) $(javac.flags) -h $(dir.bld.c) -cp $(classpath) $(JAVA_FILES)
|
||||
all: $(SQLite3Jni.class)
|
||||
#.PHONY: classfiles
|
||||
|
||||
########################################################################
|
||||
# Set up sqlite3.c and sqlite3.h...
|
||||
#
|
||||
# To build with SEE (https://sqlite.org/see), either put sqlite3-see.c
|
||||
# in the top of this build tree or pass
|
||||
# sqlite3.c=PATH_TO_sqlite3-see.c to the build. Note that only
|
||||
# encryption modules with no 3rd-party dependencies will currently
|
||||
# work here: AES256-OFB, AES128-OFB, and AES128-CCM. Not
|
||||
# coincidentally, those 3 modules are included in the sqlite3-see.c
|
||||
# bundle.
|
||||
#
|
||||
# A custom sqlite3.c must not have any spaces in its name.
|
||||
# $(sqlite3.canonical.c) must point to the sqlite3.c in
|
||||
# the sqlite3 canonical source tree, as that source file
|
||||
# is required for certain utility and test code.
|
||||
sqlite3.canonical.c := $(firstword $(wildcard $(dir.src.c)/sqlite3.c) $(dir.top)/sqlite3.c)
|
||||
sqlite3.canonical.h := $(firstword $(wildcard $(dir.src.c)/sqlite3.h) $(dir.top)/sqlite3.h)
|
||||
sqlite3.c := $(sqlite3.canonical.c)
|
||||
sqlite3.h := $(sqlite3.canonical.h)
|
||||
#ifeq (,$(shell grep sqlite3_activate_see $(sqlite3.c) 2>/dev/null))
|
||||
# SQLITE_C_IS_SEE := 0
|
||||
#else
|
||||
# SQLITE_C_IS_SEE := 1
|
||||
# $(info This is an SEE build.)
|
||||
#endif
|
||||
|
||||
.NOTPARALLEL: $(sqlite3.h)
|
||||
$(sqlite3.h):
|
||||
$(MAKE) -C $(dir.top) sqlite3.c
|
||||
$(sqlite3.c): $(sqlite3.h)
|
||||
|
||||
SQLITE_OPT = \
|
||||
-DSQLITE_ENABLE_RTREE \
|
||||
-DSQLITE_ENABLE_EXPLAIN_COMMENTS \
|
||||
-DSQLITE_ENABLE_STMTVTAB \
|
||||
-DSQLITE_ENABLE_DBPAGE_VTAB \
|
||||
-DSQLITE_ENABLE_DBSTAT_VTAB \
|
||||
-DSQLITE_ENABLE_BYTECODE_VTAB \
|
||||
-DSQLITE_ENABLE_OFFSET_SQL_FUNC \
|
||||
-DSQLITE_OMIT_LOAD_EXTENSION \
|
||||
-DSQLITE_OMIT_DEPRECATED \
|
||||
-DSQLITE_OMIT_SHARED_CACHE \
|
||||
-DSQLITE_THREADSAFE=0 \
|
||||
-DSQLITE_TEMP_STORE=2 \
|
||||
-DSQLITE_USE_URI=1 \
|
||||
-DSQLITE_C=$(sqlite3.c)
|
||||
# -DSQLITE_DEBUG
|
||||
# -DSQLITE_DEBUG is just to work around a -Wall warning
|
||||
# for a var which gets set in all builds but only read
|
||||
# via assert().
|
||||
|
||||
SQLITE_OPT += -g -DDEBUG -UNDEBUG
|
||||
|
||||
ifeq (1,$(enable.fts5))
|
||||
SQLITE_OPT += -DSQLITE_ENABLE_FTS5
|
||||
endif
|
||||
|
||||
sqlite3-jni.c := $(dir.src.c)/sqlite3-jni.c
|
||||
sqlite3-jni.o := $(dir.bld.c)/sqlite3-jni.o
|
||||
sqlite3-jni.h := $(dir.src.c)/sqlite3-jni.h
|
||||
sqlite3-jni.dll := $(dir.bld.c)/libsqlite3-jni.so
|
||||
# All javac-generated .h files must be listed in $(sqlite3-jni.h.in):
|
||||
sqlite3-jni.h.in :=
|
||||
define ADD_JNI_H
|
||||
sqlite3-jni.h.in += $$(dir.bld.c)/org_sqlite_jni_$(1).h
|
||||
$$(dir.bld.c)/org_sqlite_jni_$(1).h: $$(dir.src.jni)/$(1).java
|
||||
endef
|
||||
$(eval $(call ADD_JNI_H,SQLite3Jni))
|
||||
ifeq (1,$(enable.fts5))
|
||||
$(eval $(call ADD_JNI_H,Fts5ExtensionApi))
|
||||
$(eval $(call ADD_JNI_H,fts5_api))
|
||||
$(eval $(call ADD_JNI_H,fts5_tokenizer))
|
||||
endif
|
||||
ifeq (1,$(enable.tester))
|
||||
sqlite3-jni.h.in += $(dir.bld.c)/org_sqlite_jni_tester_SQLTester.h
|
||||
$(dir.bld.c)/org_sqlite_jni_tester_SQLTester.h: $(dir.src.jni.tester)/SQLTester.java
|
||||
endif
|
||||
#sqlite3-jni.dll.cfiles := $(dir.src.c)
|
||||
sqlite3-jni.dll.cflags = \
|
||||
-fPIC \
|
||||
-I. \
|
||||
-I$(dir $(sqlite3.h)) \
|
||||
-I$(dir.src.c) \
|
||||
-I$(JDK_HOME)/include \
|
||||
$(patsubst %,-I%,$(patsubst %.h,,$(wildcard $(JDK_HOME)/include/*))) \
|
||||
-Wall
|
||||
# Using (-Wall -Wextra) triggers an untennable number of
|
||||
# gcc warnings from sqlite3.c for mundane things like
|
||||
# unused parameters.
|
||||
#
|
||||
# The gross $(patsubst...) above is to include the platform-specific
|
||||
# subdir which lives under $(JDK_HOME)/include and is a required
|
||||
# include path for client-level code.
|
||||
########################################################################
|
||||
ifeq (1,$(enable.tester))
|
||||
sqlite3-jni.dll.cflags += -DS3JNI_ENABLE_SQLTester
|
||||
endif
|
||||
$(sqlite3-jni.h): $(sqlite3-jni.h.in) $(MAKEFILE)
|
||||
cat $(sqlite3-jni.h.in) > $@
|
||||
$(sqlite3-jni.dll): $(sqlite3-jni.h) $(sqlite3.c) $(sqlite3.h)
|
||||
$(sqlite3-jni.dll): $(dir.bld.c) $(sqlite3-jni.c) $(SQLite3Jni.java) $(MAKEFILE)
|
||||
$(CC) $(sqlite3-jni.dll.cflags) $(SQLITE_OPT) \
|
||||
$(sqlite3-jni.c) -shared -o $@
|
||||
all: $(sqlite3-jni.dll)
|
||||
|
||||
.PHONY: test
|
||||
test.flags ?= -v
|
||||
test: $(SQLite3Jni.class) $(sqlite3-jni.dll)
|
||||
$(bin.java) -ea -Djava.library.path=$(dir.bld.c) \
|
||||
$(java.flags) -cp $(classpath) \
|
||||
org.sqlite.jni.Tester1 $(if $(test.flags),-- $(test.flags),)
|
||||
|
||||
tester.scripts := $(sort $(wildcard $(dir.src)/tests/*.test))
|
||||
tester.flags ?= # --verbose
|
||||
.PHONY: tester tester-local tester-ext
|
||||
ifeq (1,$(enable.tester))
|
||||
tester-local: $(CLASS_FILES.tester) $(sqlite3-jni.dll)
|
||||
$(bin.java) -ea -Djava.library.path=$(dir.bld.c) \
|
||||
$(java.flags) -cp $(classpath) \
|
||||
org.sqlite.jni.tester.SQLTester $(tester.flags) $(tester.scripts)
|
||||
tester: tester-local
|
||||
else
|
||||
tester:
|
||||
@echo "SQLTester support is disabled. Build with enable.tester=1 to enable it."
|
||||
endif
|
||||
|
||||
tester.extdir.default := src/tests/ext
|
||||
tester.extdir ?= $(tester.extdir.default)
|
||||
tester.extern-scripts := $(wildcard $(tester.extdir)/*.test)
|
||||
ifneq (,$(tester.extern-scripts))
|
||||
tester-ext:
|
||||
$(bin.java) -ea -Djava.library.path=$(dir.bld.c) \
|
||||
$(java.flags) -cp $(classpath) \
|
||||
org.sqlite.jni.tester.SQLTester $(tester.flags) $(tester.extern-scripts)
|
||||
else
|
||||
tester-ext:
|
||||
@echo "******************************************************"; \
|
||||
echo "*** Include the out-of-tree test suite in the 'tester'"; \
|
||||
echo "*** target by either symlinking its directory to"; \
|
||||
echo "*** $(tester.extdir.default) or passing it to make"; \
|
||||
echo "*** as tester.extdir=/path/to/that/dir."; \
|
||||
echo "******************************************************";
|
||||
endif
|
||||
|
||||
tester-ext: tester-local
|
||||
tester: tester-ext
|
||||
tests: test tester
|
||||
package.jar.in := $(abspath $(dir.src)/jar.in)
|
||||
CLEAN_FILES += $(package.jar.in)
|
||||
$(package.jar.in): $(MAKEFILE) $(CLASS_FILES.main)
|
||||
cd $(dir.src); ls -1 org/sqlite/jni/*.java org/sqlite/jni/*.class > $@
|
||||
@ls -la $@
|
||||
@echo "To use this jar you will need the -Djava.library.path=DIR/WITH/libsqlite3-jni.so flag."
|
||||
@echo "e.g. java -jar $@ -Djava.library.path=bld"
|
||||
|
||||
$(package.jar): $(CLASS_FILES) $(MAKEFILE) $(package.jar.in)
|
||||
rm -f $(dir.src)/c/*~ $(dir.src.jni)/*~
|
||||
cd $(dir.src); $(bin.jar) -cfe ../$@ org.sqlite.jni.Tester1 @$(package.jar.in)
|
||||
|
||||
jar: $(package.jar)
|
||||
|
||||
CLEAN_FILES += $(dir.bld.c)/* \
|
||||
$(dir.src.jni)/*.class \
|
||||
$(dir.src.jni.tester)/*.class \
|
||||
$(sqlite3-jni.dll) \
|
||||
hs_err_pid*.log
|
||||
|
||||
.PHONY: clean distclean
|
||||
clean:
|
||||
-rm -f $(CLEAN_FILES)
|
||||
distclean: clean
|
||||
-rm -f $(DISTCLEAN_FILES)
|
||||
-rm -fr $(dir.bld.c)
|
||||
|
||||
########################################################################
|
||||
# disttribution bundle rules...
|
||||
|
||||
ifeq (,$(filter snapshot,$(MAKECMDGOALS)))
|
||||
dist-name-prefix := sqlite-jni
|
||||
else
|
||||
dist-name-prefix := sqlite-jni-snapshot-$(shell /usr/bin/date +%Y%m%d)
|
||||
endif
|
||||
dist-name := $(dist-name-prefix)-TEMP
|
||||
|
||||
|
||||
dist-dir.top := $(dist-name)
|
||||
dist-dir.src := $(dist-dir.top)/src
|
||||
dist.top.extras := \
|
||||
README.md
|
||||
|
||||
.PHONY: dist snapshot
|
||||
|
||||
dist: \
|
||||
$(bin.version-info) $(sqlite3.canonical.c) \
|
||||
$(package.jar) $(MAKEFILE)
|
||||
@echo "Making end-user deliverables..."
|
||||
@rm -fr $(dist-dir.top)
|
||||
@mkdir -p $(dist-dir.src)
|
||||
@cp -p $(dist.top.extras) $(dist-dir.top)/.
|
||||
@cp -p jar-dist.make $(dist-dir.top)/Makefile
|
||||
@cp -p $(dir.src.c)/*.[ch] $(dist-dir.src)/.
|
||||
@cp -p $(sqlite3.canonical.c) $(sqlite3.canonical.h) $(dist-dir.src)/.
|
||||
@set -e; \
|
||||
vnum=$$($(bin.version-info) --download-version); \
|
||||
vjar=$$($(bin.version-info) --version); \
|
||||
vdir=$(dist-name-prefix)-$$vnum; \
|
||||
arczip=$$vdir.zip; \
|
||||
cp -p $(package.jar) $(dist-dir.top)/sqlite3-jni-$${vjar}.jar; \
|
||||
echo "Making $$arczip ..."; \
|
||||
rm -fr $$arczip $$vdir; \
|
||||
mv $(dist-dir.top) $$vdir; \
|
||||
zip -qr $$arczip $$vdir; \
|
||||
rm -fr $$vdir; \
|
||||
ls -la $$arczip; \
|
||||
set +e; \
|
||||
unzip -lv $$arczip || echo "Missing unzip app? Not fatal."
|
||||
|
||||
snapshot: dist
|
||||
|
||||
.PHONY: dist-clean
|
||||
clean: dist-clean
|
||||
dist-clean:
|
||||
rm -fr $(dist-name) $(wildcard sqlite-jni-*.zip)
|
||||
@@ -0,0 +1,234 @@
|
||||
SQLite3 via JNI
|
||||
========================================================================
|
||||
|
||||
This directory houses a Java Native Interface (JNI) binding for the
|
||||
sqlite3 API. If you are reading this from the distribution ZIP file,
|
||||
links to resources in the canonical source tree will note work. The
|
||||
canonical copy of this file can be browsed at:
|
||||
|
||||
<https://sqlite.org/src/doc/trunk/ext/jni/README.md>
|
||||
|
||||
Technical support is available in the forum:
|
||||
|
||||
<https://sqlite.org/forum>
|
||||
|
||||
|
||||
> **FOREWARNING:** this subproject is very much in development and
|
||||
subject to any number of changes. Please do not rely on any
|
||||
information about its API until this disclaimer is removed.
|
||||
|
||||
Project goals/requirements:
|
||||
|
||||
- A [1-to-1(-ish) mapping of the C API](#1to1ish) to Java via JNI,
|
||||
insofar as cross-language semantics allow for. A closely-related
|
||||
goal is that [the C documentation](https://sqlite.org/c3ref/intro.html)
|
||||
should be usable as-is, insofar as possible, for the JNI binding.
|
||||
|
||||
- Support Java as far back as version 8 (2014).
|
||||
|
||||
- Environment-independent. Should work everywhere both Java
|
||||
and SQLite3 do.
|
||||
|
||||
- No 3rd-party dependencies beyond the JDK. That includes no
|
||||
build-level dependencies for specific IDEs and toolchains. We
|
||||
welcome the addition of build files for arbitrary environments
|
||||
insofar as they neither interfere with each other nor become
|
||||
a maintenance burden for the sqlite developers.
|
||||
|
||||
Non-goals:
|
||||
|
||||
- Creation of high-level OO wrapper APIs. Clients are free to create
|
||||
them off of the C-style API.
|
||||
|
||||
|
||||
Significant TODOs
|
||||
========================================================================
|
||||
|
||||
- The initial beta release with version 3.43 has severe threading
|
||||
limitations. Namely, two threads cannot call into the JNI-bound API
|
||||
at once. This limitation will be remove in a subsequent release.
|
||||
|
||||
|
||||
Building
|
||||
========================================================================
|
||||
|
||||
The canonical builds assumes a Linux-like environment and requires:
|
||||
|
||||
- GNU Make
|
||||
- A JDK supporting Java 8 or higher
|
||||
- A modern C compiler. gcc and clang should both work.
|
||||
|
||||
Put simply:
|
||||
|
||||
```
|
||||
$ export JAVA_HOME=/path/to/jdk/root
|
||||
$ make
|
||||
$ make test
|
||||
$ make clean
|
||||
```
|
||||
|
||||
<a id='1to1ish'></a>
|
||||
One-to-One(-ish) Mapping to C
|
||||
========================================================================
|
||||
|
||||
This JNI binding aims to provide as close to a 1-to-1 experience with
|
||||
the C API as cross-language semantics allow. Exceptions are
|
||||
necessarily made where cross-language semantics do not allow a 1-to-1,
|
||||
and judiciously made where a 1-to-1 mapping would be unduly cumbersome
|
||||
to use in Java.
|
||||
|
||||
Golden Rule: _Never_ Throw from Callbacks
|
||||
------------------------------------------------------------------------
|
||||
|
||||
JNI bindings which accept client-defined functions _must never throw
|
||||
exceptions_ unless _very explicitly documented_ as being
|
||||
throw-safe. Exceptions are generally reserved for higher-level
|
||||
bindings which are constructed to specifically deal with them and
|
||||
ensure that they do not leak C-level resources. Some of the JNI
|
||||
bindings are provided as Java functions which expect this rule to
|
||||
always hold.
|
||||
|
||||
UTF-8(-ish)
|
||||
------------------------------------------------------------------------
|
||||
|
||||
SQLite internally uses UTF-8 encoding, whereas Java natively uses
|
||||
UTF-16. Java JNI has routines for converting to and from UTF-8, _but_
|
||||
Java uses what its docs call "[modified UTF-8][modutf8]." Care must be
|
||||
taken when converting Java strings to UTF-8 to ensure that the proper
|
||||
conversion is performed. In short,
|
||||
`String.getBytes(StandardCharsets.UTF_8)` performs the proper
|
||||
conversion in Java, and there is no JNI C API for that conversion
|
||||
(JNI's `NewStringUTF()` returns MUTF-8).
|
||||
|
||||
Known consequences and limitations of this discrepancy include:
|
||||
|
||||
- Names of databases, tables, and collations must not contain
|
||||
characters which differ in MUTF-8 and UTF-8, or certain APIs will
|
||||
mis-translate them on their way between languages. APIs which
|
||||
transfer other client-side data to Java take extra care to
|
||||
convert the data at the cost of performance.
|
||||
|
||||
[modutf8]: https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8
|
||||
|
||||
|
||||
Unwieldy Constructs are Re-mapped
|
||||
------------------------------------------------------------------------
|
||||
|
||||
Some constructs, when modelled 1-to-1 from C to Java, are unduly
|
||||
clumsy to work with in Java because they try to shoehorn C's way of
|
||||
doing certain things into Java's wildly different ways. The following
|
||||
subsections cover those, starting with a verbose explanation and
|
||||
demonstration of where such changes are "really necessary"...
|
||||
|
||||
### Custom Collations
|
||||
|
||||
A prime example of where interface changes for Java are necessary for
|
||||
usability is [registration of a custom
|
||||
collation](https://sqlite.org/c3ref/create_collation.html):
|
||||
|
||||
```
|
||||
// C:
|
||||
int sqlite3_create_collation(sqlite3 * db, const char * name, int eTextRep,
|
||||
void *pUserData,
|
||||
int (*xCompare)(void*,int,void const *,int,void const *));
|
||||
|
||||
int sqlite3_create_collation_v2(sqlite3 * db, const char * name, int eTextRep,
|
||||
void *pUserData,
|
||||
int (*xCompare)(void*,int,void const *,int,void const *),
|
||||
void (*xDestroy)(void*));
|
||||
```
|
||||
|
||||
The `pUserData` object is optional client-defined state for the
|
||||
`xCompare()` and/or `xDestroy()` callback functions, both of which are
|
||||
passed that object as their first argument. That data is passed around
|
||||
"externally" in C because that's how C models the world. If we were to
|
||||
bind that part as-is to Java, the result would be awkward to use (^Yes,
|
||||
we tried this.):
|
||||
|
||||
```
|
||||
// Java:
|
||||
int sqlite3_create_collation(sqlite3 db, String name, int eTextRep,
|
||||
Object pUserData, xCompareType xCompare);
|
||||
|
||||
int sqlite3_create_collation_v2(sqlite3 db, String name, int eTextRep,
|
||||
Object pUserData,
|
||||
xCompareType xCompare, xDestroyType xDestroy);
|
||||
```
|
||||
|
||||
The awkwardness comes from (A) having two distinctly different objects
|
||||
for callbacks and (B) having their internal state provided separately,
|
||||
which is ill-fitting in Java. For the sake of usability, C APIs which
|
||||
follow that pattern use a slightly different Java interface:
|
||||
|
||||
```
|
||||
int sqlite3_create_collation(sqlite3 db, String name, int eTextRep,
|
||||
Collation collation);
|
||||
```
|
||||
|
||||
Where the `Collation` class has an abstract `xCompare()` method and
|
||||
no-op `xDestroy()` method which can be overridden if needed, leading to
|
||||
a much more Java-esque usage:
|
||||
|
||||
```
|
||||
int rc = sqlite3_create_collation(db, "mycollation", SQLITE_UTF8, new Collation(){
|
||||
|
||||
// Required comparison function:
|
||||
@Override public int xCompare(byte[] lhs, byte[] rhs){ ... }
|
||||
|
||||
// Optional finalizer function:
|
||||
@Override public void xDestroy(){ ... }
|
||||
|
||||
// Optional local state:
|
||||
private String localState1 =
|
||||
"This is local state. There are many like it, but this one is mine.";
|
||||
private MyStateType localState2 = new MyStateType();
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
Noting that:
|
||||
|
||||
- It is still possible to bind in call-scope-local state via closures,
|
||||
if desired.
|
||||
|
||||
- No capabilities of the C API are lost or unduly obscured via the
|
||||
above API reshaping, so power users need not make any compromises.
|
||||
|
||||
- In the specific example above, `sqlite3_create_collation_v2()`
|
||||
becomes superfluous because the provided interface effectively
|
||||
provides both the v1 and v2 interfaces, the difference being that
|
||||
overriding the `xDestroy()` method effectively gives it v2
|
||||
semantics.
|
||||
|
||||
### User-defined SQL Functions (a.k.a. UDFs)
|
||||
|
||||
The [`sqlite3_create_function()`](https://sqlite.org/c3ref/create_function.html)
|
||||
family of APIs make heavy use of function pointers to provide
|
||||
client-defined callbacks, necessitating interface changes in the JNI
|
||||
binding. The Java API has only one core function-registration function:
|
||||
|
||||
```
|
||||
int sqlite3_create_function(sqlite3 db, String funcName, int nArgs,
|
||||
int encoding, SQLFunction func);
|
||||
```
|
||||
|
||||
> Design question: does the encoding argument serve any purpose in JS?
|
||||
|
||||
`SQLFunction` is not used directly, but is instead instantiated via
|
||||
one of its three subclasses:
|
||||
|
||||
- `SQLFunction.Scalar` implements simple scalar functions using but a
|
||||
single callback.
|
||||
- `SQLFunction.Aggregate` implements aggregate functions using two
|
||||
callbacks.
|
||||
- `SQLFunction.Window` implements window functions using four
|
||||
callbacks.
|
||||
|
||||
Search [`Tester1.java`](/file/ext/jni/src/org/sqlite/jni/Tester1.java) for
|
||||
`SQLFunction` for how it's used.
|
||||
|
||||
Reminder: see the disclaimer at the top of this document regarding the
|
||||
in-flux nature of this API.
|
||||
|
||||
[jsrc]: /file/
|
||||
[www]: https://sqlite.org
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/this/is/make
|
||||
#^^^^ help emacs out
|
||||
#
|
||||
# This is a POSIX-make-compatible makefile for building the sqlite3
|
||||
# JNI library from "dist" zip file. It must be edited to set the
|
||||
# proper top-level JDK directory and, depending on the platform, add a
|
||||
# platform-specific -I directory. It should build as-is with any
|
||||
# 2020s-era version of gcc or clang. It requires JDK version 8 or
|
||||
# higher.
|
||||
|
||||
default: all
|
||||
|
||||
JAVA_HOME = /usr/lib/jvm/java-1.8.0-openjdk-amd64
|
||||
CFLAGS = \
|
||||
-fPIC \
|
||||
-Isrc \
|
||||
-I$(JAVA_HOME)/include \
|
||||
-I$(JAVA_HOME)/include/linux \
|
||||
-I$(JAVA_HOME)/include/apple \
|
||||
-I$(JAVA_HOME)/include/bsd \
|
||||
-Wall
|
||||
|
||||
SQLITE_OPT = \
|
||||
-DSQLITE_ENABLE_RTREE \
|
||||
-DSQLITE_ENABLE_EXPLAIN_COMMENTS \
|
||||
-DSQLITE_ENABLE_STMTVTAB \
|
||||
-DSQLITE_ENABLE_DBPAGE_VTAB \
|
||||
-DSQLITE_ENABLE_DBSTAT_VTAB \
|
||||
-DSQLITE_ENABLE_BYTECODE_VTAB \
|
||||
-DSQLITE_ENABLE_OFFSET_SQL_FUNC \
|
||||
-DSQLITE_OMIT_LOAD_EXTENSION \
|
||||
-DSQLITE_OMIT_DEPRECATED \
|
||||
-DSQLITE_OMIT_SHARED_CACHE \
|
||||
-DSQLITE_THREADSAFE=0 \
|
||||
-DSQLITE_TEMP_STORE=2 \
|
||||
-DSQLITE_USE_URI=1 \
|
||||
-DSQLITE_ENABLE_FTS5
|
||||
|
||||
sqlite3-jni.dll = libsqlite3-jni.so
|
||||
$(sqlite3-jni.dll):
|
||||
@echo "************************************************************************"; \
|
||||
echo "*** If this fails to build, be sure to edit this makefile ***"; \
|
||||
echo "*** to configure it for your system. ***"; \
|
||||
echo "************************************************************************"
|
||||
$(CC) $(CFLAGS) $(SQLITE_OPT) \
|
||||
src/sqlite3-jni.c -shared -o $@
|
||||
@echo "Now try running it with: make test"
|
||||
|
||||
test: $(sqlite3-jni.dll)
|
||||
java -jar -Djava.library.path=. sqlite3-jni-*.jar
|
||||
|
||||
clean:
|
||||
-rm -f $(sqlite3-jni.dll)
|
||||
|
||||
all: $(sqlite3-jni.dll)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
** 2023-08-05
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
A callback for use with sqlite3_set_authorizer().
|
||||
*/
|
||||
public interface Authorizer {
|
||||
/**
|
||||
Must functions as described for the sqlite3_set_authorizer()
|
||||
callback, with one caveat: the string values passed here were
|
||||
initially (at the C level) encoded in standard UTF-8. If they
|
||||
contained any constructs which are not compatible with MUTF-8,
|
||||
these strings will not have the expected values. For further
|
||||
details, see the documentation for the SQLite3Jni class.
|
||||
|
||||
Must not throw.
|
||||
*/
|
||||
int xAuth(int opId, @Nullable String s1, @Nullable String s2,
|
||||
@Nullable String s3, @Nullable String s4);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
** 2023-08-05
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
A callback for use with sqlite3_auto_extension().
|
||||
*/
|
||||
public interface AutoExtension {
|
||||
/**
|
||||
Must function as described for the sqlite3_auto_extension(),
|
||||
with the caveat that the signature is more limited.
|
||||
|
||||
As an exception (as it were) to the callbacks-must-not-throw
|
||||
rule, AutoExtensions may do so and the exception's error message
|
||||
will be set as the db's error string.
|
||||
|
||||
Results are undefined if db is closed by an auto-extension.
|
||||
*/
|
||||
int xEntryPoint(sqlite3 db);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
** 2023-07-22
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
Callback proxy for use with sqlite3_busy_handler().
|
||||
*/
|
||||
public abstract class BusyHandler {
|
||||
/**
|
||||
Must function as documented for the sqlite3_busy_handler()
|
||||
callback argument, minus the (void*) argument the C-level
|
||||
function requires.
|
||||
|
||||
Any exceptions thrown by this callback are suppressed in order to
|
||||
retain the C-style API semantics of the JNI bindings.
|
||||
*/
|
||||
public abstract int xCallback(int n);
|
||||
|
||||
/**
|
||||
Optionally override to perform any cleanup when this busy
|
||||
handler is destroyed. It is destroyed when:
|
||||
|
||||
- The associated db is passed to sqlite3_close() or
|
||||
sqlite3_close_v2().
|
||||
|
||||
- sqlite3_busy_handler() is called to replace the handler,
|
||||
whether it's passed a null handler or any other instance of
|
||||
this class.
|
||||
|
||||
- sqlite3_busy_timeout() is called, which implicitly installs
|
||||
a busy handler.
|
||||
*/
|
||||
public void xDestroy(){}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
** 2023-07-22
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
*/
|
||||
public abstract class Collation {
|
||||
/**
|
||||
Must compare the given byte arrays using memcmp() semantics.
|
||||
*/
|
||||
public abstract int xCompare(byte[] lhs, byte[] rhs);
|
||||
/**
|
||||
Called by SQLite when the collation is destroyed. If a Collation
|
||||
requires custom cleanup, override this method.
|
||||
*/
|
||||
public void xDestroy() {}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
** 2023-07-30
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
Callback proxy for use with sqlite3_collation_needed().
|
||||
*/
|
||||
public interface CollationNeeded {
|
||||
/**
|
||||
Has the same semantics as the C-level sqlite3_create_collation()
|
||||
callback.
|
||||
|
||||
If it throws, the exception message is passed on to the db and
|
||||
the exception is suppressed.
|
||||
*/
|
||||
int xCollationNeeded(sqlite3 db, int eTextRep, String collationName);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
** 2023-07-22
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
Callback proxy for use with sqlite3_commit_hook().
|
||||
*/
|
||||
public interface CommitHook {
|
||||
/**
|
||||
Works as documented for the sqlite3_commit_hook() callback.
|
||||
Must not throw.
|
||||
*/
|
||||
int xCommitHook();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
** 2023-08-05
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
INCOMPLETE AND COMPLETELY UNTESTED.
|
||||
|
||||
A wrapper for communicating C-level (fts5_api*) instances with
|
||||
Java. These wrappers do not own their associated pointer, they
|
||||
simply provide a type-safe way to communicate it between Java and C
|
||||
via JNI.
|
||||
*/
|
||||
public final class Fts5 {
|
||||
/* Not used */
|
||||
private Fts5(){}
|
||||
|
||||
//! Callback type for use with xTokenize() variants
|
||||
public static interface xTokenizeCallback {
|
||||
int xToken(int tFlags, byte txt[], int iStart, int iEnd);
|
||||
}
|
||||
|
||||
public static final int FTS5_TOKENIZE_QUERY = 0x0001;
|
||||
public static final int FTS5_TOKENIZE_PREFIX = 0x0002;
|
||||
public static final int FTS5_TOKENIZE_DOCUMENT = 0x0004;
|
||||
public static final int FTS5_TOKENIZE_AUX = 0x0008;
|
||||
public static final int FTS5_TOKEN_COLOCATED = 0x0001;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
** 2023-08-04
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
A wrapper for communicating C-level (Fts5Context*) instances with
|
||||
Java. These wrappers do not own their associated pointer, they
|
||||
simply provide a type-safe way to communicate it between Java and C
|
||||
via JNI.
|
||||
*/
|
||||
public final class Fts5Context extends NativePointerHolder<Fts5Context> {
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
** 2023-08-04
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
ALMOST COMPLETELY UNTESTED.
|
||||
|
||||
FAR FROM COMPLETE and the feasibility of binding this to Java
|
||||
is still undetermined. This might be removed.
|
||||
*/
|
||||
public final class Fts5ExtensionApi extends NativePointerHolder<Fts5ExtensionApi> {
|
||||
//! Only called from JNI
|
||||
private Fts5ExtensionApi(){}
|
||||
private int iVersion = 2;
|
||||
|
||||
/* Callback type for used by xQueryPhrase(). */
|
||||
public static interface xQueryPhraseCallback {
|
||||
int xCallback(Fts5ExtensionApi fapi, Fts5Context cx);
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the singleton instance of this class.
|
||||
*/
|
||||
public static synchronized native Fts5ExtensionApi getInstance();
|
||||
|
||||
public synchronized native int xColumnCount(@NotNull Fts5Context fcx);
|
||||
public synchronized native int xColumnSize(@NotNull Fts5Context cx, int iCol,
|
||||
@NotNull OutputPointer.Int32 pnToken);
|
||||
public synchronized native int xColumnText(@NotNull Fts5Context cx, int iCol,
|
||||
@NotNull OutputPointer.String txt);
|
||||
public synchronized native int xColumnTotalSize(@NotNull Fts5Context fcx, int iCol,
|
||||
@NotNull OutputPointer.Int64 pnToken);
|
||||
public synchronized native Object xGetAuxdata(@NotNull Fts5Context cx, boolean clearIt);
|
||||
public synchronized native int xInst(@NotNull Fts5Context cx, int iIdx,
|
||||
@NotNull OutputPointer.Int32 piPhrase,
|
||||
@NotNull OutputPointer.Int32 piCol,
|
||||
@NotNull OutputPointer.Int32 piOff);
|
||||
public synchronized native int xInstCount(@NotNull Fts5Context fcx,
|
||||
@NotNull OutputPointer.Int32 pnInst);
|
||||
public synchronized native int xPhraseCount(@NotNull Fts5Context fcx);
|
||||
public synchronized native int xPhraseFirst(@NotNull Fts5Context cx, int iPhrase,
|
||||
@NotNull Fts5PhraseIter iter,
|
||||
@NotNull OutputPointer.Int32 iCol,
|
||||
@NotNull OutputPointer.Int32 iOff);
|
||||
public synchronized native int xPhraseFirstColumn(@NotNull Fts5Context cx, int iPhrase,
|
||||
@NotNull Fts5PhraseIter iter,
|
||||
@NotNull OutputPointer.Int32 iCol);
|
||||
public synchronized native void xPhraseNext(@NotNull Fts5Context cx,
|
||||
@NotNull Fts5PhraseIter iter,
|
||||
@NotNull OutputPointer.Int32 iCol,
|
||||
@NotNull OutputPointer.Int32 iOff);
|
||||
public synchronized native void xPhraseNextColumn(@NotNull Fts5Context cx,
|
||||
@NotNull Fts5PhraseIter iter,
|
||||
@NotNull OutputPointer.Int32 iCol);
|
||||
public synchronized native int xPhraseSize(@NotNull Fts5Context fcx, int iPhrase);
|
||||
public synchronized native int xQueryPhrase(@NotNull Fts5Context cx, int iPhrase,
|
||||
@NotNull xQueryPhraseCallback callback);
|
||||
public synchronized native int xRowCount(@NotNull Fts5Context fcx,
|
||||
@NotNull OutputPointer.Int64 nRow);
|
||||
public synchronized native long xRowid(@NotNull Fts5Context cx);
|
||||
/* Note that the JNI binding lacks the C version's xDelete()
|
||||
callback argument. Instead, if pAux has an xDestroy() method, it
|
||||
is called if the FTS5 API finalizes the aux state (including if
|
||||
allocation of storage for the auxdata fails). Any reference to
|
||||
pAux held by the JNI layer will be relinquished regardless of
|
||||
whether pAux has an xDestroy() method. */
|
||||
public synchronized native int xSetAuxdata(@NotNull Fts5Context cx, @Nullable Object pAux);
|
||||
public synchronized native int xTokenize(@NotNull Fts5Context cx, @NotNull byte pText[],
|
||||
@NotNull Fts5.xTokenizeCallback callback);
|
||||
|
||||
public synchronized native Object xUserData(Fts5Context cx);
|
||||
//^^^ returns the pointer passed as the 3rd arg to the C-level
|
||||
// fts5_api::xCreateFunction.
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
** 2023-08-04
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
Fts5Function is used in conjunction with the
|
||||
sqlite3_create_fts_function() JNI-bound API to give that native code
|
||||
access to the callback functions needed in order to implement
|
||||
FTS5 auxiliary functions in Java.
|
||||
*/
|
||||
public abstract class Fts5Function {
|
||||
|
||||
public abstract void xFunction(Fts5ExtensionApi pApi, Fts5Context pFts,
|
||||
sqlite3_context pCtx, sqlite3_value argv[]);
|
||||
public void xDestroy() {}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
** 2023-08-04
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
A wrapper for C-level Fts5PhraseIter. They are only modified and
|
||||
inspected by native-level code.
|
||||
*/
|
||||
public final class Fts5PhraseIter extends NativePointerHolder<Fts5PhraseIter> {
|
||||
//! Updated and used only by native code.
|
||||
private long a;
|
||||
private long b;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
** 2023-08-05x
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
INCOMPLETE AND COMPLETELY UNTESTED.
|
||||
|
||||
A wrapper for communicating C-level (Fts5Tokenizer*) instances with
|
||||
Java. These wrappers do not own their associated pointer, they
|
||||
simply provide a type-safe way to communicate it between Java and C
|
||||
via JNI.
|
||||
|
||||
At the C level, the Fts5Tokenizer type is essentially a void
|
||||
pointer used specifically for tokenizers.
|
||||
*/
|
||||
public final class Fts5Tokenizer extends NativePointerHolder<Fts5Tokenizer> {
|
||||
//! Only called from JNI.
|
||||
private Fts5Tokenizer(){}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
** 2023-07-21
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
A helper for passing pointers between JNI C code and Java, in
|
||||
particular for output pointers of high-level object types in the
|
||||
sqlite3 C API, e.g. (sqlite3**) and (sqlite3_stmt**). This is
|
||||
intended to be subclassed and the ContextType is intended to be the
|
||||
class which is doing the subclassing. The intent of the ContextType
|
||||
is strictly to provide some level of type safety by avoiding that
|
||||
NativePointerHolder is not inadvertently passed to an incompatible
|
||||
function signature.
|
||||
|
||||
These objects do not _own_ the pointer they refer to. They are
|
||||
intended simply to communicate that pointer between C and Java.
|
||||
*/
|
||||
public class NativePointerHolder<ContextType> {
|
||||
//! Only set from JNI, where access permissions don't matter.
|
||||
private long nativePointer = 0;
|
||||
public final long getNativePointer(){ return nativePointer; }
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
** 2023-07-21
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
Helper classes for handling JNI output pointers.
|
||||
|
||||
We do not use a generic OutputPointer<T> because working with those
|
||||
from the native JNI code is unduly quirky due to a lack of
|
||||
autoboxing at that level.
|
||||
|
||||
The usage is similar for all of thes types:
|
||||
|
||||
```
|
||||
OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
|
||||
assert( null==out.get() );
|
||||
int rc = sqlite3_open(":memory:", out);
|
||||
if( 0!=rc ) ... error;
|
||||
assert( null!=out.get() );
|
||||
sqlite3 db = out.take();
|
||||
assert( null==out.get() );
|
||||
```
|
||||
|
||||
With the minor exception that the primitive types permit direct
|
||||
access to the object's value via the `value` property, whereas the
|
||||
JNI-level opaque types do not permit client-level code to set that
|
||||
property.
|
||||
*/
|
||||
public final class OutputPointer {
|
||||
|
||||
/**
|
||||
Output pointer for use with routines, such as sqlite3_open(),
|
||||
which return a database handle via an output pointer. These
|
||||
pointers can only be set by the JNI layer, not by client-level
|
||||
code.
|
||||
*/
|
||||
public static final class sqlite3 {
|
||||
private org.sqlite.jni.sqlite3 value;
|
||||
//! Initializes with a null value.
|
||||
public sqlite3(){value = null;}
|
||||
//! Sets the current value to null.
|
||||
public void clear(){value = null;}
|
||||
//! Returns the current value.
|
||||
public final org.sqlite.jni.sqlite3 get(){return value;}
|
||||
//! Equivalent to calling get() then clear().
|
||||
public final org.sqlite.jni.sqlite3 take(){
|
||||
final org.sqlite.jni.sqlite3 v = value;
|
||||
value = null;
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Output pointer for use with routines, such as sqlite3_prepare(),
|
||||
which return a statement handle via an output pointer. These
|
||||
pointers can only be set by the JNI layer, not by client-level
|
||||
code.
|
||||
*/
|
||||
public static final class sqlite3_stmt {
|
||||
private org.sqlite.jni.sqlite3_stmt value;
|
||||
//! Initializes with a null value.
|
||||
public sqlite3_stmt(){value = null;}
|
||||
//! Sets the current value to null.
|
||||
public void clear(){value = null;}
|
||||
//! Returns the current value.
|
||||
public final org.sqlite.jni.sqlite3_stmt get(){return value;}
|
||||
//! Equivalent to calling get() then clear().
|
||||
public final org.sqlite.jni.sqlite3_stmt take(){
|
||||
final org.sqlite.jni.sqlite3_stmt v = value;
|
||||
value = null;
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Output pointer for use with native routines which return integers via
|
||||
output pointers.
|
||||
*/
|
||||
public static final class Int32 {
|
||||
/**
|
||||
This is public for ease of use. Accessors are provided for
|
||||
consistency with the higher-level types.
|
||||
*/
|
||||
public int value;
|
||||
//! Initializes with the value 0.
|
||||
public Int32(){this(0);}
|
||||
//! Initializes with the value v.
|
||||
public Int32(int v){value = v;}
|
||||
//! Returns the current value.
|
||||
public final int get(){return value;}
|
||||
//! Sets the current value to v.
|
||||
public final void set(int v){value = v;}
|
||||
}
|
||||
|
||||
/**
|
||||
Output pointer for use with native routines which return 64-bit integers
|
||||
via output pointers.
|
||||
*/
|
||||
public static final class Int64 {
|
||||
/**
|
||||
This is public for ease of use. Accessors are provided for
|
||||
consistency with the higher-level types.
|
||||
*/
|
||||
public long value;
|
||||
//! Initializes with the value 0.
|
||||
public Int64(){this(0);}
|
||||
//! Initializes with the value v.
|
||||
public Int64(long v){value = v;}
|
||||
//! Returns the current value.
|
||||
public final long get(){return value;}
|
||||
//! Sets the current value.
|
||||
public final void set(long v){value = v;}
|
||||
}
|
||||
|
||||
/**
|
||||
Output pointer for use with native routines which return strings via
|
||||
output pointers.
|
||||
*/
|
||||
public static final class String {
|
||||
/**
|
||||
This is public for ease of use. Accessors are provided for
|
||||
consistency with the higher-level types.
|
||||
*/
|
||||
public java.lang.String value;
|
||||
//! Initializes with a null value.
|
||||
public String(){this(null);}
|
||||
//! Initializes with the value v.
|
||||
public String(java.lang.String v){value = v;}
|
||||
//! Returns the current value.
|
||||
public final java.lang.String get(){return value;}
|
||||
//! Sets the current value.
|
||||
public final void set(java.lang.String v){value = v;}
|
||||
}
|
||||
|
||||
/**
|
||||
Output pointer for use with native routines which return byte
|
||||
arrays via output pointers.
|
||||
*/
|
||||
public static final class ByteArray {
|
||||
/**
|
||||
This is public for ease of use. Accessors are provided for
|
||||
consistency with the higher-level types.
|
||||
*/
|
||||
public byte[] value;
|
||||
//! Initializes with the value null.
|
||||
public ByteArray(){this(null);}
|
||||
//! Initializes with the value v.
|
||||
public ByteArray(byte[] v){value = v;}
|
||||
//! Returns the current value.
|
||||
public final byte[] get(){return value;}
|
||||
//! Sets the current value.
|
||||
public final void set(byte[] v){value = v;}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
** 2023-07-22
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
Callback proxy for use with sqlite3_progress_handler().
|
||||
*/
|
||||
public interface ProgressHandler {
|
||||
/**
|
||||
Works as documented for the sqlite3_progress_handler() callback.
|
||||
|
||||
If it throws, the exception message is passed on to the db and
|
||||
the exception is suppressed.
|
||||
*/
|
||||
int xCallback();
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
** 2023-07-21
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
This enum contains all of the core and "extended" result codes used
|
||||
by the sqlite3 library. It is provided not for use with the C-style
|
||||
API (with which it won't work) but for higher-level code which may
|
||||
find it useful to map SQLite result codes to human-readable names.
|
||||
*/
|
||||
public enum ResultCode {
|
||||
SQLITE_OK(SQLite3Jni.SQLITE_OK),
|
||||
SQLITE_ERROR(SQLite3Jni.SQLITE_ERROR),
|
||||
SQLITE_INTERNAL(SQLite3Jni.SQLITE_INTERNAL),
|
||||
SQLITE_PERM(SQLite3Jni.SQLITE_PERM),
|
||||
SQLITE_ABORT(SQLite3Jni.SQLITE_ABORT),
|
||||
SQLITE_BUSY(SQLite3Jni.SQLITE_BUSY),
|
||||
SQLITE_LOCKED(SQLite3Jni.SQLITE_LOCKED),
|
||||
SQLITE_NOMEM(SQLite3Jni.SQLITE_NOMEM),
|
||||
SQLITE_READONLY(SQLite3Jni.SQLITE_READONLY),
|
||||
SQLITE_INTERRUPT(SQLite3Jni.SQLITE_INTERRUPT),
|
||||
SQLITE_IOERR(SQLite3Jni.SQLITE_IOERR),
|
||||
SQLITE_CORRUPT(SQLite3Jni.SQLITE_CORRUPT),
|
||||
SQLITE_NOTFOUND(SQLite3Jni.SQLITE_NOTFOUND),
|
||||
SQLITE_FULL(SQLite3Jni.SQLITE_FULL),
|
||||
SQLITE_CANTOPEN(SQLite3Jni.SQLITE_CANTOPEN),
|
||||
SQLITE_PROTOCOL(SQLite3Jni.SQLITE_PROTOCOL),
|
||||
SQLITE_EMPTY(SQLite3Jni.SQLITE_EMPTY),
|
||||
SQLITE_SCHEMA(SQLite3Jni.SQLITE_SCHEMA),
|
||||
SQLITE_TOOBIG(SQLite3Jni.SQLITE_TOOBIG),
|
||||
SQLITE_CONSTRAINT(SQLite3Jni.SQLITE_CONSTRAINT),
|
||||
SQLITE_MISMATCH(SQLite3Jni.SQLITE_MISMATCH),
|
||||
SQLITE_MISUSE(SQLite3Jni.SQLITE_MISUSE),
|
||||
SQLITE_NOLFS(SQLite3Jni.SQLITE_NOLFS),
|
||||
SQLITE_AUTH(SQLite3Jni.SQLITE_AUTH),
|
||||
SQLITE_FORMAT(SQLite3Jni.SQLITE_FORMAT),
|
||||
SQLITE_RANGE(SQLite3Jni.SQLITE_RANGE),
|
||||
SQLITE_NOTADB(SQLite3Jni.SQLITE_NOTADB),
|
||||
SQLITE_NOTICE(SQLite3Jni.SQLITE_NOTICE),
|
||||
SQLITE_WARNING(SQLite3Jni.SQLITE_WARNING),
|
||||
SQLITE_ROW(SQLite3Jni.SQLITE_ROW),
|
||||
SQLITE_DONE(SQLite3Jni.SQLITE_DONE),
|
||||
SQLITE_ERROR_MISSING_COLLSEQ(SQLite3Jni.SQLITE_ERROR_MISSING_COLLSEQ),
|
||||
SQLITE_ERROR_RETRY(SQLite3Jni.SQLITE_ERROR_RETRY),
|
||||
SQLITE_ERROR_SNAPSHOT(SQLite3Jni.SQLITE_ERROR_SNAPSHOT),
|
||||
SQLITE_IOERR_READ(SQLite3Jni.SQLITE_IOERR_READ),
|
||||
SQLITE_IOERR_SHORT_READ(SQLite3Jni.SQLITE_IOERR_SHORT_READ),
|
||||
SQLITE_IOERR_WRITE(SQLite3Jni.SQLITE_IOERR_WRITE),
|
||||
SQLITE_IOERR_FSYNC(SQLite3Jni.SQLITE_IOERR_FSYNC),
|
||||
SQLITE_IOERR_DIR_FSYNC(SQLite3Jni.SQLITE_IOERR_DIR_FSYNC),
|
||||
SQLITE_IOERR_TRUNCATE(SQLite3Jni.SQLITE_IOERR_TRUNCATE),
|
||||
SQLITE_IOERR_FSTAT(SQLite3Jni.SQLITE_IOERR_FSTAT),
|
||||
SQLITE_IOERR_UNLOCK(SQLite3Jni.SQLITE_IOERR_UNLOCK),
|
||||
SQLITE_IOERR_RDLOCK(SQLite3Jni.SQLITE_IOERR_RDLOCK),
|
||||
SQLITE_IOERR_DELETE(SQLite3Jni.SQLITE_IOERR_DELETE),
|
||||
SQLITE_IOERR_BLOCKED(SQLite3Jni.SQLITE_IOERR_BLOCKED),
|
||||
SQLITE_IOERR_NOMEM(SQLite3Jni.SQLITE_IOERR_NOMEM),
|
||||
SQLITE_IOERR_ACCESS(SQLite3Jni.SQLITE_IOERR_ACCESS),
|
||||
SQLITE_IOERR_CHECKRESERVEDLOCK(SQLite3Jni.SQLITE_IOERR_CHECKRESERVEDLOCK),
|
||||
SQLITE_IOERR_LOCK(SQLite3Jni.SQLITE_IOERR_LOCK),
|
||||
SQLITE_IOERR_CLOSE(SQLite3Jni.SQLITE_IOERR_CLOSE),
|
||||
SQLITE_IOERR_DIR_CLOSE(SQLite3Jni.SQLITE_IOERR_DIR_CLOSE),
|
||||
SQLITE_IOERR_SHMOPEN(SQLite3Jni.SQLITE_IOERR_SHMOPEN),
|
||||
SQLITE_IOERR_SHMSIZE(SQLite3Jni.SQLITE_IOERR_SHMSIZE),
|
||||
SQLITE_IOERR_SHMLOCK(SQLite3Jni.SQLITE_IOERR_SHMLOCK),
|
||||
SQLITE_IOERR_SHMMAP(SQLite3Jni.SQLITE_IOERR_SHMMAP),
|
||||
SQLITE_IOERR_SEEK(SQLite3Jni.SQLITE_IOERR_SEEK),
|
||||
SQLITE_IOERR_DELETE_NOENT(SQLite3Jni.SQLITE_IOERR_DELETE_NOENT),
|
||||
SQLITE_IOERR_MMAP(SQLite3Jni.SQLITE_IOERR_MMAP),
|
||||
SQLITE_IOERR_GETTEMPPATH(SQLite3Jni.SQLITE_IOERR_GETTEMPPATH),
|
||||
SQLITE_IOERR_CONVPATH(SQLite3Jni.SQLITE_IOERR_CONVPATH),
|
||||
SQLITE_IOERR_VNODE(SQLite3Jni.SQLITE_IOERR_VNODE),
|
||||
SQLITE_IOERR_AUTH(SQLite3Jni.SQLITE_IOERR_AUTH),
|
||||
SQLITE_IOERR_BEGIN_ATOMIC(SQLite3Jni.SQLITE_IOERR_BEGIN_ATOMIC),
|
||||
SQLITE_IOERR_COMMIT_ATOMIC(SQLite3Jni.SQLITE_IOERR_COMMIT_ATOMIC),
|
||||
SQLITE_IOERR_ROLLBACK_ATOMIC(SQLite3Jni.SQLITE_IOERR_ROLLBACK_ATOMIC),
|
||||
SQLITE_IOERR_DATA(SQLite3Jni.SQLITE_IOERR_DATA),
|
||||
SQLITE_IOERR_CORRUPTFS(SQLite3Jni.SQLITE_IOERR_CORRUPTFS),
|
||||
SQLITE_LOCKED_SHAREDCACHE(SQLite3Jni.SQLITE_LOCKED_SHAREDCACHE),
|
||||
SQLITE_LOCKED_VTAB(SQLite3Jni.SQLITE_LOCKED_VTAB),
|
||||
SQLITE_BUSY_RECOVERY(SQLite3Jni.SQLITE_BUSY_RECOVERY),
|
||||
SQLITE_BUSY_SNAPSHOT(SQLite3Jni.SQLITE_BUSY_SNAPSHOT),
|
||||
SQLITE_BUSY_TIMEOUT(SQLite3Jni.SQLITE_BUSY_TIMEOUT),
|
||||
SQLITE_CANTOPEN_NOTEMPDIR(SQLite3Jni.SQLITE_CANTOPEN_NOTEMPDIR),
|
||||
SQLITE_CANTOPEN_ISDIR(SQLite3Jni.SQLITE_CANTOPEN_ISDIR),
|
||||
SQLITE_CANTOPEN_FULLPATH(SQLite3Jni.SQLITE_CANTOPEN_FULLPATH),
|
||||
SQLITE_CANTOPEN_CONVPATH(SQLite3Jni.SQLITE_CANTOPEN_CONVPATH),
|
||||
SQLITE_CANTOPEN_SYMLINK(SQLite3Jni.SQLITE_CANTOPEN_SYMLINK),
|
||||
SQLITE_CORRUPT_VTAB(SQLite3Jni.SQLITE_CORRUPT_VTAB),
|
||||
SQLITE_CORRUPT_SEQUENCE(SQLite3Jni.SQLITE_CORRUPT_SEQUENCE),
|
||||
SQLITE_CORRUPT_INDEX(SQLite3Jni.SQLITE_CORRUPT_INDEX),
|
||||
SQLITE_READONLY_RECOVERY(SQLite3Jni.SQLITE_READONLY_RECOVERY),
|
||||
SQLITE_READONLY_CANTLOCK(SQLite3Jni.SQLITE_READONLY_CANTLOCK),
|
||||
SQLITE_READONLY_ROLLBACK(SQLite3Jni.SQLITE_READONLY_ROLLBACK),
|
||||
SQLITE_READONLY_DBMOVED(SQLite3Jni.SQLITE_READONLY_DBMOVED),
|
||||
SQLITE_READONLY_CANTINIT(SQLite3Jni.SQLITE_READONLY_CANTINIT),
|
||||
SQLITE_READONLY_DIRECTORY(SQLite3Jni.SQLITE_READONLY_DIRECTORY),
|
||||
SQLITE_ABORT_ROLLBACK(SQLite3Jni.SQLITE_ABORT_ROLLBACK),
|
||||
SQLITE_CONSTRAINT_CHECK(SQLite3Jni.SQLITE_CONSTRAINT_CHECK),
|
||||
SQLITE_CONSTRAINT_COMMITHOOK(SQLite3Jni.SQLITE_CONSTRAINT_COMMITHOOK),
|
||||
SQLITE_CONSTRAINT_FOREIGNKEY(SQLite3Jni.SQLITE_CONSTRAINT_FOREIGNKEY),
|
||||
SQLITE_CONSTRAINT_FUNCTION(SQLite3Jni.SQLITE_CONSTRAINT_FUNCTION),
|
||||
SQLITE_CONSTRAINT_NOTNULL(SQLite3Jni.SQLITE_CONSTRAINT_NOTNULL),
|
||||
SQLITE_CONSTRAINT_PRIMARYKEY(SQLite3Jni.SQLITE_CONSTRAINT_PRIMARYKEY),
|
||||
SQLITE_CONSTRAINT_TRIGGER(SQLite3Jni.SQLITE_CONSTRAINT_TRIGGER),
|
||||
SQLITE_CONSTRAINT_UNIQUE(SQLite3Jni.SQLITE_CONSTRAINT_UNIQUE),
|
||||
SQLITE_CONSTRAINT_VTAB(SQLite3Jni.SQLITE_CONSTRAINT_VTAB),
|
||||
SQLITE_CONSTRAINT_ROWID(SQLite3Jni.SQLITE_CONSTRAINT_ROWID),
|
||||
SQLITE_CONSTRAINT_PINNED(SQLite3Jni.SQLITE_CONSTRAINT_PINNED),
|
||||
SQLITE_CONSTRAINT_DATATYPE(SQLite3Jni.SQLITE_CONSTRAINT_DATATYPE),
|
||||
SQLITE_NOTICE_RECOVER_WAL(SQLite3Jni.SQLITE_NOTICE_RECOVER_WAL),
|
||||
SQLITE_NOTICE_RECOVER_ROLLBACK(SQLite3Jni.SQLITE_NOTICE_RECOVER_ROLLBACK),
|
||||
SQLITE_WARNING_AUTOINDEX(SQLite3Jni.SQLITE_WARNING_AUTOINDEX),
|
||||
SQLITE_AUTH_USER(SQLite3Jni.SQLITE_AUTH_USER),
|
||||
SQLITE_OK_LOAD_PERMANENTLY(SQLite3Jni.SQLITE_OK_LOAD_PERMANENTLY);
|
||||
|
||||
public final int value;
|
||||
|
||||
ResultCode(int rc){
|
||||
value = rc;
|
||||
ResultCodeMap.set(rc, this);
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the entry from this enum for the given result code, or
|
||||
null if no match is found.
|
||||
*/
|
||||
public static ResultCode getEntryForInt(int rc){
|
||||
return ResultCodeMap.get(rc);
|
||||
}
|
||||
|
||||
/**
|
||||
Internal level of indirection required because we cannot initialize
|
||||
static enum members in an enum before the enum constructor is
|
||||
invoked.
|
||||
*/
|
||||
private static final class ResultCodeMap {
|
||||
private static final java.util.Map<Integer,ResultCode> i2e
|
||||
= new java.util.HashMap<>();
|
||||
private static void set(int rc, ResultCode e){ i2e.put(rc, e); }
|
||||
private static ResultCode get(int rc){ return i2e.get(rc); }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
** 2023-07-22
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
Callback proxy for use with sqlite3_rollback_hook().
|
||||
*/
|
||||
public interface RollbackHook {
|
||||
/**
|
||||
Works as documented for the sqlite3_rollback_hook() callback.
|
||||
Must not throw.
|
||||
*/
|
||||
void xRollbackHook();
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
** 2023-07-22
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
SQLFunction is used in conjunction with the
|
||||
sqlite3_create_function() JNI-bound API to give that native code
|
||||
access to the callback functions needed in order to implement SQL
|
||||
functions in Java.
|
||||
|
||||
This class is not used by itself, but is a marker base class. The
|
||||
three UDF types are modelled by the inner classes Scalar,
|
||||
Aggregate<T>, and Window<T>. Most simply, clients may create
|
||||
anonymous classes from those to implement UDFs. Clients are free to
|
||||
create their own classes for use with UDFs, so long as they conform
|
||||
to the public interfaces defined by those three classes. The JNI
|
||||
layer only actively relies on the SQLFunction base class.
|
||||
*/
|
||||
public abstract class SQLFunction {
|
||||
|
||||
/**
|
||||
PerContextState assists aggregate and window functions in
|
||||
managinga their accumulator state across calls to the UDF's
|
||||
callbacks.
|
||||
|
||||
If a given aggregate or window function is called multiple times
|
||||
in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)...,
|
||||
then the clients need some way of knowing which call is which so
|
||||
that they can map their state between their various UDF callbacks
|
||||
and reset it via xFinal(). This class takes care of such
|
||||
mappings.
|
||||
|
||||
This class works by mapping
|
||||
sqlite3_context.getAggregateContext() to a single piece of
|
||||
state, of a client-defined type (the T part of this class), which
|
||||
persists across a "matching set" of the UDF's callbacks.
|
||||
|
||||
This class is a helper providing commonly-needed functionality -
|
||||
it is not required for use with aggregate or window functions.
|
||||
Client UDFs are free to perform such mappings using custom
|
||||
approaches. The provided Aggregate<T> and Window<T> classes
|
||||
use this.
|
||||
*/
|
||||
public static final class PerContextState<T> {
|
||||
private final java.util.Map<Long,ValueHolder<T>> map
|
||||
= new java.util.HashMap<>();
|
||||
|
||||
/**
|
||||
Should be called from a UDF's xStep(), xValue(), and xInverse()
|
||||
methods, passing it that method's first argument and an initial
|
||||
value for the persistent state. If there is currently no
|
||||
mapping for cx.getAggregateContext() within the map, one is
|
||||
created using the given initial value, else the existing one is
|
||||
used and the 2nd argument is ignored. It returns a
|
||||
ValueHolder<T> which can be used to modify that state directly
|
||||
without requiring that the client update the underlying map's
|
||||
entry.
|
||||
|
||||
T must be of a type which can be legally stored as a value in
|
||||
java.util.HashMap<KeyType,T>.
|
||||
*/
|
||||
public ValueHolder<T> getAggregateState(sqlite3_context cx, T initialValue){
|
||||
ValueHolder<T> rc = map.get(cx.getAggregateContext());
|
||||
if(null == rc){
|
||||
map.put(cx.getAggregateContext(), rc = new ValueHolder<>(initialValue));
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
Should be called from a UDF's xFinal() method and passed that
|
||||
method's first argument. This function removes the value
|
||||
associated with cx.getAggregateContext() from the map and
|
||||
returns it, returning null if no other UDF method has been
|
||||
called to set up such a mapping. The latter condition will be
|
||||
the case if a UDF is used in a statement which has no result
|
||||
rows.
|
||||
*/
|
||||
public T takeAggregateState(sqlite3_context cx){
|
||||
final ValueHolder<T> h = map.remove(cx.getAggregateContext());
|
||||
return null==h ? null : h.value;
|
||||
}
|
||||
}
|
||||
|
||||
//! Subclass for creating scalar functions.
|
||||
public static abstract class Scalar extends SQLFunction {
|
||||
|
||||
//! As for the xFunc() argument of the C API's sqlite3_create_function()
|
||||
public abstract void xFunc(sqlite3_context cx, sqlite3_value[] args);
|
||||
|
||||
/**
|
||||
Optionally override to be notified when the UDF is finalized by
|
||||
SQLite.
|
||||
*/
|
||||
public void xDestroy() {}
|
||||
}
|
||||
|
||||
/**
|
||||
SQLFunction Subclass for creating aggregate functions. Its T is
|
||||
the data type of its "accumulator" state, an instance of which is
|
||||
intended to be be managed using the getAggregateState() and
|
||||
takeAggregateState() methods.
|
||||
*/
|
||||
public static abstract class Aggregate<T> extends SQLFunction {
|
||||
|
||||
//! As for the xStep() argument of the C API's sqlite3_create_function()
|
||||
public abstract void xStep(sqlite3_context cx, sqlite3_value[] args);
|
||||
|
||||
//! As for the xFinal() argument of the C API's sqlite3_create_function()
|
||||
public abstract void xFinal(sqlite3_context cx);
|
||||
|
||||
/**
|
||||
Optionally override to be notified when the UDF is finalized by
|
||||
SQLite.
|
||||
*/
|
||||
public void xDestroy() {}
|
||||
|
||||
//! Per-invocation state for the UDF.
|
||||
private final PerContextState<T> map = new PerContextState<>();
|
||||
|
||||
/**
|
||||
To be called from the implementation's xStep() method, as well
|
||||
as the xValue() and xInverse() methods of the Window<T>
|
||||
subclass, to fetch the current per-call UDF state. On the
|
||||
first call to this method for any given sqlite3_context
|
||||
argument, the context is set to the given initial value. On all other
|
||||
calls, the 2nd argument is ignored.
|
||||
|
||||
@see PerContextState<T>#takeAggregateState()
|
||||
*/
|
||||
protected final ValueHolder<T> getAggregateState(sqlite3_context cx, T initialValue){
|
||||
return map.getAggregateState(cx, initialValue);
|
||||
}
|
||||
|
||||
/**
|
||||
To be called from the implementation's xFinal() method to fetch
|
||||
the final state of the UDF and remove its mapping.
|
||||
|
||||
@see PerContextState<T>#takeAggregateState()
|
||||
*/
|
||||
protected final T takeAggregateState(sqlite3_context cx){
|
||||
return map.takeAggregateState(cx);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
An SQLFunction subclass for creating window functions. Note that
|
||||
Window<T> inherits from Aggregate<T> and each instance is
|
||||
required to implement the inherited abstract methods from that
|
||||
class. See Aggregate<T> for information on managing the UDF's
|
||||
invocation-specific state.
|
||||
*/
|
||||
public static abstract class Window<T> extends Aggregate<T> {
|
||||
|
||||
//! As for the xInverse() argument of the C API's sqlite3_create_window_function()
|
||||
public abstract void xInverse(sqlite3_context cx, sqlite3_value[] args);
|
||||
|
||||
//! As for the xValue() argument of the C API's sqlite3_create_window_function()
|
||||
public abstract void xValue(sqlite3_context cx);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
** 2023-08-04
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file contains a set of tests for the sqlite3 JNI bindings.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
import static org.sqlite.jni.SQLite3Jni.*;
|
||||
import static org.sqlite.jni.Tester1.*;
|
||||
|
||||
public class TesterFts5 {
|
||||
|
||||
private static void test1(){
|
||||
Fts5ExtensionApi fea = Fts5ExtensionApi.getInstance();
|
||||
affirm( null != fea );
|
||||
affirm( fea.getNativePointer() != 0 );
|
||||
affirm( fea == Fts5ExtensionApi.getInstance() )/*singleton*/;
|
||||
|
||||
sqlite3 db = createNewDb();
|
||||
fts5_api fApi = fts5_api.getInstanceForDb(db);
|
||||
affirm( fApi != null );
|
||||
affirm( fApi == fts5_api.getInstanceForDb(db) /* singleton per db */ );
|
||||
|
||||
execSql(db, new String[] {
|
||||
"CREATE VIRTUAL TABLE ft USING fts5(a, b);",
|
||||
"INSERT INTO ft(rowid, a, b) VALUES(1, 'X Y', 'Y Z');",
|
||||
"INSERT INTO ft(rowid, a, b) VALUES(2, 'A Z', 'Y Y');"
|
||||
});
|
||||
|
||||
final String pUserData = "This is pUserData";
|
||||
ValueHolder<Boolean> xDestroyCalled = new ValueHolder<>(false);
|
||||
ValueHolder<Integer> xFuncCount = new ValueHolder<>(0);
|
||||
final fts5_extension_function func = new fts5_extension_function(){
|
||||
public void xFunction(Fts5ExtensionApi ext, Fts5Context fCx,
|
||||
sqlite3_context pCx, sqlite3_value argv[]){
|
||||
int nCols = ext.xColumnCount(fCx);
|
||||
affirm( 2 == nCols );
|
||||
affirm( nCols == argv.length );
|
||||
affirm( ext.xUserData(fCx) == pUserData );
|
||||
if(true){
|
||||
OutputPointer.String op = new OutputPointer.String();
|
||||
for(int i = 0; i < nCols; ++i ){
|
||||
int rc = ext.xColumnText(fCx, i, op);
|
||||
affirm( 0 == rc );
|
||||
final String val = op.value;
|
||||
affirm( val.equals(sqlite3_value_text(argv[i])) );
|
||||
//outln("xFunction col "+i+": "+val);
|
||||
}
|
||||
}
|
||||
++xFuncCount.value;
|
||||
}
|
||||
public void xDestroy(){
|
||||
xDestroyCalled.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
int rc = fApi.xCreateFunction("myaux", pUserData, func);
|
||||
affirm( 0==rc );
|
||||
|
||||
affirm( 0==xFuncCount.value );
|
||||
execSql(db, "select myaux(ft,a,b) from ft;");
|
||||
affirm( 2==xFuncCount.value );
|
||||
affirm( !xDestroyCalled.value );
|
||||
sqlite3_close_v2(db);
|
||||
affirm( xDestroyCalled.value );
|
||||
}
|
||||
|
||||
public TesterFts5(){
|
||||
int oldAffirmCount = Tester1.affirmCount;
|
||||
Tester1.affirmCount = 0;
|
||||
final long timeStart = System.nanoTime();
|
||||
test1();
|
||||
final long timeEnd = System.nanoTime();
|
||||
outln("FTS5 Tests done. Metrics:");
|
||||
outln("\tAssertions checked: "+Tester1.affirmCount);
|
||||
outln("\tTotal time = "
|
||||
+((timeEnd - timeStart)/1000000.0)+"ms");
|
||||
Tester1.affirmCount = oldAffirmCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
** 2023-07-22
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
Callback proxy for use with sqlite3_trace_v2().
|
||||
*/
|
||||
public interface Tracer {
|
||||
/**
|
||||
Achtung: this interface is subject to change because the current
|
||||
approach to mapping the passed-in natives back to Java is
|
||||
uncomfortably quirky.
|
||||
|
||||
Called by sqlite3 for various tracing operations, as per
|
||||
sqlite3_trace_v2(). Note that this interface elides the 2nd
|
||||
argument to the native trace callback, as that role is better
|
||||
filled by instance-local state.
|
||||
|
||||
The 2nd argument to this function, if non-0, will be a native
|
||||
pointer to either an sqlite3 or sqlite3_stmt object, depending on
|
||||
the first argument (see below). Client code can pass it to the
|
||||
sqlite3 resp. sqlite3_stmt constructor to create a wrapping
|
||||
object, if necessary. This API does not do so by default because
|
||||
tracing can be called frequently, creating such a wrapper for
|
||||
each call is comparatively expensive, and the objects are
|
||||
probably only seldom useful.
|
||||
|
||||
The final argument to this function is the "X" argument
|
||||
documented for sqlite3_trace() and sqlite3_trace_v2(). Its type
|
||||
depends on value of the first argument:
|
||||
|
||||
- SQLITE_TRACE_STMT: pNative is a sqlite3_stmt. pX is a string
|
||||
containing the prepared SQL, with one caveat: JNI only provides
|
||||
us with the ability to convert that string to MUTF-8, as
|
||||
opposed to standard UTF-8, and is cannot be ruled out that that
|
||||
difference may be significant for certain inputs. The
|
||||
alternative would be that we first convert it to UTF-16 before
|
||||
passing it on, but there's no readily-available way to do that
|
||||
without calling back into the db to peform the conversion
|
||||
(which would lead to further tracing).
|
||||
|
||||
- SQLITE_TRACE_PROFILE: pNative is a sqlite3_stmt. pX is a Long
|
||||
holding an approximate number of nanoseconds the statement took
|
||||
to run.
|
||||
|
||||
- SQLITE_TRACE_ROW: pNative is a sqlite3_stmt. pX is null.
|
||||
|
||||
- SQLITE_TRACE_CLOSE: pNative is a sqlite3. pX is null.
|
||||
*/
|
||||
int xCallback(int traceFlag, Object pNative, Object pX);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
** 2023-07-22
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
Callback proxy for use with sqlite3_update_hook().
|
||||
*/
|
||||
public interface UpdateHook {
|
||||
/**
|
||||
Works as documented for the sqlite3_update_hook() callback.
|
||||
Must not throw.
|
||||
*/
|
||||
void xUpdateHook(int opId, String dbName, String tableName, long rowId);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
** 2023-07-21
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
A helper class which simply holds a single value. Its current use
|
||||
is for communicating values out of anonymous classes, as doing so
|
||||
requires a "final" reference.
|
||||
*/
|
||||
public class ValueHolder<T> {
|
||||
public T value;
|
||||
public ValueHolder(){}
|
||||
public ValueHolder(T v){value = v;}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
** 2023-08-05
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
INCOMPLETE AND COMPLETELY UNTESTED.
|
||||
|
||||
A wrapper for communicating C-level (fts5_api*) instances with
|
||||
Java. These wrappers do not own their associated pointer, they
|
||||
simply provide a type-safe way to communicate it between Java and C
|
||||
via JNI.
|
||||
*/
|
||||
public final class fts5_api extends NativePointerHolder<fts5_api> {
|
||||
/* Only invoked from JNI */
|
||||
private fts5_api(){}
|
||||
public final int iVersion = 2;
|
||||
|
||||
/**
|
||||
Returns the fts5_api instance associated with the given db, or
|
||||
null if something goes horribly wrong.
|
||||
*/
|
||||
public static synchronized native fts5_api getInstanceForDb(@NotNull sqlite3 db);
|
||||
|
||||
// int (*xCreateTokenizer)(
|
||||
// fts5_api *pApi,
|
||||
// const char *zName,
|
||||
// void *pContext,
|
||||
// fts5_tokenizer *pTokenizer,
|
||||
// void (*xDestroy)(void*)
|
||||
// );
|
||||
|
||||
// /* Find an existing tokenizer */
|
||||
// int (*xFindTokenizer)(
|
||||
// fts5_api *pApi,
|
||||
// const char *zName,
|
||||
// void **ppContext,
|
||||
// fts5_tokenizer *pTokenizer
|
||||
// );
|
||||
|
||||
// /* Create a new auxiliary function */
|
||||
// int (*xCreateFunction)(
|
||||
// fts5_api *pApi,
|
||||
// const char *zName,
|
||||
// void *pContext,
|
||||
// fts5_extension_function xFunction,
|
||||
// void (*xDestroy)(void*)
|
||||
// );
|
||||
|
||||
public synchronized native int xCreateFunction(@NotNull String name,
|
||||
@Nullable Object userData,
|
||||
@NotNull fts5_extension_function xFunction);
|
||||
|
||||
public int xCreateFunction(@NotNull String name,
|
||||
@NotNull fts5_extension_function xFunction){
|
||||
return xCreateFunction(name, null, xFunction);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
** 2023-08-05
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
JNI-level wrapper for C's fts5_extension_function type.
|
||||
|
||||
*/
|
||||
public abstract class fts5_extension_function {
|
||||
// typedef void (*fts5_extension_function)(
|
||||
// const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
|
||||
// Fts5Context *pFts, /* First arg to pass to pApi functions */
|
||||
// sqlite3_context *pCtx, /* Context for returning result/error */
|
||||
// int nVal, /* Number of values in apVal[] array */
|
||||
// sqlite3_value **apVal /* Array of trailing arguments */
|
||||
// );
|
||||
|
||||
/**
|
||||
The callback implementation, corresponding to the xFunction
|
||||
argument of C's fts5_api::xCreateFunction().
|
||||
*/
|
||||
public abstract void xFunction(Fts5ExtensionApi ext, Fts5Context fCx,
|
||||
sqlite3_context pCx, sqlite3_value argv[]);
|
||||
//! Optionally override
|
||||
public void xDestroy(){}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
** 2023-08-05
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
INCOMPLETE AND COMPLETELY UNTESTED.
|
||||
|
||||
A wrapper for communicating C-level (fts5_tokenizer*) instances with
|
||||
Java. These wrappers do not own their associated pointer, they
|
||||
simply provide a type-safe way to communicate it between Java and C
|
||||
via JNI.
|
||||
*/
|
||||
public final class fts5_tokenizer extends NativePointerHolder<fts5_tokenizer> {
|
||||
/* Only invoked by JNI */
|
||||
private fts5_tokenizer(){}
|
||||
|
||||
// int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut);
|
||||
// void (*xDelete)(Fts5Tokenizer*);
|
||||
|
||||
public native int xTokenize(@NotNull Fts5Tokenizer t, int tokFlags,
|
||||
@NotNull byte pText[],
|
||||
@NotNull Fts5.xTokenizeCallback callback);
|
||||
|
||||
|
||||
// int (*xTokenize)(Fts5Tokenizer*,
|
||||
// void *pCtx,
|
||||
// int flags, /* Mask of FTS5_TOKENIZE_* flags */
|
||||
// const char *pText, int nText,
|
||||
// int (*xToken)(
|
||||
// void *pCtx, /* Copy of 2nd argument to xTokenize() */
|
||||
// int tflags, /* Mask of FTS5_TOKEN_* flags */
|
||||
// const char *pToken, /* Pointer to buffer containing token */
|
||||
// int nToken, /* Size of token in bytes */
|
||||
// int iStart, /* Byte offset of token within input text */
|
||||
// int iEnd /* Byte offset of end of token within input text */
|
||||
// )
|
||||
// );
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
** 2023-07-21
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
A wrapper for communicating C-level (sqlite3*) instances with
|
||||
Java. These wrappers do not own their associated pointer, they
|
||||
simply provide a type-safe way to communicate it between Java
|
||||
and C via JNI.
|
||||
*/
|
||||
public final class sqlite3 extends NativePointerHolder<sqlite3> {
|
||||
// Only invoked from JNI
|
||||
private sqlite3(){}
|
||||
|
||||
public String toString(){
|
||||
long ptr = getNativePointer();
|
||||
if( 0==ptr ){
|
||||
return sqlite3.class.getSimpleName()+"@null";
|
||||
}
|
||||
String fn = SQLite3Jni.sqlite3_db_filename(this, "main");
|
||||
return sqlite3.class.getSimpleName()
|
||||
+"@"+String.format("0x%08x",ptr)
|
||||
+"["+((null == fn) ? "<unnamed>" : fn)+"]"
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
** 2023-07-21
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
sqlite3_context instances are used in conjunction with user-defined
|
||||
SQL functions (a.k.a. UDFs).
|
||||
*/
|
||||
public final class sqlite3_context extends NativePointerHolder<sqlite3_context> {
|
||||
/**
|
||||
For use only by the JNI layer. It's permitted to set this even
|
||||
though it's private.
|
||||
*/
|
||||
private long aggregateContext = 0;
|
||||
|
||||
/**
|
||||
getAggregateContext() corresponds to C's
|
||||
sqlite3_aggregate_context(), with a slightly different interface
|
||||
to account for cross-language differences. It serves the same
|
||||
purposes in a slightly different way: it provides a key which is
|
||||
stable across invocations of "matching sets" of a UDF's callbacks,
|
||||
such that all calls into those callbacks can determine which "set"
|
||||
of those calls they belong to.
|
||||
|
||||
If this object is being used in the context of an aggregate or
|
||||
window UDF, this function returns a non-0 value which is distinct
|
||||
for each set of UDF callbacks from a single invocation of the
|
||||
UDF, otherwise it returns 0. The returned value is only only
|
||||
valid within the context of execution of a single SQL statement,
|
||||
and may be re-used by future invocations of the UDF in different
|
||||
SQL statements.
|
||||
|
||||
Consider this SQL, where MYFUNC is a user-defined aggregate function:
|
||||
|
||||
SELECT MYFUNC(A), MYFUNC(B) FROM T;
|
||||
|
||||
The xStep() and xFinal() methods of the callback need to be able
|
||||
to differentiate between those two invocations in order to
|
||||
perform their work properly. The value returned by
|
||||
getAggregateContext() will be distinct for each of those
|
||||
invocations of MYFUNC() and is intended to be used as a lookup
|
||||
key for mapping callback invocations to whatever client-defined
|
||||
state is needed by the UDF.
|
||||
|
||||
There is one case where this will return 0 in the context of an
|
||||
aggregate or window function: if the result set has no rows,
|
||||
the UDF's xFinal() will be called without any other x...() members
|
||||
having been called. In that one case, no aggregate context key will
|
||||
have been generated. xFinal() implementations need to be prepared to
|
||||
accept that condition as legal.
|
||||
*/
|
||||
public long getAggregateContext(){
|
||||
return aggregateContext;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
** 2023-07-21
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
/**
|
||||
A wrapper for communicating C-level (sqlite3_stmt*) instances with
|
||||
Java. These wrappers do not own their associated pointer, they
|
||||
simply provide a type-safe way to communicate it between Java and C
|
||||
via JNI.
|
||||
*/
|
||||
public final class sqlite3_stmt extends NativePointerHolder<sqlite3_stmt> {
|
||||
// Only invoked from JNI.
|
||||
private sqlite3_stmt(){}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
** 2023-07-21
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file is part of the JNI bindings for the sqlite3 C API.
|
||||
*/
|
||||
package org.sqlite.jni;
|
||||
|
||||
public final class sqlite3_value extends NativePointerHolder<sqlite3_value> {
|
||||
//! Invoked only from JNI.
|
||||
private sqlite3_value(){}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,269 @@
|
||||
# Specifications For A Rudimentary SQLite Test Script Interpreter
|
||||
|
||||
## Overview
|
||||
|
||||
The purpose of the Test Script Interpreter is to read and interpret
|
||||
script files that contain SQL commands and desired results. The
|
||||
interpreter will check results and report an discrepencies found.
|
||||
|
||||
The test script files are ASCII text files. The filename always ends with
|
||||
".test". Each script is evaluated independently; context does not carry
|
||||
forward from one script to the next. So, for example, the --null command
|
||||
run in one test script does not cause any changes in the behavior of
|
||||
subsequent test scripts. All open database connections are closed
|
||||
at the end of each test script. All database files created by a test
|
||||
script are deleted when the script finishes.
|
||||
|
||||
## Parsing Rules:
|
||||
|
||||
1. The test script is read line by line, where a line is a sequence of
|
||||
characters that runs up to the next '\\n' (0x0a) character or until
|
||||
the end of the file. There is never a need to read ahead past the
|
||||
end of the current line.
|
||||
|
||||
2. If any line contains the string " MODULE_NAME:" (with a space before
|
||||
the initial "M") or "MIXED_MODULE_NAME:" then that test script is
|
||||
incompatible with this spec. Processing of the test script should
|
||||
end immediately. There is no need to read any more of the file.
|
||||
In verbose mode, the interpreter might choose to emit an informational
|
||||
messages saying that the test script was abandoned due to an
|
||||
incompatible module type.
|
||||
|
||||
3. If any line contains the string "SCRIPT_MODULE_NAME:" then the input
|
||||
script is known to be of the correct type for this specification and
|
||||
processing may continue. The "MODULE_NAME" checking in steps 2 and 3
|
||||
may optionally be discontinued after sighting a "SCRIPT_MODULE_NAME".
|
||||
|
||||
4. If any line contains "REQUIRED_PROPERTIES:" and that substring is followed
|
||||
by any non-whitespace text, then the script is not compatible with this
|
||||
spec. Processing should stop immediately. In verbose mode, the
|
||||
interpreter might choose to emit an information message saying that the
|
||||
test script was abandoned due to unsupported requirement properties.
|
||||
|
||||
5. If any line begins with the "\|" (0x7c) character, that indicates that
|
||||
the input script is not compatible with this specification. Processing
|
||||
of the script should stop immediately. In verbose mode, the interpreter
|
||||
might choose to emit an informational message indicating that the
|
||||
test script was abandoned because it contained "a dbtotxt format database
|
||||
specification".
|
||||
|
||||
6. Any line that begins with "#" is a C-preprocessor line. The interpreter
|
||||
described by this spec does not know how to deal with C-preprocessor lines.
|
||||
Hence, processing should be abandoned. In verbose mode, the interpreter
|
||||
might emit an informational message similar to
|
||||
"script NAME abandoned due to C-preprocessor line: ..."
|
||||
|
||||
7. If a line begins with exactly two minus signs followed by a
|
||||
lowercase letter, that is a command. Process commands as described
|
||||
below.
|
||||
|
||||
8. All other lines should be accumulated into the "input buffer".
|
||||
The various commands will have access to this input buffer.
|
||||
Some commands will reset the buffer.
|
||||
|
||||
## Initialization
|
||||
|
||||
The initial state of the interpreter at the start of processing each script
|
||||
is as if the following command sequence had been run:
|
||||
|
||||
> ~~~
|
||||
--close all
|
||||
--db 0
|
||||
--new test.db
|
||||
--null nil
|
||||
~~~
|
||||
|
||||
In words, all database connections are closed except for connection 0 (the
|
||||
default) which is open on an empty database named "test.db". The string
|
||||
"nil" is displayed for NULL column values.
|
||||
|
||||
The only context carried forward after the evaluation of one test script
|
||||
into the evaluation of the next test script is the count of the number of
|
||||
tests run and the number of failures seen.
|
||||
|
||||
## Commands:
|
||||
|
||||
Each command looks like an SQL comment. The command begins at the left
|
||||
margin (no leading space) and starts with exactly 2 minus signs ("-").
|
||||
The command name consists of lowercase letters and maybe a "-" or two.
|
||||
Some commands have arguments.
|
||||
The arguments are separated from the command name by one or more spaces.
|
||||
|
||||
Commands have access to the input buffer and might reset the input buffer.
|
||||
The command can also optionally read (and consume) additional text from
|
||||
script that comes after the command.
|
||||
|
||||
Unknown or unrecognized commands indicate that the script contains features
|
||||
that are not (yet) supported by this specification. Processing of the
|
||||
script should terminate immediately. When this happens and when the
|
||||
interpreter is in a "verbose" mode, the interpreter might choose to emit
|
||||
an informational message along the lines of "test script NAME abandoned
|
||||
due to unsupported command: --whatever".
|
||||
|
||||
The initial implemention will only recognize a few commands. Other
|
||||
commands may be added later. The following is the initial set of
|
||||
commands:
|
||||
|
||||
### The --testcase command
|
||||
|
||||
Every test case starts with a --testcase command. The --testcase
|
||||
command resets both the "input buffer" and the "result buffer". The
|
||||
argument to the --testcase command is the name of the test case. That
|
||||
test case name is used for logging and debugging and when printing
|
||||
errors. The input buffer is set to the body of the test case.
|
||||
|
||||
### The --result command
|
||||
|
||||
The --result command tries to execute the text in the input buffer as SQL.
|
||||
For each row of result coming out of this SQL, the text of that result is
|
||||
appended to the "result buffer". If a result row contains multiple columns,
|
||||
the columns are processed from left to right. For each column, text is
|
||||
appended to the result buffer according to the following rules:
|
||||
|
||||
* If the result buffer already contains some text, append a space.
|
||||
(In this way, all column values and all row values are separated from
|
||||
each other by a single space.)
|
||||
|
||||
* If sqlite3_column_text() returns NULL, then append "nil" - or
|
||||
some other text that is specified by the --null command - and skip
|
||||
all subsequent rules.
|
||||
|
||||
* If sqlite3_column_text() is an empty string, append `{}` to the
|
||||
result buffer and skip all subsequent rules.
|
||||
|
||||
* If sqlite3_column_text() does not contain any special
|
||||
characters, append it to the result buffer without any
|
||||
formatting and skip all subsequent rules. Special characters are:
|
||||
0x00 to 0x20 (inclusive), double-quote (0x22), backslash (0x5c),
|
||||
curly braces (0x7b and 0x7d).
|
||||
|
||||
* If sqlite3_column_text() does not contains curly braces, then put
|
||||
the text inside of `{...}` and append it and skip all subsequent rules.
|
||||
|
||||
* Append the text within double-quotes (`"..."`) and within the text
|
||||
escape '"' and '\\' by prepending a single '\\' and escape any
|
||||
control characters (characters less than 0x20) using octal notation:
|
||||
'\\NNN'.
|
||||
|
||||
If an error is encountered while running the SQL, then append the
|
||||
symbolic C-preprocessor name for the error
|
||||
code (ex: "SQLITE_CONSTRAINT") as if it were a column value. Then append
|
||||
the error message text as if it where a column value. Then stop processing.
|
||||
|
||||
After the SQL text has been run, compare the content of the result buffer
|
||||
against the argument to the --result command and report a testing error if
|
||||
there are any differences.
|
||||
|
||||
The --result command resets the input buffer, but it does not reset
|
||||
the result buffer. This distinction does not matter for the --result
|
||||
command itself, but it is important for related commands like --glob
|
||||
and --notglob. Sometimes test cases will contains a bunch of SQL
|
||||
followed by multiple --glob and/or --notglob statements. All of the
|
||||
globs should be evaluted agains the result buffer correct, but the SQL
|
||||
should only be run once. This is accomplished by resetting the input
|
||||
buffer but not the result buffer.
|
||||
|
||||
### The --glob command
|
||||
|
||||
The --glob command works just like --result except that the argument to
|
||||
--glob is interpreted as a TEST-GLOB pattern and the results are compared
|
||||
using that glob pattern rather than using strcmp(). Other than that,
|
||||
the two operate the same.
|
||||
|
||||
The TEST-GLOB pattern is slightly different for a standard GLOB:
|
||||
|
||||
* The '*' character matches zero or more characters.
|
||||
|
||||
* The '?' character matches any single character
|
||||
|
||||
* The '[...]' character sequence machines a single character
|
||||
in between the brackets.
|
||||
|
||||
* The '#' character matches one or more digits (This is the main
|
||||
difference between standard unix-glob and TEST-GLOB. unix-glob
|
||||
does not have this feature. It was added to because it comes
|
||||
up a lot during SQLite testing.)
|
||||
|
||||
### The --notglob command
|
||||
|
||||
The --notglob command works just like --glob except that it reports an
|
||||
error if the GLOB does match, rather than if the GLOB does not matches.
|
||||
|
||||
### The --oom command
|
||||
|
||||
This command is to be used for out-of-memory testing. It means that
|
||||
OOM errors should be simulated to ensure that SQLite is able to deal with
|
||||
them. This command can be silently ignored for now. We might add support
|
||||
for this later.
|
||||
|
||||
### The --tableresult command
|
||||
|
||||
The --tableresult command works like --glob except that the GLOB pattern
|
||||
to be matched is taken from subsequent lines of the input script up to
|
||||
the next --end. Every span of one or more whitespace characters in this
|
||||
pattern text is collapsed into a single space (0x20).
|
||||
Leading and trailing whitespace are removed from the pattern.
|
||||
The --end that ends the GLOB pattern is not part of the GLOB pattern, but
|
||||
the --end is consumed from the script input.
|
||||
|
||||
### The --new and --open commands
|
||||
|
||||
The --new and --open commands cause a database file to be opened.
|
||||
The name of the file is the argument to the command. The --new command
|
||||
opens an initially empty database (it deletes the file before opening it)
|
||||
whereas the --open command opens an existing database if it already
|
||||
exists.
|
||||
|
||||
### The --db command
|
||||
|
||||
The script interpreter can have up to 7 different SQLite database
|
||||
connections open at a time. The --db command is used to switch between
|
||||
them. The argument to --db is an integer between 0 and 6 that selects
|
||||
which database connection to use moving forward.
|
||||
|
||||
### The --close command
|
||||
|
||||
The --close command causes an existing database connection to close.
|
||||
This command is a no-op if the database connection is not currently
|
||||
open. There can be up to 7 different database connections, numbered 0
|
||||
through 6. The number of the database connection to close is an
|
||||
argument to the --close command, which will fail if an out-of-range
|
||||
value is provided. Or if the argument to --close is "all" then all
|
||||
open database connections are closed. If passed no argument, the
|
||||
currently-active database is assumed.
|
||||
|
||||
### The --null command
|
||||
|
||||
The NULL command changes the text that is used to represent SQL NULL
|
||||
values in the result buffer.
|
||||
|
||||
### The --run command
|
||||
|
||||
The --run command executes text in the input buffer as if it where SQL.
|
||||
However, nothing is added to the result buffer. Any output from the SQL
|
||||
is silently ignored. Errors in the SQL are silently ignored.
|
||||
|
||||
The --run command normally executes the SQL in the current database
|
||||
connection. However, if --run has an argument that is an integer between
|
||||
0 and 6 then the SQL is run in the alternative database connection specified
|
||||
by that argument.
|
||||
|
||||
### The --json and --json-block commands
|
||||
|
||||
The --json and --json-block commands work like --result and --tableresult,
|
||||
respectively. The difference is that column values are appended to the
|
||||
result buffer literally, without ever enclosing the values in `{...}` or
|
||||
`"..."` and without escaping any characters in the column value and comparison
|
||||
is always an exact strcmp() not a GLOB.
|
||||
|
||||
### The --print command
|
||||
|
||||
The --print command emits both its arguments and its body (if any) to
|
||||
stdout, indenting each line of output.
|
||||
|
||||
### The --column-names command
|
||||
|
||||
The --column-names command requires 0 or 1 as an argument, to disable
|
||||
resp. enable it, and modifies SQL execution to include column names
|
||||
in output. When this option is on, each column value emitted gets
|
||||
prefixed by its column name, with a single space between them.
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
** This is a comment. There are many like it but this one is mine.
|
||||
**
|
||||
** SCRIPT_MODULE_NAME: sanity-check
|
||||
** xMIXED_MODULE_NAME: mixed-module
|
||||
** xMODULE_NAME: module-name
|
||||
** xREQUIRED_PROPERTIES: small fast reliable
|
||||
** xREQUIRED_PROPERTIES: RECURSIVE_TRIGGERS
|
||||
** xREQUIRED_PROPERTIES: TEMPSTORE_MEM TEMPSTORE_FILE
|
||||
**
|
||||
*/
|
||||
--print starting up 😃
|
||||
--close all
|
||||
--oom
|
||||
--db 0
|
||||
--new my.db
|
||||
--null zilch
|
||||
--testcase 1.0
|
||||
SELECT 1, null;
|
||||
--result 1 zilch
|
||||
--glob *zil*
|
||||
--notglob *ZIL*
|
||||
SELECT 1, 2;
|
||||
intentional error;
|
||||
--run
|
||||
--testcase json-1
|
||||
SELECT json_array(1,2,3)
|
||||
--json [1,2,3]
|
||||
--testcase tableresult-1
|
||||
select 1, 'a';
|
||||
select 2, 'b';
|
||||
--tableresult
|
||||
# [a-z]
|
||||
2 b
|
||||
--end
|
||||
--testcase json-block-1
|
||||
select json_array(1,2,3);
|
||||
select json_object('a',1,'b',2);
|
||||
--json-block
|
||||
[1,2,3]
|
||||
{"a":1,"b":2}
|
||||
--end
|
||||
--testcase col-names-on
|
||||
--column-names 1
|
||||
select 1 as 'a', 2 as 'b';
|
||||
--result a 1 b 2
|
||||
--testcase col-names-off
|
||||
--column-names 0
|
||||
select 1 as 'a', 2 as 'b';
|
||||
--result 1 2
|
||||
--close
|
||||
--print reached the end 😃
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
** This script must be marked as ignored because it contains
|
||||
** content which triggers that condition.
|
||||
**
|
||||
** SCRIPT_MODULE_NAME: ignored
|
||||
**
|
||||
*/
|
||||
|
||||
|
|
||||
+19
-5
@@ -148,9 +148,9 @@ static char* toBase64( u8 *pIn, int nbIn, char *pOut ){
|
||||
}
|
||||
|
||||
/* Skip over text which is not base64 numeral(s). */
|
||||
static char * skipNonB64( char *s ){
|
||||
static char * skipNonB64( char *s, int nc ){
|
||||
char c;
|
||||
while( (c = *s) && !IS_BX_DIGIT(BX_DV_PROTO(c)) ) ++s;
|
||||
while( nc-- > 0 && (c = *s) && !IS_BX_DIGIT(BX_DV_PROTO(c)) ) ++s;
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ static u8* fromBase64( char *pIn, int ncIn, u8 *pOut ){
|
||||
if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn;
|
||||
while( ncIn>0 && *pIn!=PAD_CHAR ){
|
||||
static signed char nboi[] = { 0, 0, 1, 2, 3 };
|
||||
char *pUse = skipNonB64(pIn);
|
||||
char *pUse = skipNonB64(pIn, ncIn);
|
||||
unsigned long qv = 0L;
|
||||
int nti, nbo, nac;
|
||||
ncIn -= (pUse - pIn);
|
||||
@@ -219,9 +219,16 @@ static void base64(sqlite3_context *context, int na, sqlite3_value *av[]){
|
||||
sqlite3_result_error(context, "blob expanded to base64 too big", -1);
|
||||
return;
|
||||
}
|
||||
bBuf = (u8*)sqlite3_value_blob(av[0]);
|
||||
if( !bBuf ){
|
||||
if( SQLITE_NOMEM==sqlite3_errcode(sqlite3_context_db_handle(context)) ){
|
||||
goto memFail;
|
||||
}
|
||||
sqlite3_result_text(context,"",-1,SQLITE_STATIC);
|
||||
break;
|
||||
}
|
||||
cBuf = sqlite3_malloc(nc);
|
||||
if( !cBuf ) goto memFail;
|
||||
bBuf = (u8*)sqlite3_value_blob(av[0]);
|
||||
nc = (int)(toBase64(bBuf, nb, cBuf) - cBuf);
|
||||
sqlite3_result_text(context, cBuf, nc, sqlite3_free);
|
||||
break;
|
||||
@@ -234,9 +241,16 @@ static void base64(sqlite3_context *context, int na, sqlite3_value *av[]){
|
||||
}else if( nb<1 ){
|
||||
nb = 1;
|
||||
}
|
||||
cBuf = (char *)sqlite3_value_text(av[0]);
|
||||
if( !cBuf ){
|
||||
if( SQLITE_NOMEM==sqlite3_errcode(sqlite3_context_db_handle(context)) ){
|
||||
goto memFail;
|
||||
}
|
||||
sqlite3_result_zeroblob(context, 0);
|
||||
break;
|
||||
}
|
||||
bBuf = sqlite3_malloc(nb);
|
||||
if( !bBuf ) goto memFail;
|
||||
cBuf = (char *)sqlite3_value_text(av[0]);
|
||||
nb = (int)(fromBase64(cBuf, nc, bBuf) - bBuf);
|
||||
sqlite3_result_blob(context, bBuf, nb, sqlite3_free);
|
||||
break;
|
||||
|
||||
+19
-5
@@ -140,9 +140,9 @@ static u8 base85DigitValue( char c ){
|
||||
#define B85_DARK_MAX 80
|
||||
|
||||
|
||||
static char * skipNonB85( char *s ){
|
||||
static char * skipNonB85( char *s, int nc ){
|
||||
char c;
|
||||
while( (c = *s) && !IS_B85(c) ) ++s;
|
||||
while( nc-- > 0 && (c = *s) && !IS_B85(c) ) ++s;
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ static u8* fromBase85( char *pIn, int ncIn, u8 *pOut ){
|
||||
if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn;
|
||||
while( ncIn>0 ){
|
||||
static signed char nboi[] = { 0, 0, 1, 2, 3, 4 };
|
||||
char *pUse = skipNonB85(pIn);
|
||||
char *pUse = skipNonB85(pIn, ncIn);
|
||||
unsigned long qv = 0L;
|
||||
int nti, nbo;
|
||||
ncIn -= (pUse - pIn);
|
||||
@@ -297,9 +297,16 @@ static void base85(sqlite3_context *context, int na, sqlite3_value *av[]){
|
||||
sqlite3_result_error(context, "blob expanded to base85 too big", -1);
|
||||
return;
|
||||
}
|
||||
bBuf = (u8*)sqlite3_value_blob(av[0]);
|
||||
if( !bBuf ){
|
||||
if( SQLITE_NOMEM==sqlite3_errcode(sqlite3_context_db_handle(context)) ){
|
||||
goto memFail;
|
||||
}
|
||||
sqlite3_result_text(context,"",-1,SQLITE_STATIC);
|
||||
break;
|
||||
}
|
||||
cBuf = sqlite3_malloc(nc);
|
||||
if( !cBuf ) goto memFail;
|
||||
bBuf = (u8*)sqlite3_value_blob(av[0]);
|
||||
nc = (int)(toBase85(bBuf, nb, cBuf, "\n") - cBuf);
|
||||
sqlite3_result_text(context, cBuf, nc, sqlite3_free);
|
||||
break;
|
||||
@@ -312,9 +319,16 @@ static void base85(sqlite3_context *context, int na, sqlite3_value *av[]){
|
||||
}else if( nb<1 ){
|
||||
nb = 1;
|
||||
}
|
||||
cBuf = (char *)sqlite3_value_text(av[0]);
|
||||
if( !cBuf ){
|
||||
if( SQLITE_NOMEM==sqlite3_errcode(sqlite3_context_db_handle(context)) ){
|
||||
goto memFail;
|
||||
}
|
||||
sqlite3_result_zeroblob(context, 0);
|
||||
break;
|
||||
}
|
||||
bBuf = sqlite3_malloc(nb);
|
||||
if( !bBuf ) goto memFail;
|
||||
cBuf = (char *)sqlite3_value_text(av[0]);
|
||||
nb = (int)(fromBase85(cBuf, nc, bBuf) - bBuf);
|
||||
sqlite3_result_blob(context, bBuf, nb, sqlite3_free);
|
||||
break;
|
||||
|
||||
+5
-2
@@ -69,9 +69,12 @@ __declspec(dllexport)
|
||||
#endif
|
||||
int sqlite3_basexx_init(sqlite3 *db, char **pzErr,
|
||||
const sqlite3_api_routines *pApi){
|
||||
int rc1;
|
||||
int rc2;
|
||||
|
||||
init_api_ptr(pApi);
|
||||
int rc1 = BASE64_INIT(db);
|
||||
int rc2 = BASE85_INIT(db);
|
||||
rc1 = BASE64_INIT(db);
|
||||
rc2 = BASE85_INIT(db);
|
||||
|
||||
if( rc1==SQLITE_OK && rc2==SQLITE_OK ){
|
||||
BASE64_EXPOSE(db, pzErr);
|
||||
|
||||
+345
-96
@@ -58,41 +58,24 @@ static void decimal_free(Decimal *p){
|
||||
}
|
||||
|
||||
/*
|
||||
** Allocate a new Decimal object. Initialize it to the number given
|
||||
** by the input string.
|
||||
** Allocate a new Decimal object initialized to the text in zIn[].
|
||||
** Return NULL if any kind of error occurs.
|
||||
*/
|
||||
static Decimal *decimal_new(
|
||||
sqlite3_context *pCtx,
|
||||
sqlite3_value *pIn,
|
||||
int nAlt,
|
||||
const unsigned char *zAlt
|
||||
){
|
||||
Decimal *p;
|
||||
int n, i;
|
||||
const unsigned char *zIn;
|
||||
static Decimal *decimalNewFromText(const char *zIn, int n){
|
||||
Decimal *p = 0;
|
||||
int i;
|
||||
int iExp = 0;
|
||||
|
||||
p = sqlite3_malloc( sizeof(*p) );
|
||||
if( p==0 ) goto new_no_mem;
|
||||
if( p==0 ) goto new_from_text_failed;
|
||||
p->sign = 0;
|
||||
p->oom = 0;
|
||||
p->isInit = 1;
|
||||
p->isNull = 0;
|
||||
p->nDigit = 0;
|
||||
p->nFrac = 0;
|
||||
if( zAlt ){
|
||||
n = nAlt,
|
||||
zIn = zAlt;
|
||||
}else{
|
||||
if( sqlite3_value_type(pIn)==SQLITE_NULL ){
|
||||
p->a = 0;
|
||||
p->isNull = 1;
|
||||
return p;
|
||||
}
|
||||
n = sqlite3_value_bytes(pIn);
|
||||
zIn = sqlite3_value_text(pIn);
|
||||
}
|
||||
p->a = sqlite3_malloc64( n+1 );
|
||||
if( p->a==0 ) goto new_no_mem;
|
||||
if( p->a==0 ) goto new_from_text_failed;
|
||||
for(i=0; isspace(zIn[i]); i++){}
|
||||
if( zIn[i]=='-' ){
|
||||
p->sign = 1;
|
||||
@@ -143,7 +126,7 @@ static Decimal *decimal_new(
|
||||
}
|
||||
if( iExp>0 ){
|
||||
p->a = sqlite3_realloc64(p->a, p->nDigit + iExp + 1 );
|
||||
if( p->a==0 ) goto new_no_mem;
|
||||
if( p->a==0 ) goto new_from_text_failed;
|
||||
memset(p->a+p->nDigit, 0, iExp);
|
||||
p->nDigit += iExp;
|
||||
}
|
||||
@@ -162,7 +145,7 @@ static Decimal *decimal_new(
|
||||
}
|
||||
if( iExp>0 ){
|
||||
p->a = sqlite3_realloc64(p->a, p->nDigit + iExp + 1 );
|
||||
if( p->a==0 ) goto new_no_mem;
|
||||
if( p->a==0 ) goto new_from_text_failed;
|
||||
memmove(p->a+iExp, p->a, p->nDigit);
|
||||
memset(p->a, 0, iExp);
|
||||
p->nDigit += iExp;
|
||||
@@ -171,7 +154,76 @@ static Decimal *decimal_new(
|
||||
}
|
||||
return p;
|
||||
|
||||
new_no_mem:
|
||||
new_from_text_failed:
|
||||
if( p ){
|
||||
if( p->a ) sqlite3_free(p->a);
|
||||
sqlite3_free(p);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Forward reference */
|
||||
static Decimal *decimalFromDouble(double);
|
||||
|
||||
/*
|
||||
** Allocate a new Decimal object from an sqlite3_value. Return a pointer
|
||||
** to the new object, or NULL if there is an error. If the pCtx argument
|
||||
** is not NULL, then errors are reported on it as well.
|
||||
**
|
||||
** If the pIn argument is SQLITE_TEXT or SQLITE_INTEGER, it is converted
|
||||
** directly into a Decimal. For SQLITE_FLOAT or for SQLITE_BLOB of length
|
||||
** 8 bytes, the resulting double value is expanded into its decimal equivalent.
|
||||
** If pIn is NULL or if it is a BLOB that is not exactly 8 bytes in length,
|
||||
** then NULL is returned.
|
||||
*/
|
||||
static Decimal *decimal_new(
|
||||
sqlite3_context *pCtx, /* Report error here, if not null */
|
||||
sqlite3_value *pIn, /* Construct the decimal object from this */
|
||||
int bTextOnly /* Always interpret pIn as text if true */
|
||||
){
|
||||
Decimal *p = 0;
|
||||
int eType = sqlite3_value_type(pIn);
|
||||
if( bTextOnly && (eType==SQLITE_FLOAT || eType==SQLITE_BLOB) ){
|
||||
eType = SQLITE_TEXT;
|
||||
}
|
||||
switch( eType ){
|
||||
case SQLITE_TEXT:
|
||||
case SQLITE_INTEGER: {
|
||||
const char *zIn = (const char*)sqlite3_value_text(pIn);
|
||||
int n = sqlite3_value_bytes(pIn);
|
||||
p = decimalNewFromText(zIn, n);
|
||||
if( p==0 ) goto new_failed;
|
||||
break;
|
||||
}
|
||||
|
||||
case SQLITE_FLOAT: {
|
||||
p = decimalFromDouble(sqlite3_value_double(pIn));
|
||||
break;
|
||||
}
|
||||
|
||||
case SQLITE_BLOB: {
|
||||
const unsigned char *x;
|
||||
unsigned int i;
|
||||
sqlite3_uint64 v = 0;
|
||||
double r;
|
||||
|
||||
if( sqlite3_value_bytes(pIn)!=sizeof(r) ) break;
|
||||
x = sqlite3_value_blob(pIn);
|
||||
for(i=0; i<sizeof(r); i++){
|
||||
v = (v<<8) | x[i];
|
||||
}
|
||||
memcpy(&r, &v, sizeof(r));
|
||||
p = decimalFromDouble(r);
|
||||
break;
|
||||
}
|
||||
|
||||
case SQLITE_NULL: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return p;
|
||||
|
||||
new_failed:
|
||||
if( pCtx ) sqlite3_result_error_nomem(pCtx);
|
||||
sqlite3_free(p);
|
||||
return 0;
|
||||
@@ -231,19 +283,64 @@ static void decimal_result(sqlite3_context *pCtx, Decimal *p){
|
||||
}
|
||||
|
||||
/*
|
||||
** SQL Function: decimal(X)
|
||||
**
|
||||
** Convert input X into decimal and then back into text
|
||||
** Make the given Decimal the result in an format similar to '%+#e'.
|
||||
** In other words, show exponential notation with leading and trailing
|
||||
** zeros omitted.
|
||||
*/
|
||||
static void decimalFunc(
|
||||
sqlite3_context *context,
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
){
|
||||
Decimal *p = decimal_new(context, argv[0], 0, 0);
|
||||
UNUSED_PARAMETER(argc);
|
||||
decimal_result(context, p);
|
||||
decimal_free(p);
|
||||
static void decimal_result_sci(sqlite3_context *pCtx, Decimal *p){
|
||||
char *z; /* The output buffer */
|
||||
int i; /* Loop counter */
|
||||
int nZero; /* Number of leading zeros */
|
||||
int nDigit; /* Number of digits not counting trailing zeros */
|
||||
int nFrac; /* Digits to the right of the decimal point */
|
||||
int exp; /* Exponent value */
|
||||
signed char zero; /* Zero value */
|
||||
signed char *a; /* Array of digits */
|
||||
|
||||
if( p==0 || p->oom ){
|
||||
sqlite3_result_error_nomem(pCtx);
|
||||
return;
|
||||
}
|
||||
if( p->isNull ){
|
||||
sqlite3_result_null(pCtx);
|
||||
return;
|
||||
}
|
||||
for(nDigit=p->nDigit; nDigit>0 && p->a[nDigit-1]==0; nDigit--){}
|
||||
for(nZero=0; nZero<nDigit && p->a[nZero]==0; nZero++){}
|
||||
nFrac = p->nFrac + (nDigit - p->nDigit);
|
||||
nDigit -= nZero;
|
||||
z = sqlite3_malloc( nDigit+20 );
|
||||
if( z==0 ){
|
||||
sqlite3_result_error_nomem(pCtx);
|
||||
return;
|
||||
}
|
||||
if( nDigit==0 ){
|
||||
zero = 0;
|
||||
a = &zero;
|
||||
nDigit = 1;
|
||||
nFrac = 0;
|
||||
}else{
|
||||
a = &p->a[nZero];
|
||||
}
|
||||
if( p->sign && nDigit>0 ){
|
||||
z[0] = '-';
|
||||
}else{
|
||||
z[0] = '+';
|
||||
}
|
||||
z[1] = a[0]+'0';
|
||||
z[2] = '.';
|
||||
if( nDigit==1 ){
|
||||
z[3] = '0';
|
||||
i = 4;
|
||||
}else{
|
||||
for(i=1; i<nDigit; i++){
|
||||
z[2+i] = a[i]+'0';
|
||||
}
|
||||
i = nDigit+2;
|
||||
}
|
||||
exp = nDigit - nFrac - 1;
|
||||
sqlite3_snprintf(nDigit+20-i, &z[i], "e%+03d", exp);
|
||||
sqlite3_result_text(pCtx, z, -1, sqlite3_free);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -296,9 +393,9 @@ static void decimalCmpFunc(
|
||||
int rc;
|
||||
|
||||
UNUSED_PARAMETER(argc);
|
||||
pA = decimal_new(context, argv[0], 0, 0);
|
||||
pA = decimal_new(context, argv[0], 1);
|
||||
if( pA==0 || pA->isNull ) goto cmp_done;
|
||||
pB = decimal_new(context, argv[1], 0, 0);
|
||||
pB = decimal_new(context, argv[1], 1);
|
||||
if( pB==0 || pB->isNull ) goto cmp_done;
|
||||
rc = decimal_cmp(pA, pB);
|
||||
if( rc<0 ) rc = -1;
|
||||
@@ -338,7 +435,7 @@ static void decimal_expand(Decimal *p, int nDigit, int nFrac){
|
||||
}
|
||||
|
||||
/*
|
||||
** Add the value pB into pA.
|
||||
** Add the value pB into pA. A := A + B.
|
||||
**
|
||||
** Both pA and pB might become denormalized by this routine.
|
||||
*/
|
||||
@@ -407,6 +504,172 @@ static void decimal_add(Decimal *pA, Decimal *pB){
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Multiply A by B. A := A * B
|
||||
**
|
||||
** All significant digits after the decimal point are retained.
|
||||
** Trailing zeros after the decimal point are omitted as long as
|
||||
** the number of digits after the decimal point is no less than
|
||||
** either the number of digits in either input.
|
||||
*/
|
||||
static void decimalMul(Decimal *pA, Decimal *pB){
|
||||
signed char *acc = 0;
|
||||
int i, j, k;
|
||||
int minFrac;
|
||||
|
||||
if( pA==0 || pA->oom || pA->isNull
|
||||
|| pB==0 || pB->oom || pB->isNull
|
||||
){
|
||||
goto mul_end;
|
||||
}
|
||||
acc = sqlite3_malloc64( pA->nDigit + pB->nDigit + 2 );
|
||||
if( acc==0 ){
|
||||
pA->oom = 1;
|
||||
goto mul_end;
|
||||
}
|
||||
memset(acc, 0, pA->nDigit + pB->nDigit + 2);
|
||||
minFrac = pA->nFrac;
|
||||
if( pB->nFrac<minFrac ) minFrac = pB->nFrac;
|
||||
for(i=pA->nDigit-1; i>=0; i--){
|
||||
signed char f = pA->a[i];
|
||||
int carry = 0, x;
|
||||
for(j=pB->nDigit-1, k=i+j+3; j>=0; j--, k--){
|
||||
x = acc[k] + f*pB->a[j] + carry;
|
||||
acc[k] = x%10;
|
||||
carry = x/10;
|
||||
}
|
||||
x = acc[k] + carry;
|
||||
acc[k] = x%10;
|
||||
acc[k-1] += x/10;
|
||||
}
|
||||
sqlite3_free(pA->a);
|
||||
pA->a = acc;
|
||||
acc = 0;
|
||||
pA->nDigit += pB->nDigit + 2;
|
||||
pA->nFrac += pB->nFrac;
|
||||
pA->sign ^= pB->sign;
|
||||
while( pA->nFrac>minFrac && pA->a[pA->nDigit-1]==0 ){
|
||||
pA->nFrac--;
|
||||
pA->nDigit--;
|
||||
}
|
||||
|
||||
mul_end:
|
||||
sqlite3_free(acc);
|
||||
}
|
||||
|
||||
/*
|
||||
** Create a new Decimal object that contains an integer power of 2.
|
||||
*/
|
||||
static Decimal *decimalPow2(int N){
|
||||
Decimal *pA = 0; /* The result to be returned */
|
||||
Decimal *pX = 0; /* Multiplier */
|
||||
if( N<-20000 || N>20000 ) goto pow2_fault;
|
||||
pA = decimalNewFromText("1.0", 3);
|
||||
if( pA==0 || pA->oom ) goto pow2_fault;
|
||||
if( N==0 ) return pA;
|
||||
if( N>0 ){
|
||||
pX = decimalNewFromText("2.0", 3);
|
||||
}else{
|
||||
N = -N;
|
||||
pX = decimalNewFromText("0.5", 3);
|
||||
}
|
||||
if( pX==0 || pX->oom ) goto pow2_fault;
|
||||
while( 1 /* Exit by break */ ){
|
||||
if( N & 1 ){
|
||||
decimalMul(pA, pX);
|
||||
if( pA->oom ) goto pow2_fault;
|
||||
}
|
||||
N >>= 1;
|
||||
if( N==0 ) break;
|
||||
decimalMul(pX, pX);
|
||||
}
|
||||
decimal_free(pX);
|
||||
return pA;
|
||||
|
||||
pow2_fault:
|
||||
decimal_free(pA);
|
||||
decimal_free(pX);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Use an IEEE754 binary64 ("double") to generate a new Decimal object.
|
||||
*/
|
||||
static Decimal *decimalFromDouble(double r){
|
||||
sqlite3_int64 m, a;
|
||||
int e;
|
||||
int isNeg;
|
||||
Decimal *pA;
|
||||
Decimal *pX;
|
||||
char zNum[100];
|
||||
if( r<0.0 ){
|
||||
isNeg = 1;
|
||||
r = -r;
|
||||
}else{
|
||||
isNeg = 0;
|
||||
}
|
||||
memcpy(&a,&r,sizeof(a));
|
||||
if( a==0 ){
|
||||
e = 0;
|
||||
m = 0;
|
||||
}else{
|
||||
e = a>>52;
|
||||
m = a & ((((sqlite3_int64)1)<<52)-1);
|
||||
if( e==0 ){
|
||||
m <<= 1;
|
||||
}else{
|
||||
m |= ((sqlite3_int64)1)<<52;
|
||||
}
|
||||
while( e<1075 && m>0 && (m&1)==0 ){
|
||||
m >>= 1;
|
||||
e++;
|
||||
}
|
||||
if( isNeg ) m = -m;
|
||||
e = e - 1075;
|
||||
if( e>971 ){
|
||||
return 0; /* A NaN or an Infinity */
|
||||
}
|
||||
}
|
||||
|
||||
/* At this point m is the integer significand and e is the exponent */
|
||||
sqlite3_snprintf(sizeof(zNum), zNum, "%lld", m);
|
||||
pA = decimalNewFromText(zNum, (int)strlen(zNum));
|
||||
pX = decimalPow2(e);
|
||||
decimalMul(pA, pX);
|
||||
decimal_free(pX);
|
||||
return pA;
|
||||
}
|
||||
|
||||
/*
|
||||
** SQL Function: decimal(X)
|
||||
** OR: decimal_exp(X)
|
||||
**
|
||||
** Convert input X into decimal and then back into text.
|
||||
**
|
||||
** If X is originally a float, then a full decimal expansion of that floating
|
||||
** point value is done. Or if X is an 8-byte blob, it is interpreted
|
||||
** as a float and similarly expanded.
|
||||
**
|
||||
** The decimal_exp(X) function returns the result in exponential notation.
|
||||
** decimal(X) returns a complete decimal, without the e+NNN at the end.
|
||||
*/
|
||||
static void decimalFunc(
|
||||
sqlite3_context *context,
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
){
|
||||
Decimal *p = decimal_new(context, argv[0], 0);
|
||||
UNUSED_PARAMETER(argc);
|
||||
if( p ){
|
||||
if( sqlite3_user_data(context)!=0 ){
|
||||
decimal_result_sci(context, p);
|
||||
}else{
|
||||
decimal_result(context, p);
|
||||
}
|
||||
decimal_free(p);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Compare text in decimal order.
|
||||
*/
|
||||
@@ -417,8 +680,8 @@ static int decimalCollFunc(
|
||||
){
|
||||
const unsigned char *zA = (const unsigned char*)pKey1;
|
||||
const unsigned char *zB = (const unsigned char*)pKey2;
|
||||
Decimal *pA = decimal_new(0, 0, nKey1, zA);
|
||||
Decimal *pB = decimal_new(0, 0, nKey2, zB);
|
||||
Decimal *pA = decimalNewFromText((const char*)zA, nKey1);
|
||||
Decimal *pB = decimalNewFromText((const char*)zB, nKey2);
|
||||
int rc;
|
||||
UNUSED_PARAMETER(notUsed);
|
||||
if( pA==0 || pB==0 ){
|
||||
@@ -443,8 +706,8 @@ static void decimalAddFunc(
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
){
|
||||
Decimal *pA = decimal_new(context, argv[0], 0, 0);
|
||||
Decimal *pB = decimal_new(context, argv[1], 0, 0);
|
||||
Decimal *pA = decimal_new(context, argv[0], 1);
|
||||
Decimal *pB = decimal_new(context, argv[1], 1);
|
||||
UNUSED_PARAMETER(argc);
|
||||
decimal_add(pA, pB);
|
||||
decimal_result(context, pA);
|
||||
@@ -456,8 +719,8 @@ static void decimalSubFunc(
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
){
|
||||
Decimal *pA = decimal_new(context, argv[0], 0, 0);
|
||||
Decimal *pB = decimal_new(context, argv[1], 0, 0);
|
||||
Decimal *pA = decimal_new(context, argv[0], 1);
|
||||
Decimal *pB = decimal_new(context, argv[1], 1);
|
||||
UNUSED_PARAMETER(argc);
|
||||
if( pB ){
|
||||
pB->sign = !pB->sign;
|
||||
@@ -495,7 +758,7 @@ static void decimalSumStep(
|
||||
p->nFrac = 0;
|
||||
}
|
||||
if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
|
||||
pArg = decimal_new(context, argv[0], 0, 0);
|
||||
pArg = decimal_new(context, argv[0], 1);
|
||||
decimal_add(p, pArg);
|
||||
decimal_free(pArg);
|
||||
}
|
||||
@@ -510,7 +773,7 @@ static void decimalSumInverse(
|
||||
p = sqlite3_aggregate_context(context, sizeof(*p));
|
||||
if( p==0 ) return;
|
||||
if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
|
||||
pArg = decimal_new(context, argv[0], 0, 0);
|
||||
pArg = decimal_new(context, argv[0], 1);
|
||||
if( pArg ) pArg->sign = !pArg->sign;
|
||||
decimal_add(p, pArg);
|
||||
decimal_free(pArg);
|
||||
@@ -531,66 +794,49 @@ static void decimalSumFinalize(sqlite3_context *context){
|
||||
** SQL Function: decimal_mul(X, Y)
|
||||
**
|
||||
** Return the product of X and Y.
|
||||
**
|
||||
** All significant digits after the decimal point are retained.
|
||||
** Trailing zeros after the decimal point are omitted as long as
|
||||
** the number of digits after the decimal point is no less than
|
||||
** either the number of digits in either input.
|
||||
*/
|
||||
static void decimalMulFunc(
|
||||
sqlite3_context *context,
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
){
|
||||
Decimal *pA = decimal_new(context, argv[0], 0, 0);
|
||||
Decimal *pB = decimal_new(context, argv[1], 0, 0);
|
||||
signed char *acc = 0;
|
||||
int i, j, k;
|
||||
int minFrac;
|
||||
Decimal *pA = decimal_new(context, argv[0], 1);
|
||||
Decimal *pB = decimal_new(context, argv[1], 1);
|
||||
UNUSED_PARAMETER(argc);
|
||||
if( pA==0 || pA->oom || pA->isNull
|
||||
|| pB==0 || pB->oom || pB->isNull
|
||||
){
|
||||
goto mul_end;
|
||||
}
|
||||
acc = sqlite3_malloc64( pA->nDigit + pB->nDigit + 2 );
|
||||
if( acc==0 ){
|
||||
sqlite3_result_error_nomem(context);
|
||||
decimalMul(pA, pB);
|
||||
if( pA->oom ){
|
||||
goto mul_end;
|
||||
}
|
||||
memset(acc, 0, pA->nDigit + pB->nDigit + 2);
|
||||
minFrac = pA->nFrac;
|
||||
if( pB->nFrac<minFrac ) minFrac = pB->nFrac;
|
||||
for(i=pA->nDigit-1; i>=0; i--){
|
||||
signed char f = pA->a[i];
|
||||
int carry = 0, x;
|
||||
for(j=pB->nDigit-1, k=i+j+3; j>=0; j--, k--){
|
||||
x = acc[k] + f*pB->a[j] + carry;
|
||||
acc[k] = x%10;
|
||||
carry = x/10;
|
||||
}
|
||||
x = acc[k] + carry;
|
||||
acc[k] = x%10;
|
||||
acc[k-1] += x/10;
|
||||
}
|
||||
sqlite3_free(pA->a);
|
||||
pA->a = acc;
|
||||
acc = 0;
|
||||
pA->nDigit += pB->nDigit + 2;
|
||||
pA->nFrac += pB->nFrac;
|
||||
pA->sign ^= pB->sign;
|
||||
while( pA->nFrac>minFrac && pA->a[pA->nDigit-1]==0 ){
|
||||
pA->nFrac--;
|
||||
pA->nDigit--;
|
||||
}
|
||||
decimal_result(context, pA);
|
||||
|
||||
mul_end:
|
||||
sqlite3_free(acc);
|
||||
decimal_free(pA);
|
||||
decimal_free(pB);
|
||||
}
|
||||
|
||||
/*
|
||||
** SQL Function: decimal_pow2(N)
|
||||
**
|
||||
** Return the N-th power of 2. N must be an integer.
|
||||
*/
|
||||
static void decimalPow2Func(
|
||||
sqlite3_context *context,
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
){
|
||||
UNUSED_PARAMETER(argc);
|
||||
if( sqlite3_value_type(argv[0])==SQLITE_INTEGER ){
|
||||
Decimal *pA = decimalPow2(sqlite3_value_int(argv[0]));
|
||||
decimal_result_sci(context, pA);
|
||||
decimal_free(pA);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
@@ -603,13 +849,16 @@ int sqlite3_decimal_init(
|
||||
static const struct {
|
||||
const char *zFuncName;
|
||||
int nArg;
|
||||
int iArg;
|
||||
void (*xFunc)(sqlite3_context*,int,sqlite3_value**);
|
||||
} aFunc[] = {
|
||||
{ "decimal", 1, decimalFunc },
|
||||
{ "decimal_cmp", 2, decimalCmpFunc },
|
||||
{ "decimal_add", 2, decimalAddFunc },
|
||||
{ "decimal_sub", 2, decimalSubFunc },
|
||||
{ "decimal_mul", 2, decimalMulFunc },
|
||||
{ "decimal", 1, 0, decimalFunc },
|
||||
{ "decimal_exp", 1, 1, decimalFunc },
|
||||
{ "decimal_cmp", 2, 0, decimalCmpFunc },
|
||||
{ "decimal_add", 2, 0, decimalAddFunc },
|
||||
{ "decimal_sub", 2, 0, decimalSubFunc },
|
||||
{ "decimal_mul", 2, 0, decimalMulFunc },
|
||||
{ "decimal_pow2", 1, 0, decimalPow2Func },
|
||||
};
|
||||
unsigned int i;
|
||||
(void)pzErrMsg; /* Unused parameter */
|
||||
@@ -619,7 +868,7 @@ int sqlite3_decimal_init(
|
||||
for(i=0; i<(int)(sizeof(aFunc)/sizeof(aFunc[0])) && rc==SQLITE_OK; i++){
|
||||
rc = sqlite3_create_function(db, aFunc[i].zFuncName, aFunc[i].nArg,
|
||||
SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC,
|
||||
0, aFunc[i].xFunc, 0, 0);
|
||||
aFunc[i].iArg ? db : 0, aFunc[i].xFunc, 0, 0);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_create_window_function(db, "decimal_sum", 1,
|
||||
|
||||
+32
-1
@@ -256,6 +256,37 @@ static void ieee754func_to_blob(
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** SQL Function: ieee754_inc(r,N)
|
||||
**
|
||||
** Move the floating point value r by N quantums and return the new
|
||||
** values.
|
||||
**
|
||||
** Behind the scenes: this routine merely casts r into a 64-bit unsigned
|
||||
** integer, adds N, then casts the value back into float.
|
||||
**
|
||||
** Example: To find the smallest positive number:
|
||||
**
|
||||
** SELECT ieee754_inc(0.0,+1);
|
||||
*/
|
||||
static void ieee754inc(
|
||||
sqlite3_context *context,
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
){
|
||||
double r;
|
||||
sqlite3_int64 N;
|
||||
sqlite3_uint64 m1, m2;
|
||||
double r2;
|
||||
UNUSED_PARAMETER(argc);
|
||||
r = sqlite3_value_double(argv[0]);
|
||||
N = sqlite3_value_int64(argv[1]);
|
||||
memcpy(&m1, &r, 8);
|
||||
m2 = m1 + N;
|
||||
memcpy(&r2, &m2, 8);
|
||||
sqlite3_result_double(context, r2);
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
@@ -277,7 +308,7 @@ int sqlite3_ieee_init(
|
||||
{ "ieee754_exponent", 1, 2, ieee754func },
|
||||
{ "ieee754_to_blob", 1, 0, ieee754func_to_blob },
|
||||
{ "ieee754_from_blob", 1, 0, ieee754func_from_blob },
|
||||
|
||||
{ "ieee754_inc", 2, 0, ieee754inc },
|
||||
};
|
||||
unsigned int i;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
** 2023-06-21
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
**
|
||||
** This file implements an extension that uses the SQLITE_CONFIG_PCACHE2
|
||||
** mechanism to add a tracing layer on top of pluggable page cache of
|
||||
** SQLite. If this extension is registered prior to sqlite3_initialize(),
|
||||
** it will cause all page cache activities to be logged on standard output,
|
||||
** or to some other FILE specified by the initializer.
|
||||
**
|
||||
** This file needs to be compiled into the application that uses it.
|
||||
**
|
||||
** This extension is used to implement the --pcachetrace option of the
|
||||
** command-line shell.
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* The original page cache routines */
|
||||
static sqlite3_pcache_methods2 pcacheBase;
|
||||
static FILE *pcachetraceOut;
|
||||
|
||||
/* Methods that trace pcache activity */
|
||||
static int pcachetraceInit(void *pArg){
|
||||
int nRes;
|
||||
if( pcachetraceOut ){
|
||||
fprintf(pcachetraceOut, "PCACHETRACE: xInit(%p)\n", pArg);
|
||||
}
|
||||
nRes = pcacheBase.xInit(pArg);
|
||||
if( pcachetraceOut ){
|
||||
fprintf(pcachetraceOut, "PCACHETRACE: xInit(%p) -> %d\n", pArg, nRes);
|
||||
}
|
||||
return nRes;
|
||||
}
|
||||
static void pcachetraceShutdown(void *pArg){
|
||||
if( pcachetraceOut ){
|
||||
fprintf(pcachetraceOut, "PCACHETRACE: xShutdown(%p)\n", pArg);
|
||||
}
|
||||
pcacheBase.xShutdown(pArg);
|
||||
}
|
||||
static sqlite3_pcache *pcachetraceCreate(int szPage, int szExtra, int bPurge){
|
||||
sqlite3_pcache *pRes;
|
||||
if( pcachetraceOut ){
|
||||
fprintf(pcachetraceOut, "PCACHETRACE: xCreate(%d,%d,%d)\n",
|
||||
szPage, szExtra, bPurge);
|
||||
}
|
||||
pRes = pcacheBase.xCreate(szPage, szExtra, bPurge);
|
||||
if( pcachetraceOut ){
|
||||
fprintf(pcachetraceOut, "PCACHETRACE: xCreate(%d,%d,%d) -> %p\n",
|
||||
szPage, szExtra, bPurge, pRes);
|
||||
}
|
||||
return pRes;
|
||||
}
|
||||
static void pcachetraceCachesize(sqlite3_pcache *p, int nCachesize){
|
||||
if( pcachetraceOut ){
|
||||
fprintf(pcachetraceOut, "PCACHETRACE: xCachesize(%p, %d)\n", p, nCachesize);
|
||||
}
|
||||
pcacheBase.xCachesize(p, nCachesize);
|
||||
}
|
||||
static int pcachetracePagecount(sqlite3_pcache *p){
|
||||
int nRes;
|
||||
if( pcachetraceOut ){
|
||||
fprintf(pcachetraceOut, "PCACHETRACE: xPagecount(%p)\n", p);
|
||||
}
|
||||
nRes = pcacheBase.xPagecount(p);
|
||||
if( pcachetraceOut ){
|
||||
fprintf(pcachetraceOut, "PCACHETRACE: xPagecount(%p) -> %d\n", p, nRes);
|
||||
}
|
||||
return nRes;
|
||||
}
|
||||
static sqlite3_pcache_page *pcachetraceFetch(
|
||||
sqlite3_pcache *p,
|
||||
unsigned key,
|
||||
int crFg
|
||||
){
|
||||
sqlite3_pcache_page *pRes;
|
||||
if( pcachetraceOut ){
|
||||
fprintf(pcachetraceOut, "PCACHETRACE: xFetch(%p,%u,%d)\n", p, key, crFg);
|
||||
}
|
||||
pRes = pcacheBase.xFetch(p, key, crFg);
|
||||
if( pcachetraceOut ){
|
||||
fprintf(pcachetraceOut, "PCACHETRACE: xFetch(%p,%u,%d) -> %p\n",
|
||||
p, key, crFg, pRes);
|
||||
}
|
||||
return pRes;
|
||||
}
|
||||
static void pcachetraceUnpin(
|
||||
sqlite3_pcache *p,
|
||||
sqlite3_pcache_page *pPg,
|
||||
int bDiscard
|
||||
){
|
||||
if( pcachetraceOut ){
|
||||
fprintf(pcachetraceOut, "PCACHETRACE: xUnpin(%p, %p, %d)\n",
|
||||
p, pPg, bDiscard);
|
||||
}
|
||||
pcacheBase.xUnpin(p, pPg, bDiscard);
|
||||
}
|
||||
static void pcachetraceRekey(
|
||||
sqlite3_pcache *p,
|
||||
sqlite3_pcache_page *pPg,
|
||||
unsigned oldKey,
|
||||
unsigned newKey
|
||||
){
|
||||
if( pcachetraceOut ){
|
||||
fprintf(pcachetraceOut, "PCACHETRACE: xRekey(%p, %p, %u, %u)\n",
|
||||
p, pPg, oldKey, newKey);
|
||||
}
|
||||
pcacheBase.xRekey(p, pPg, oldKey, newKey);
|
||||
}
|
||||
static void pcachetraceTruncate(sqlite3_pcache *p, unsigned n){
|
||||
if( pcachetraceOut ){
|
||||
fprintf(pcachetraceOut, "PCACHETRACE: xTruncate(%p, %u)\n", p, n);
|
||||
}
|
||||
pcacheBase.xTruncate(p, n);
|
||||
}
|
||||
static void pcachetraceDestroy(sqlite3_pcache *p){
|
||||
if( pcachetraceOut ){
|
||||
fprintf(pcachetraceOut, "PCACHETRACE: xDestroy(%p)\n", p);
|
||||
}
|
||||
pcacheBase.xDestroy(p);
|
||||
}
|
||||
static void pcachetraceShrink(sqlite3_pcache *p){
|
||||
if( pcachetraceOut ){
|
||||
fprintf(pcachetraceOut, "PCACHETRACE: xShrink(%p)\n", p);
|
||||
}
|
||||
pcacheBase.xShrink(p);
|
||||
}
|
||||
|
||||
/* The substitute pcache methods */
|
||||
static sqlite3_pcache_methods2 ersaztPcacheMethods = {
|
||||
0,
|
||||
0,
|
||||
pcachetraceInit,
|
||||
pcachetraceShutdown,
|
||||
pcachetraceCreate,
|
||||
pcachetraceCachesize,
|
||||
pcachetracePagecount,
|
||||
pcachetraceFetch,
|
||||
pcachetraceUnpin,
|
||||
pcachetraceRekey,
|
||||
pcachetraceTruncate,
|
||||
pcachetraceDestroy,
|
||||
pcachetraceShrink
|
||||
};
|
||||
|
||||
/* Begin tracing memory allocations to out. */
|
||||
int sqlite3PcacheTraceActivate(FILE *out){
|
||||
int rc = SQLITE_OK;
|
||||
if( pcacheBase.xFetch==0 ){
|
||||
rc = sqlite3_config(SQLITE_CONFIG_GETPCACHE2, &pcacheBase);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_config(SQLITE_CONFIG_PCACHE2, &ersaztPcacheMethods);
|
||||
}
|
||||
}
|
||||
pcachetraceOut = out;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Deactivate memory tracing */
|
||||
int sqlite3PcacheTraceDeactivate(void){
|
||||
int rc = SQLITE_OK;
|
||||
if( pcacheBase.xFetch!=0 ){
|
||||
rc = sqlite3_config(SQLITE_CONFIG_PCACHE2, &pcacheBase);
|
||||
if( rc==SQLITE_OK ){
|
||||
memset(&pcacheBase, 0, sizeof(pcacheBase));
|
||||
}
|
||||
}
|
||||
pcachetraceOut = 0;
|
||||
return rc;
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
** 2023-04-28
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
******************************************************************************
|
||||
**
|
||||
** This SQLite extension implements a the random_json(SEED) and
|
||||
** random_json5(SEED) functions. Given a numeric SEED value, these
|
||||
** routines generate pseudo-random JSON or JSON5, respectively. The
|
||||
** same value is always generated for the same seed.
|
||||
**
|
||||
** These SQL functions are intended for testing. They do not have any
|
||||
** practical real-world use, that we know of.
|
||||
**
|
||||
** COMPILE:
|
||||
**
|
||||
** gcc --shared -fPIC -o randomjson.so -I. ext/misc/randomjson.c
|
||||
**
|
||||
** USING FROM THE CLI:
|
||||
**
|
||||
** .load ./randomjson
|
||||
** SELECT random_json(1);
|
||||
*/
|
||||
#include "sqlite3ext.h"
|
||||
SQLITE_EXTENSION_INIT1
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* Pseudo-random number generator */
|
||||
typedef struct Prng {
|
||||
unsigned int x, y;
|
||||
} Prng;
|
||||
|
||||
/* Reseed the PRNG */
|
||||
static void prngSeed(Prng *p, unsigned int iSeed){
|
||||
p->x = iSeed | 1;
|
||||
p->y = iSeed;
|
||||
}
|
||||
|
||||
/* Extract a random number */
|
||||
static unsigned int prngInt(Prng *p){
|
||||
p->x = (p->x>>1) ^ ((1+~(p->x&1)) & 0xd0000001);
|
||||
p->y = p->y*1103515245 + 12345;
|
||||
return p->x ^ p->y;
|
||||
}
|
||||
|
||||
static const char *azJsonAtoms[] = {
|
||||
/* JSON /* JSON-5 */
|
||||
"0", "0",
|
||||
"1", "1",
|
||||
"-1", "-1",
|
||||
"2", "+2",
|
||||
"3", "3",
|
||||
"2.5", "2.5",
|
||||
"0.75", ".75",
|
||||
"-4.0e2", "-4.e2",
|
||||
"5.0e-3", "+5e-3",
|
||||
"0", "0x0",
|
||||
"512", "0x200",
|
||||
"256", "+0x100",
|
||||
"-2748", "-0xabc",
|
||||
"true", "true",
|
||||
"false", "false",
|
||||
"null", "null",
|
||||
"9.0e999", "Infinity",
|
||||
"-9.0e999", "-Infinity",
|
||||
"9.0e999", "+Infinity",
|
||||
"null", "NaN",
|
||||
"-0.0005123", "-0.0005123",
|
||||
"4.35e-3", "+4.35e-3",
|
||||
"\"gem\\\"hay\"", "\"gem\\\"hay\"",
|
||||
"\"icy'joy\"", "'icy\\'joy\'",
|
||||
"\"keylog\"", "\"key\\\nlog\"",
|
||||
"\"mix\\\\\\tnet\"", "\"mix\\\\\\tnet\"",
|
||||
"{}", "{}",
|
||||
"[]", "[]",
|
||||
"[]", "[/*empty*/]",
|
||||
"{}", "{//empty\n}",
|
||||
"\"ask\"", "\"ask\"",
|
||||
"\"bag\"", "\"bag\"",
|
||||
"\"can\"", "\"can\"",
|
||||
"\"day\"", "\"day\"",
|
||||
"\"end\"", "'end'",
|
||||
"\"fly\"", "\"fly\"",
|
||||
"\"\"", "\"\"",
|
||||
};
|
||||
static const char *azJsonTemplate[] = {
|
||||
/* JSON JSON-5 */
|
||||
"{\"a\":%,\"b\":%,\"c\":%}", "{a:%,b:%,c:%}",
|
||||
"{\"a\":%,\"b\":%,\"c\":%,\"d\":%,\"e\":%}", "{a:%,b:%,c:%,d:%,e:%}",
|
||||
"{\"a\":%,\"b\":%,\"c\":%,\"d\":%,\"\":%}", "{a:%,b:%,c:%,d:%,\"\":%}",
|
||||
"{\"d\":%}", "{d:%}",
|
||||
"{\"eeee\":%, \"ffff\":%}", "{eeee:% /*and*/, ffff:%}",
|
||||
"{\"$g\":%,\"_h_\":%}", "{$g:%,_h_:%,}",
|
||||
"{\"x\":%,\n \"y\":%}", "{\"x\":%,\n \"y\":%}",
|
||||
"{\"a b c d\":%,\"e\":%,\"f\":%,\"x\":%,\"y\":%}",
|
||||
"{\"a b c d\":%,e:%,f:%,x:%,y:%}",
|
||||
"{\"Z\":%}", "{Z:%,}",
|
||||
"[%]", "[%,]",
|
||||
"[%,%]", "[%,%]",
|
||||
"[%,%,%]", "[%,%,%,]",
|
||||
"[%,%,%,%]", "[%,%,%,%]",
|
||||
"[%,%,%,%,%]", "[%,%,%,%,%]",
|
||||
};
|
||||
|
||||
#define count(X) (sizeof(X)/sizeof(X[0]))
|
||||
|
||||
#define STRSZ 10000
|
||||
|
||||
static void jsonExpand(
|
||||
const char *zSrc,
|
||||
char *zDest,
|
||||
Prng *p,
|
||||
int eType, /* 0 for JSON, 1 for JSON5 */
|
||||
unsigned int r /* Growth probability 0..1000. 0 means no growth */
|
||||
){
|
||||
unsigned int i, j, k;
|
||||
const char *z;
|
||||
size_t n;
|
||||
|
||||
j = 0;
|
||||
if( zSrc==0 ){
|
||||
k = prngInt(p)%(count(azJsonTemplate)/2);
|
||||
k = k*2 + eType;
|
||||
zSrc = azJsonTemplate[k];
|
||||
}
|
||||
if( strlen(zSrc)>=STRSZ/10 ) r = 0;
|
||||
for(i=0; zSrc[i]; i++){
|
||||
if( zSrc[i]!='%' ){
|
||||
if( j<STRSZ ) zDest[j++] = zSrc[i];
|
||||
continue;
|
||||
}
|
||||
if( r==0 || (r<1000 && (prngInt(p)%1000)<=r) ){
|
||||
/* Fill in without values without any new % */
|
||||
k = prngInt(p)%(count(azJsonAtoms)/2);
|
||||
k = k*2 + eType;
|
||||
z = azJsonAtoms[k];
|
||||
}else{
|
||||
/* Add new % terms */
|
||||
k = prngInt(p)%(count(azJsonTemplate)/2);
|
||||
k = k*2 + eType;
|
||||
z = azJsonTemplate[k];
|
||||
}
|
||||
n = strlen(z);
|
||||
if( j+n<STRSZ ){
|
||||
memcpy(&zDest[j], z, n);
|
||||
j += n;
|
||||
}
|
||||
}
|
||||
zDest[STRSZ-1] = 0;
|
||||
if( j<STRSZ ) zDest[j] = 0;
|
||||
}
|
||||
|
||||
static void randJsonFunc(
|
||||
sqlite3_context *context,
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
){
|
||||
unsigned int iSeed;
|
||||
int eType = *(int*)sqlite3_user_data(context);
|
||||
Prng prng;
|
||||
char z1[STRSZ+1], z2[STRSZ+1];
|
||||
|
||||
iSeed = (unsigned int)sqlite3_value_int(argv[0]);
|
||||
prngSeed(&prng, iSeed);
|
||||
jsonExpand(0, z2, &prng, eType, 1000);
|
||||
jsonExpand(z2, z1, &prng, eType, 1000);
|
||||
jsonExpand(z1, z2, &prng, eType, 100);
|
||||
jsonExpand(z2, z1, &prng, eType, 0);
|
||||
sqlite3_result_text(context, z1, -1, SQLITE_TRANSIENT);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
int sqlite3_randomjson_init(
|
||||
sqlite3 *db,
|
||||
char **pzErrMsg,
|
||||
const sqlite3_api_routines *pApi
|
||||
){
|
||||
static int cOne = 1;
|
||||
static int cZero = 0;
|
||||
int rc = SQLITE_OK;
|
||||
SQLITE_EXTENSION_INIT2(pApi);
|
||||
(void)pzErrMsg; /* Unused parameter */
|
||||
rc = sqlite3_create_function(db, "random_json", 1,
|
||||
SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC,
|
||||
&cZero, randJsonFunc, 0, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_create_function(db, "random_json5", 1,
|
||||
SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC,
|
||||
&cOne, randJsonFunc, 0, 0);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
+2
-2
@@ -595,7 +595,7 @@ static const char *re_subcompile_string(ReCompiled *p){
|
||||
break;
|
||||
}
|
||||
case '[': {
|
||||
int iFirst = p->nState;
|
||||
unsigned int iFirst = p->nState;
|
||||
if( rePeek(p)=='^' ){
|
||||
re_append(p, RE_OP_CC_EXC, 0);
|
||||
p->sIn.i++;
|
||||
@@ -619,7 +619,7 @@ static const char *re_subcompile_string(ReCompiled *p){
|
||||
if( rePeek(p)==']' ){ p->sIn.i++; break; }
|
||||
}
|
||||
if( c==0 ) return "unclosed '['";
|
||||
p->aArg[iFirst] = p->nState - iFirst;
|
||||
if( p->nState>iFirst ) p->aArg[iFirst] = p->nState - iFirst;
|
||||
break;
|
||||
}
|
||||
case '\\': {
|
||||
|
||||
+175
-50
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
** 2015-08-18
|
||||
** 2015-08-18, 2023-04-28
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
@@ -12,7 +12,19 @@
|
||||
**
|
||||
** This file demonstrates how to create a table-valued-function using
|
||||
** a virtual table. This demo implements the generate_series() function
|
||||
** which gives similar results to the eponymous function in PostgreSQL.
|
||||
** which gives the same results as the eponymous function in PostgreSQL,
|
||||
** within the limitation that its arguments are signed 64-bit integers.
|
||||
**
|
||||
** Considering its equivalents to generate_series(start,stop,step): A
|
||||
** value V[n] sequence is produced for integer n ascending from 0 where
|
||||
** ( V[n] == start + n * step && sgn(V[n] - stop) * sgn(step) >= 0 )
|
||||
** for each produced value (independent of production time ordering.)
|
||||
**
|
||||
** All parameters must be either integer or convertable to integer.
|
||||
** The start parameter is required.
|
||||
** The stop parameter defaults to (1<<32)-1 (aka 4294967295 or 0xffffffff)
|
||||
** The step parameter defaults to 1 and 0 is treated as 1.
|
||||
**
|
||||
** Examples:
|
||||
**
|
||||
** SELECT * FROM generate_series(0,100,5);
|
||||
@@ -28,6 +40,14 @@
|
||||
**
|
||||
** Integers 20 through 29.
|
||||
**
|
||||
** SELECT * FROM generate_series(0,-100,-5);
|
||||
**
|
||||
** Integers 0 -5 -10 ... -100.
|
||||
**
|
||||
** SELECT * FROM generate_series(0,-1);
|
||||
**
|
||||
** Empty sequence.
|
||||
**
|
||||
** HOW IT WORKS
|
||||
**
|
||||
** The generate_series "function" is really a virtual table with the
|
||||
@@ -40,6 +60,9 @@
|
||||
** step HIDDEN
|
||||
** );
|
||||
**
|
||||
** The virtual table also has a rowid, logically equivalent to n+1 where
|
||||
** "n" is the ascending integer in the aforesaid production definition.
|
||||
**
|
||||
** Function arguments in queries against this virtual table are translated
|
||||
** into equality constraints against successive hidden columns. In other
|
||||
** words, the following pairs of queries are equivalent to each other:
|
||||
@@ -72,9 +95,126 @@
|
||||
SQLITE_EXTENSION_INIT1
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
/*
|
||||
** Return that member of a generate_series(...) sequence whose 0-based
|
||||
** index is ix. The 0th member is given by smBase. The sequence members
|
||||
** progress per ix increment by smStep.
|
||||
*/
|
||||
static sqlite3_int64 genSeqMember(sqlite3_int64 smBase,
|
||||
sqlite3_int64 smStep,
|
||||
sqlite3_uint64 ix){
|
||||
if( ix>=(sqlite3_uint64)LLONG_MAX ){
|
||||
/* Get ix into signed i64 range. */
|
||||
ix -= (sqlite3_uint64)LLONG_MAX;
|
||||
/* With 2's complement ALU, this next can be 1 step, but is split into
|
||||
* 2 for UBSAN's satisfaction (and hypothetical 1's complement ALUs.) */
|
||||
smBase += (LLONG_MAX/2) * smStep;
|
||||
smBase += (LLONG_MAX - LLONG_MAX/2) * smStep;
|
||||
}
|
||||
/* Under UBSAN (or on 1's complement machines), must do this last term
|
||||
* in steps to avoid the dreaded (and harmless) signed multiply overlow. */
|
||||
if( ix>=2 ){
|
||||
sqlite3_int64 ix2 = (sqlite3_int64)ix/2;
|
||||
smBase += ix2*smStep;
|
||||
ix -= ix2;
|
||||
}
|
||||
return smBase + ((sqlite3_int64)ix)*smStep;
|
||||
}
|
||||
|
||||
typedef unsigned char u8;
|
||||
|
||||
typedef struct SequenceSpec {
|
||||
sqlite3_int64 iBase; /* Starting value ("start") */
|
||||
sqlite3_int64 iTerm; /* Given terminal value ("stop") */
|
||||
sqlite3_int64 iStep; /* Increment ("step") */
|
||||
sqlite3_uint64 uSeqIndexMax; /* maximum sequence index (aka "n") */
|
||||
sqlite3_uint64 uSeqIndexNow; /* Current index during generation */
|
||||
sqlite3_int64 iValueNow; /* Current value during generation */
|
||||
u8 isNotEOF; /* Sequence generation not exhausted */
|
||||
u8 isReversing; /* Sequence is being reverse generated */
|
||||
} SequenceSpec;
|
||||
|
||||
/*
|
||||
** Prepare a SequenceSpec for use in generating an integer series
|
||||
** given initialized iBase, iTerm and iStep values. Sequence is
|
||||
** initialized per given isReversing. Other members are computed.
|
||||
*/
|
||||
static void setupSequence( SequenceSpec *pss ){
|
||||
int bSameSigns;
|
||||
pss->uSeqIndexMax = 0;
|
||||
pss->isNotEOF = 0;
|
||||
bSameSigns = (pss->iBase < 0)==(pss->iTerm < 0);
|
||||
if( pss->iTerm < pss->iBase ){
|
||||
sqlite3_uint64 nuspan = 0;
|
||||
if( bSameSigns ){
|
||||
nuspan = (sqlite3_uint64)(pss->iBase - pss->iTerm);
|
||||
}else{
|
||||
/* Under UBSAN (or on 1's complement machines), must do this in steps.
|
||||
* In this clause, iBase>=0 and iTerm<0 . */
|
||||
nuspan = 1;
|
||||
nuspan += pss->iBase;
|
||||
nuspan += -(pss->iTerm+1);
|
||||
}
|
||||
if( pss->iStep<0 ){
|
||||
pss->isNotEOF = 1;
|
||||
if( nuspan==ULONG_MAX ){
|
||||
pss->uSeqIndexMax = ( pss->iStep>LLONG_MIN )? nuspan/-pss->iStep : 1;
|
||||
}else if( pss->iStep>LLONG_MIN ){
|
||||
pss->uSeqIndexMax = nuspan/-pss->iStep;
|
||||
}
|
||||
}
|
||||
}else if( pss->iTerm > pss->iBase ){
|
||||
sqlite3_uint64 puspan = 0;
|
||||
if( bSameSigns ){
|
||||
puspan = (sqlite3_uint64)(pss->iTerm - pss->iBase);
|
||||
}else{
|
||||
/* Under UBSAN (or on 1's complement machines), must do this in steps.
|
||||
* In this clause, iTerm>=0 and iBase<0 . */
|
||||
puspan = 1;
|
||||
puspan += pss->iTerm;
|
||||
puspan += -(pss->iBase+1);
|
||||
}
|
||||
if( pss->iStep>0 ){
|
||||
pss->isNotEOF = 1;
|
||||
pss->uSeqIndexMax = puspan/pss->iStep;
|
||||
}
|
||||
}else if( pss->iTerm == pss->iBase ){
|
||||
pss->isNotEOF = 1;
|
||||
pss->uSeqIndexMax = 0;
|
||||
}
|
||||
pss->uSeqIndexNow = (pss->isReversing)? pss->uSeqIndexMax : 0;
|
||||
pss->iValueNow = (pss->isReversing)
|
||||
? genSeqMember(pss->iBase, pss->iStep, pss->uSeqIndexMax)
|
||||
: pss->iBase;
|
||||
}
|
||||
|
||||
/*
|
||||
** Progress sequence generator to yield next value, if any.
|
||||
** Leave its state to either yield next value or be at EOF.
|
||||
** Return whether there is a next value, or 0 at EOF.
|
||||
*/
|
||||
static int progressSequence( SequenceSpec *pss ){
|
||||
if( !pss->isNotEOF ) return 0;
|
||||
if( pss->isReversing ){
|
||||
if( pss->uSeqIndexNow > 0 ){
|
||||
pss->uSeqIndexNow--;
|
||||
pss->iValueNow -= pss->iStep;
|
||||
}else{
|
||||
pss->isNotEOF = 0;
|
||||
}
|
||||
}else{
|
||||
if( pss->uSeqIndexNow < pss->uSeqIndexMax ){
|
||||
pss->uSeqIndexNow++;
|
||||
pss->iValueNow += pss->iStep;
|
||||
}else{
|
||||
pss->isNotEOF = 0;
|
||||
}
|
||||
}
|
||||
return pss->isNotEOF;
|
||||
}
|
||||
|
||||
/* series_cursor is a subclass of sqlite3_vtab_cursor which will
|
||||
** serve as the underlying representation of a cursor that scans
|
||||
@@ -83,12 +223,7 @@ SQLITE_EXTENSION_INIT1
|
||||
typedef struct series_cursor series_cursor;
|
||||
struct series_cursor {
|
||||
sqlite3_vtab_cursor base; /* Base class - must be first */
|
||||
int isDesc; /* True to count down rather than up */
|
||||
sqlite3_int64 iRowid; /* The rowid */
|
||||
sqlite3_int64 iValue; /* Current value ("value") */
|
||||
sqlite3_int64 mnValue; /* Mimimum value ("start") */
|
||||
sqlite3_int64 mxValue; /* Maximum value ("stop") */
|
||||
sqlite3_int64 iStep; /* Increment ("step") */
|
||||
SequenceSpec ss; /* (this) Derived class data */
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -170,12 +305,7 @@ static int seriesClose(sqlite3_vtab_cursor *cur){
|
||||
*/
|
||||
static int seriesNext(sqlite3_vtab_cursor *cur){
|
||||
series_cursor *pCur = (series_cursor*)cur;
|
||||
if( pCur->isDesc ){
|
||||
pCur->iValue -= pCur->iStep;
|
||||
}else{
|
||||
pCur->iValue += pCur->iStep;
|
||||
}
|
||||
pCur->iRowid++;
|
||||
progressSequence( & pCur->ss );
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
@@ -191,23 +321,27 @@ static int seriesColumn(
|
||||
series_cursor *pCur = (series_cursor*)cur;
|
||||
sqlite3_int64 x = 0;
|
||||
switch( i ){
|
||||
case SERIES_COLUMN_START: x = pCur->mnValue; break;
|
||||
case SERIES_COLUMN_STOP: x = pCur->mxValue; break;
|
||||
case SERIES_COLUMN_STEP: x = pCur->iStep; break;
|
||||
default: x = pCur->iValue; break;
|
||||
case SERIES_COLUMN_START: x = pCur->ss.iBase; break;
|
||||
case SERIES_COLUMN_STOP: x = pCur->ss.iTerm; break;
|
||||
case SERIES_COLUMN_STEP: x = pCur->ss.iStep; break;
|
||||
default: x = pCur->ss.iValueNow; break;
|
||||
}
|
||||
sqlite3_result_int64(ctx, x);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
#ifndef LARGEST_UINT64
|
||||
#define LARGEST_UINT64 (0xffffffff|(((sqlite3_uint64)0xffffffff)<<32))
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Return the rowid for the current row. In this implementation, the
|
||||
** first row returned is assigned rowid value 1, and each subsequent
|
||||
** row a value 1 more than that of the previous.
|
||||
** Return the rowid for the current row, logically equivalent to n+1 where
|
||||
** "n" is the ascending integer in the aforesaid production definition.
|
||||
*/
|
||||
static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
|
||||
series_cursor *pCur = (series_cursor*)cur;
|
||||
*pRowid = pCur->iRowid;
|
||||
sqlite3_uint64 n = pCur->ss.uSeqIndexNow;
|
||||
*pRowid = (sqlite3_int64)((n<LARGEST_UINT64)? n+1 : 0);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
@@ -217,14 +351,10 @@ static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
|
||||
*/
|
||||
static int seriesEof(sqlite3_vtab_cursor *cur){
|
||||
series_cursor *pCur = (series_cursor*)cur;
|
||||
if( pCur->isDesc ){
|
||||
return pCur->iValue < pCur->mnValue;
|
||||
}else{
|
||||
return pCur->iValue > pCur->mxValue;
|
||||
}
|
||||
return !pCur->ss.isNotEOF;
|
||||
}
|
||||
|
||||
/* True to cause run-time checking of the start=, stop=, and/or step=
|
||||
/* True to cause run-time checking of the start=, stop=, and/or step=
|
||||
** parameters. The only reason to do this is for testing the
|
||||
** constraint checking logic for virtual tables in the SQLite core.
|
||||
*/
|
||||
@@ -235,7 +365,7 @@ static int seriesEof(sqlite3_vtab_cursor *cur){
|
||||
/*
|
||||
** This method is called to "rewind" the series_cursor object back
|
||||
** to the first row of output. This method is always called at least
|
||||
** once prior to any call to seriesColumn() or seriesRowid() or
|
||||
** once prior to any call to seriesColumn() or seriesRowid() or
|
||||
** seriesEof().
|
||||
**
|
||||
** The query plan selected by seriesBestIndex is passed in the idxNum
|
||||
@@ -255,7 +385,7 @@ static int seriesEof(sqlite3_vtab_cursor *cur){
|
||||
** (so that seriesEof() will return true) if the table is empty.
|
||||
*/
|
||||
static int seriesFilter(
|
||||
sqlite3_vtab_cursor *pVtabCursor,
|
||||
sqlite3_vtab_cursor *pVtabCursor,
|
||||
int idxNum, const char *idxStrUnused,
|
||||
int argc, sqlite3_value **argv
|
||||
){
|
||||
@@ -263,46 +393,41 @@ static int seriesFilter(
|
||||
int i = 0;
|
||||
(void)idxStrUnused;
|
||||
if( idxNum & 1 ){
|
||||
pCur->mnValue = sqlite3_value_int64(argv[i++]);
|
||||
pCur->ss.iBase = sqlite3_value_int64(argv[i++]);
|
||||
}else{
|
||||
pCur->mnValue = 0;
|
||||
pCur->ss.iBase = 0;
|
||||
}
|
||||
if( idxNum & 2 ){
|
||||
pCur->mxValue = sqlite3_value_int64(argv[i++]);
|
||||
pCur->ss.iTerm = sqlite3_value_int64(argv[i++]);
|
||||
}else{
|
||||
pCur->mxValue = 0xffffffff;
|
||||
pCur->ss.iTerm = 0xffffffff;
|
||||
}
|
||||
if( idxNum & 4 ){
|
||||
pCur->iStep = sqlite3_value_int64(argv[i++]);
|
||||
if( pCur->iStep==0 ){
|
||||
pCur->iStep = 1;
|
||||
}else if( pCur->iStep<0 ){
|
||||
pCur->iStep = -pCur->iStep;
|
||||
pCur->ss.iStep = sqlite3_value_int64(argv[i++]);
|
||||
if( pCur->ss.iStep==0 ){
|
||||
pCur->ss.iStep = 1;
|
||||
}else if( pCur->ss.iStep<0 ){
|
||||
if( (idxNum & 16)==0 ) idxNum |= 8;
|
||||
}
|
||||
}else{
|
||||
pCur->iStep = 1;
|
||||
pCur->ss.iStep = 1;
|
||||
}
|
||||
for(i=0; i<argc; i++){
|
||||
if( sqlite3_value_type(argv[i])==SQLITE_NULL ){
|
||||
/* If any of the constraints have a NULL value, then return no rows.
|
||||
** See ticket https://www.sqlite.org/src/info/fac496b61722daf2 */
|
||||
pCur->mnValue = 1;
|
||||
pCur->mxValue = 0;
|
||||
pCur->ss.iBase = 1;
|
||||
pCur->ss.iTerm = 0;
|
||||
pCur->ss.iStep = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( idxNum & 8 ){
|
||||
pCur->isDesc = 1;
|
||||
pCur->iValue = pCur->mxValue;
|
||||
if( pCur->iStep>0 ){
|
||||
pCur->iValue -= (pCur->mxValue - pCur->mnValue)%pCur->iStep;
|
||||
}
|
||||
pCur->ss.isReversing = pCur->ss.iStep > 0;
|
||||
}else{
|
||||
pCur->isDesc = 0;
|
||||
pCur->iValue = pCur->mnValue;
|
||||
pCur->ss.isReversing = pCur->ss.iStep < 0;
|
||||
}
|
||||
pCur->iRowid = 1;
|
||||
setupSequence( &pCur->ss );
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
|
||||
+13
-3
@@ -1537,9 +1537,19 @@ static u32 zipfileGetTime(sqlite3_value *pVal){
|
||||
*/
|
||||
static void zipfileRemoveEntryFromList(ZipfileTab *pTab, ZipfileEntry *pOld){
|
||||
if( pOld ){
|
||||
ZipfileEntry **pp;
|
||||
for(pp=&pTab->pFirstEntry; (*pp)!=pOld; pp=&((*pp)->pNext));
|
||||
*pp = (*pp)->pNext;
|
||||
if( pTab->pFirstEntry==pOld ){
|
||||
pTab->pFirstEntry = pOld->pNext;
|
||||
if( pTab->pLastEntry==pOld ) pTab->pLastEntry = 0;
|
||||
}else{
|
||||
ZipfileEntry *p;
|
||||
for(p=pTab->pFirstEntry; p; p=p->pNext){
|
||||
if( p->pNext==pOld ){
|
||||
p->pNext = pOld->pNext;
|
||||
if( pTab->pLastEntry==pOld ) pTab->pLastEntry = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
zipfileEntryFree(pOld);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3156,11 +3156,18 @@ static int rbuLockDatabase(sqlite3 *db){
|
||||
sqlite3_file *fd = 0;
|
||||
|
||||
sqlite3_file_control(db, "main", RBU_ZIPVFS_CTRL_FILE_POINTER, &fd);
|
||||
if( fd==0 ){
|
||||
if( fd ){
|
||||
sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, &fd);
|
||||
rc = fd->pMethods->xLock(fd, SQLITE_LOCK_SHARED);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = fd->pMethods->xUnlock(fd, SQLITE_LOCK_NONE);
|
||||
}
|
||||
sqlite3_file_control(db, "main", RBU_ZIPVFS_CTRL_FILE_POINTER, &fd);
|
||||
}else{
|
||||
sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, &fd);
|
||||
}
|
||||
|
||||
if( fd->pMethods ){
|
||||
if( rc==SQLITE_OK && fd->pMethods ){
|
||||
rc = fd->pMethods->xLock(fd, SQLITE_LOCK_SHARED);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = fd->pMethods->xLock(fd, SQLITE_LOCK_EXCLUSIVE);
|
||||
|
||||
@@ -73,13 +73,12 @@
|
||||
*/
|
||||
|
||||
#if !defined(SQLITEINT_H)
|
||||
#include "sqlite3ext.h"
|
||||
#include "sqlite3.h"
|
||||
|
||||
typedef unsigned char u8;
|
||||
typedef unsigned int u32;
|
||||
|
||||
#endif
|
||||
SQLITE_EXTENSION_INIT1
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
@@ -664,8 +663,14 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){
|
||||
if( pCsr->pHdrPtr>&pCsr->pRec[pCsr->nRec] ){
|
||||
bNextPage = 1;
|
||||
}else{
|
||||
int szField = 0;
|
||||
pCsr->pHdrPtr += dbdataGetVarintU32(pCsr->pHdrPtr, &iType);
|
||||
pCsr->pPtr += dbdataValueBytes(iType);
|
||||
szField = dbdataValueBytes(iType);
|
||||
if( (pCsr->nRec - (pCsr->pPtr - pCsr->pRec))<szField ){
|
||||
pCsr->pPtr = &pCsr->pRec[pCsr->nRec];
|
||||
}else{
|
||||
pCsr->pPtr += szField;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -938,15 +943,11 @@ static int sqlite3DbdataRegister(sqlite3 *db){
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
int sqlite3_dbdata_init(
|
||||
sqlite3 *db,
|
||||
char **pzErrMsg,
|
||||
const sqlite3_api_routines *pApi
|
||||
){
|
||||
SQLITE_EXTENSION_INIT2(pApi);
|
||||
(void)pzErrMsg;
|
||||
return sqlite3DbdataRegister(db);
|
||||
}
|
||||
|
||||
@@ -329,5 +329,38 @@ do_test 17.2 {
|
||||
list [catch { $R finish } msg] $msg
|
||||
} {0 {}}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
foreach enc {utf8 utf16 utf16le utf16be} {
|
||||
reset_db
|
||||
do_execsql_test 18.$enc.1 {
|
||||
PRAGMA auto_vacuum = 0;
|
||||
PRAGMA encoding='utf16';
|
||||
CREATE TABLE t1(a, b);
|
||||
CREATE TABLE t2(a, b);
|
||||
INSERT INTO t1 VALUES('abc', 'def');
|
||||
PRAGMA writable_schema = 1;
|
||||
DELETE FROM sqlite_schema WHERE name='t1';
|
||||
}
|
||||
|
||||
proc my_sql_hook {sql} {
|
||||
if {[string match "INSERT INTO lostandfound*" $sql]} {
|
||||
lappend ::script $sql
|
||||
}
|
||||
return 0
|
||||
}
|
||||
do_test 18.$enc.2 {
|
||||
set ::script [list]
|
||||
set R [sqlite3_recover_init_sql db main my_sql_hook]
|
||||
$R config lostandfound lostandfound
|
||||
$R run
|
||||
$R finish
|
||||
set ::script
|
||||
} {{INSERT INTO lostandfound VALUES(2, 2, 2, 1, 'abc', 'def')}}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -285,5 +285,244 @@ do_test 5.1 {
|
||||
list [catch { $R finish } msg] $msg
|
||||
} {0 {}}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
do_test 6.0 {
|
||||
sqlite3 db {}
|
||||
db deserialize [decode_hexdb {
|
||||
| size 8192 pagesize 4096 filename abc.db
|
||||
| page 1 offset 0
|
||||
| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3.
|
||||
| 16: 10 00 01 01 00 40 20 20 00 00 00 02 00 00 00 02 .....@ ........
|
||||
| 32: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 04 ................
|
||||
| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................
|
||||
| 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 ................
|
||||
| 96: 00 2e 6e b8 0d 00 00 00 01 0f dc 00 0f dc 00 00 ..n.............
|
||||
| 4048: 00 00 00 00 00 00 00 00 00 00 00 00 22 01 06 17 ................
|
||||
| 4064: 11 11 01 31 74 61 62 6c 65 74 31 74 31 02 43 52 ...1tablet1t1.CR
|
||||
| 4080: 45 41 54 45 20 54 41 42 4c 45 20 74 31 28 78 29 EATE TABLE t1(x)
|
||||
| page 2 offset 4096
|
||||
| 0: 0d 00 00 00 01 0f e2 00 0f e2 00 00 00 00 00 00 ................
|
||||
| 4064: 00 00 1c 01 02 41 61 62 63 64 65 66 67 68 69 6a .....Aabcdefghij
|
||||
| 4080: 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a klmnopqrstuvwxyz
|
||||
| end abc.db
|
||||
}]} {}
|
||||
do_test 6.1 {
|
||||
set R [sqlite3_recover_init db main test.db2]
|
||||
catch { $R run }
|
||||
list [catch { $R finish } msg] $msg
|
||||
} {0 {}}
|
||||
|
||||
reset_db
|
||||
breakpoint
|
||||
do_test 6.2 {
|
||||
sqlite3 db {}
|
||||
db deserialize [decode_hexdb {
|
||||
| size 8192 pagesize 4096 filename abc.db
|
||||
| page 1 offset 0
|
||||
| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3.
|
||||
| 16: 10 00 01 01 00 40 20 20 00 00 00 02 00 00 00 02 .....@ ........
|
||||
| 32: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 04 ................
|
||||
| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................
|
||||
| 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 ................
|
||||
| 96: 00 2e 6e b8 0d 00 00 00 01 0f dc 00 0f dc 00 00 ..n.............
|
||||
| 4048: 00 00 00 00 00 00 00 00 00 00 00 00 22 01 06 17 ................
|
||||
| 4064: 11 11 01 31 74 61 62 6c 65 74 31 74 31 02 43 52 ...1tablet1t1.CR
|
||||
| 4080: 45 41 54 45 20 54 41 42 4c 45 20 74 31 28 78 29 EATE TABLE t1(x)
|
||||
| page 2 offset 4096
|
||||
| 0: 0d 00 00 00 01 0f e2 00 0f e2 00 00 00 00 00 00 ................
|
||||
| 4064: 00 00 1c 01 02 8F FF FF FF 7E 65 66 67 68 69 6a .....Aabcdefghij
|
||||
| 4080: 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a klmnopqrstuvwxyz
|
||||
| end abc.db
|
||||
}]} {}
|
||||
do_test 6.3 {
|
||||
set R [sqlite3_recover_init db main test.db2]
|
||||
catch { $R run }
|
||||
list [catch { $R finish } msg] $msg
|
||||
} {0 {}}
|
||||
|
||||
reset_db
|
||||
breakpoint
|
||||
do_test 7.0 {
|
||||
sqlite3 db {}
|
||||
db deserialize [decode_hexdb {
|
||||
| size 4108 pagesize 4096 filename x1.db
|
||||
| page 1 offset 0
|
||||
| 0: 02 01 00 00 00 00 14 15 40 00 00 00 00 00 00 00 ........@.......
|
||||
| 16: 33 3a 6d 65 6d 6f 72 79 3a 02 02 02 02 02 02 02 3:memory:.......
|
||||
| 32: 02 02 02 02 02 02 12 02 02 02 63 6f 6c 6f 72 20 ..........color
|
||||
| 48: 73 70 61 63 00 f3 a0 81 a1 00 00 a0 02 02 02 02 spac............
|
||||
| 64: 69 95 73 6f 36 00 ff 0d 00 97 8c 90 3f 0a 70 02 i.so6.......?.p.
|
||||
| 80: 02 02 02 02 02 02 02 02 02 02 02 02 02 01 00 00 ................
|
||||
| 96: 06 02 02 02 02 5f 02 02 02 2c 02 02 02 02 02 02 ....._...,......
|
||||
| 112: 02 02 02 02 02 02 02 02 02 12 02 02 02 63 6f 6c .............col
|
||||
| 128: 6f 72 20 73 70 61 63 00 f3 a0 81 a1 00 00 a0 02 or spac.........
|
||||
| 144: 02 02 02 69 95 73 6f 36 00 ff 0d 00 97 8c 90 3f ...i.so6.......?
|
||||
| 160: 0a 70 02 02 02 02 02 02 02 02 02 02 02 02 02 02 .p..............
|
||||
| 176: 01 00 00 06 02 02 02 02 5f 02 02 02 2c 02 02 00 ........_...,...
|
||||
| 192: 00 01 00 01 00 00 00 01 00 02 fe 00 00 03 00 01 ................
|
||||
| 208: 00 00 00 01 c5 04 00 00 00 01 00 01 00 00 00 01 ................
|
||||
| 224: 00 fa 02 00 00 00 03 00 01 00 00 00 81 00 04 00 ................
|
||||
| 240: 00 00 01 00 01 00 00 00 01 00 02 00 fe 00 03 00 ................
|
||||
| 256: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................
|
||||
| 272: 01 00 02 00 00 00 03 00 01 00 00 00 01 00 04 00 ................
|
||||
| 288: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................
|
||||
| 304: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................
|
||||
| 320: 01 00 02 00 00 00 03 00 01 00 00 00 40 00 84 00 ............@...
|
||||
| 336: 84 00 84 00 01 00 00 00 09 00 06 00 f5 00 01 00 ................
|
||||
| 352: 08 01 03 00 03 00 62 00 62 00 23 00 01 00 62 00 ......b.b.#...b.
|
||||
| 368: 04 00 1e 00 62 00 62 00 62 00 01 00 00 00 0a 00 ....b.b.b.......
|
||||
| 384: 01 00 03 00 01 00 03 00 04 00 02 00 01 00 01 00 ................
|
||||
| 400: 08 00 01 00 31 c6 00 03 00 0c 00 12 00 18 00 02 ....1...........
|
||||
| 416: 00 05 00 08 00 02 00 06 00 08 00 02 00 07 00 08 ................
|
||||
| 432: 00 02 00 01 00 01 00 08 00 01 00 0c 00 03 00 16 ................
|
||||
| 448: 00 1c 00 22 00 01 00 03 00 05 00 06 00 07 00 02 ................
|
||||
| 464: 00 05 00 09 00 02 00 06 00 09 00 02 00 07 00 09 ................
|
||||
| 480: 00 00 00 00 01 00 05 00 00 00 01 00 01 00 00 00 ................
|
||||
| 496: 01 00 02 00 00 00 03 00 01 00 00 00 01 00 04 00 ................
|
||||
| 512: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................
|
||||
| 528: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................
|
||||
| 544: 01 00 02 00 00 f6 03 00 00 02 00 00 01 00 04 00 ................
|
||||
| 560: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................
|
||||
| 576: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................
|
||||
| 592: 01 00 02 00 00 00 03 00 01 00 00 00 01 00 04 00 ................
|
||||
| 608: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................
|
||||
| 624: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................
|
||||
| 640: 01 3d 02 00 00 00 03 00 06 00 00 00 01 00 01 00 .=..............
|
||||
| 656: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................
|
||||
| 672: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................
|
||||
| 688: 01 00 02 00 00 00 55 52 4c 52 65 71 75 65 73 74 ......URLRequest
|
||||
| 704: 43 6f 6e 00 00 00 01 01 0e d4 00 04 00 00 00 01 Con.............
|
||||
| 720: 0e f8 00 04 00 00 00 01 0f 1c 00 04 00 00 00 01 ................
|
||||
| 736: 0f 00 00 01 00 00 00 01 0f 86 00 01 00 00 00 01 ................
|
||||
| 752: 0f 84 00 01 00 00 00 01 00 00 01 0f c0 00 01 00 ................
|
||||
| 768: 00 00 01 0f e8 00 d6 0f 00 01 6f 00 02 0f d6 00 ..........o.....
|
||||
| 784: 02 34 03 03 03 00 01 00 00 00 01 00 05 00 00 00 .4..............
|
||||
| 800: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 01 00 ................
|
||||
| 816: 00 00 01 00 04 00 00 00 01 00 01 00 00 00 01 00 ................
|
||||
| 832: 02 00 00 00 03 00 01 00 10 00 01 00 04 00 00 00 ................
|
||||
| 848: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 00 02 ................
|
||||
| 864: 00 00 01 40 04 00 00 03 01 00 01 00 00 00 01 00 ...@............
|
||||
| 880: 02 00 00 00 03 00 01 00 00 00 00 00 01 0e f8 00 ................
|
||||
| 896: 04 77 4f 46 32 73 40 23 70 00 00 00 70 00 1f 00 .wOF2s@#p...p...
|
||||
| 912: 00 00 d8 00 00 00 ff ff 00 00 00 00 43 00 00 00 ............C...
|
||||
| 928: 00 00 ff ff ff ff ff 00 00 a8 00 00 0c 00 00 00 ................
|
||||
| 1024: 00 00 00 00 00 00 00 00 00 00 10 22 00 22 0f 00 ................
|
||||
| 1040: 00 00 00 00 00 00 10 22 00 00 70 00 1f 00 00 0f ..........p.....
|
||||
| 1056: d8 00 00 00 00 00 00 00 00 00 03 00 00 00 00 00 ................
|
||||
| 1072: 00 01 00 00 00 3f 23 70 00 00 00 01 0f 1c 00 04 .....?#p........
|
||||
| 1088: 00 00 00 01 0f 40 00 01 00 00 00 01 0f 86 00 01 .....@..........
|
||||
| 1104: 00 00 00 01 0f 84 00 01 00 00 00 01 00 00 01 0f ................
|
||||
| 1120: c0 00 01 00 00 00 01 0f e8 00 01 0f d6 00 6f 00 ..............o.
|
||||
| 1136: 02 0f d6 00 03 02 31 03 2b 03 2a f2 00 0f d4 00 ......1.+.*.....
|
||||
| 1152: 01 00 08 00 01 00 04 03 2b 00 02 02 32 00 01 0f ........+...2...
|
||||
| 1168: c8 01 15 00 02 20 c8 00 02 12 ad 02 00 24 06 c0 ..... .......$..
|
||||
| 1184: 00 00 00 03 00 00 01 24 00 2a 06 e4 00 00 00 03 .......$.*......
|
||||
| 1200: 00 00 01 25 00 38 07 0e 00 00 00 03 00 00 01 26 ...%.8.........&
|
||||
| 1216: 00 34 07 46 00 00 00 03 00 00 01 27 00 1c 07 7a .4.F.......'...z
|
||||
| 1232: 00 00 00 03 00 00 01 28 00 2a 07 96 00 00 00 03 .......(.*......
|
||||
| 1248: 00 e5 01 29 00 34 07 c0 00 00 00 03 00 00 01 2a ...).4.........*
|
||||
| 1264: 67 34 07 f4 00 00 00 03 00 00 01 2b 00 22 08 28 g4.........+...(
|
||||
| 1280: 00 00 00 00 01 00 01 00 00 00 01 00 02 00 00 00 ................
|
||||
| 1296: 03 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 ................
|
||||
| 1312: 01 00 00 00 01 00 02 00 00 00 03 00 00 02 00 00 ................
|
||||
| 1328: 01 00 04 00 00 00 01 00 01 00 00 00 01 00 02 00 ................
|
||||
| 1344: 00 00 03 00 01 00 00 00 01 00 04 00 00 00 01 00 ................
|
||||
| 1360: 01 00 00 00 01 00 02 00 00 00 03 00 01 00 00 21 ...............!
|
||||
| 1376: 04 00 01 00 00 00 00 00 01 00 00 00 01 00 02 00 ................
|
||||
| 1392: 00 00 03 00 01 00 00 00 01 00 04 00 00 00 01 00 ................
|
||||
| 1408: 01 00 00 00 01 00 02 00 00 00 03 00 01 00 00 00 ................
|
||||
| 1424: 01 00 05 00 00 00 01 00 01 00 00 01 00 02 02 02 ................
|
||||
| 1440: 12 02 02 02 63 6f 6c 6f 72 20 73 70 61 63 00 f3 ....color spac..
|
||||
| 1456: a0 81 a1 00 00 a0 02 02 02 02 69 95 73 6f 36 00 ..........i.so6.
|
||||
| 1472: ff 0d 00 97 8c 90 3f 0a 70 02 02 02 02 02 02 02 ......?.p.......
|
||||
| 1488: 02 02 02 02 02 02 02 01 00 00 06 02 02 02 02 5f ..............._
|
||||
| 1504: 02 02 02 2c 02 02 00 00 01 00 01 00 00 00 01 00 ...,............
|
||||
| 1520: 02 fe 00 00 03 00 01 00 00 00 01 c5 04 00 00 00 ................
|
||||
| 1536: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 01 00 ................
|
||||
| 1552: 00 00 81 00 04 00 00 00 01 00 01 00 00 00 01 00 ................
|
||||
| 1568: 02 00 fe 00 03 00 01 00 00 00 01 00 04 00 00 00 ................
|
||||
| 1584: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 01 00 ................
|
||||
| 1600: 00 00 01 00 04 00 00 00 01 00 01 00 00 00 01 00 ................
|
||||
| 1616: 02 00 00 00 03 00 01 00 00 00 01 00 04 00 00 00 ................
|
||||
| 1632: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 01 00 ................
|
||||
| 1648: 00 00 40 00 84 00 84 00 84 00 01 00 00 00 09 00 ..@.............
|
||||
| 1664: 06 00 f5 00 01 00 08 01 03 15 15 15 15 15 15 15 ................
|
||||
| 1680: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................
|
||||
| 1696: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................
|
||||
| 1712: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................
|
||||
| 1728: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................
|
||||
| 1744: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................
|
||||
| 1760: 15 15 15 15 15 15 15 15 15 15 15 00 03 00 62 00 ..............b.
|
||||
| 1776: 62 00 23 00 01 00 62 00 04 00 1e 00 62 00 62 00 b.#...b.....b.b.
|
||||
| 1792: 62 00 01 00 00 00 0a 00 01 00 03 00 01 00 03 00 b...............
|
||||
| 1808: 04 00 02 00 01 00 01 00 08 00 01 00 31 c6 00 03 ............1...
|
||||
| 1824: 00 0c 00 12 00 18 00 02 00 05 00 08 00 02 00 06 ................
|
||||
| 1840: 00 08 00 02 00 07 00 08 00 02 00 01 00 01 00 08 ................
|
||||
| 1856: 00 01 00 0c 00 03 00 16 00 1c 00 22 00 01 00 03 ................
|
||||
| 1872: 00 05 00 06 00 07 00 02 00 05 00 09 00 02 00 06 ................
|
||||
| 1888: 00 09 00 02 00 07 00 09 00 00 00 00 01 00 05 00 ................
|
||||
| 1904: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................
|
||||
| 1920: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................
|
||||
| 1936: 01 00 02 00 00 00 03 00 01 00 00 00 01 00 04 00 ................
|
||||
| 1952: 00 00 01 0f d6 00 02 34 03 03 03 00 01 00 00 00 .......4........
|
||||
| 1968: 01 00 05 00 00 00 01 00 01 00 00 00 01 00 02 00 ................
|
||||
| 1984: 00 00 03 00 01 00 00 00 01 00 04 00 00 00 01 00 ................
|
||||
| 2000: 01 00 00 00 01 00 02 fc 42 dc 19 5c 74 23 18 cd ........B...t#..
|
||||
| 2016: b3 a5 a8 7a 90 40 1d 66 12 5d e5 4f 85 00 68 f4 ...z.@.f.].O..h.
|
||||
| 2032: 05 98 86 25 24 dd bc c2 f6 f6 4e a3 e2 61 d2 c6 ...%$.....N..a..
|
||||
| 2048: aa c1 56 50 d4 80 82 35 f1 e2 59 41 50 a6 da 51 ..VP...5..YAP..Q
|
||||
| 2064: d4 62 9c 19 94 58 aa 31 30 8a 22 c2 5f 33 2b c9 .b...X.10..._3+.
|
||||
| 2080: b6 e6 b4 11 4e 51 82 c4 d8 b6 d8 b4 06 04 fb 68 ....NQ.........h
|
||||
| 2096: f4 d2 6f e7 cb 8a a8 82 d5 74 00 00 00 00 00 00 ..o......t......
|
||||
| 2368: 00 00 00 00 00 00 00 00 00 08 00 00 00 00 00 00 ................
|
||||
| 2432: 00 00 00 00 00 03 00 01 00 10 00 01 00 04 00 00 ................
|
||||
| 2448: 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 00 ................
|
||||
| 2464: 02 00 00 01 40 04 00 00 03 01 00 01 00 00 00 01 ....@...........
|
||||
| 2480: 00 02 00 00 00 03 00 01 00 00 00 00 00 01 0e f8 ................
|
||||
| 2496: 00 04 77 4f 46 32 73 40 23 70 00 00 00 70 00 1f ..wOF2s@#p...p..
|
||||
| 2512: 00 00 00 d8 00 00 00 ff ff 00 00 00 00 43 00 00 .............C..
|
||||
| 2528: 00 00 00 ff ff ff ff ff 00 00 a8 00 00 0c 00 00 ................
|
||||
| 2624: 00 00 00 00 00 00 00 00 00 00 00 10 22 00 22 0f ................
|
||||
| 2640: 00 00 00 00 00 00 00 10 22 00 00 70 00 1f 00 00 ...........p....
|
||||
| 2656: 0f d8 00 00 00 00 00 00 00 00 00 03 00 00 00 00 ................
|
||||
| 2672: 00 00 01 00 00 00 3f 23 70 00 00 00 01 0f 1c 00 ......?#p.......
|
||||
| 2688: 04 00 00 00 01 0f 40 00 01 00 00 00 01 0f 86 00 ......@.........
|
||||
| 2704: 01 00 00 00 01 0f 84 00 01 00 00 00 01 00 00 01 ................
|
||||
| 2720: 0f c0 00 01 00 00 00 01 0f e8 00 01 0f d6 00 6f ...............o
|
||||
| 2736: 00 02 0f d6 00 03 02 31 03 2b 03 2a f2 00 0f d4 .......1.+.*....
|
||||
| 2752: 00 01 00 08 00 01 00 04 03 2b 00 02 02 32 00 01 .........+...2..
|
||||
| 2768: 0f c8 01 15 00 02 20 c8 00 02 12 ad 02 00 24 06 ...... .......$.
|
||||
| 2784: c0 00 00 5a 03 00 00 01 24 00 2a 06 e4 00 00 00 ...Z....$.*.....
|
||||
| 2800: 03 00 00 01 25 00 38 07 0e 00 00 00 03 00 00 01 ....%.8.........
|
||||
| 2816: 26 00 34 07 46 00 00 00 03 00 00 01 27 00 1c 07 &.4.F.......'...
|
||||
| 2832: 7a 00 00 00 03 00 00 01 28 00 2a 07 96 00 00 00 z.......(.*.....
|
||||
| 2848: 03 00 e5 01 29 00 34 07 c0 00 00 00 03 00 00 01 ....).4.........
|
||||
| 2864: 2a 67 34 07 f4 00 00 00 03 00 00 01 2b 00 22 08 *g4.........+...
|
||||
| 2880: 28 00 00 00 00 01 00 01 00 00 00 01 00 02 00 00 (...............
|
||||
| 2896: 00 03 00 01 00 00 00 01 00 00 00 01 00 00 00 01 ................
|
||||
| 2912: 00 01 00 00 00 01 00 02 00 00 00 03 00 00 02 00 ................
|
||||
| 2928: 00 01 00 04 00 00 00 01 00 01 00 00 00 00 00 00 ................
|
||||
| 2992: 00 48 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .H..............
|
||||
| 3504: 00 00 00 00 00 00 00 00 00 00 00 97 00 00 00 00 ................
|
||||
| 3904: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 09 ................
|
||||
| 3920: 03 fe 00 00 01 36 00 3c 0a 38 00 00 00 03 00 00 .....6.<.8......
|
||||
| 3936: 01 37 00 20 0a 74 00 00 00 fb ff ff 00 38 00 2a .7. .t.......8.*
|
||||
| 3952: 0a 94 00 00 00 03 00 00 01 39 4f 54 54 4f 00 0e .........9OTTO..
|
||||
| 3968: 00 80 00 03 00 60 43 46 46 20 e3 ae 89 2a 00 00 .....`CFF ...*..
|
||||
| 3984: 02 b0 00 00 02 76 42 50 4f 53 00 15 00 0a 00 00 .....vBPOS......
|
||||
| 4000: 05 28 00 00 00 0c 54 53 55 42 c9 70 c3 06 00 00 .(....TSUB.p....
|
||||
| 4016: 05 34 1f 00 40 00 48 00 00 00 00 00 00 00 00 00 .4..@.H.........
|
||||
| 4064: 00 00 00 00 00 08 00 01 00 01 00 01 00 01 00 06 ................
|
||||
| 4080: 00 02 00 08 00 01 00 01 00 01 00 01 00 00 00 00 ................
|
||||
| end x1.db
|
||||
}]} {}
|
||||
do_test 7.1 {
|
||||
set R [sqlite3_recover_init db main test.db2]
|
||||
catch { $R run }
|
||||
list [catch { $R finish } msg] $msg
|
||||
} {1 {file is not a database}}
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -2103,7 +2103,7 @@ static int recoverIsValidPage(u8 *aTmp, const u8 *a, int n){
|
||||
if( iFree>(n-4) ) return 0;
|
||||
iNext = recoverGetU16(&a[iFree]);
|
||||
nByte = recoverGetU16(&a[iFree+2]);
|
||||
if( iFree+nByte>n ) return 0;
|
||||
if( iFree+nByte>n || nByte<4 ) return 0;
|
||||
if( iNext && iNext<iFree+nByte ) return 0;
|
||||
memset(&aUsed[iFree], 0xFF, nByte);
|
||||
iFree = iNext;
|
||||
|
||||
+39
-19
@@ -96,6 +96,11 @@ typedef unsigned int u32;
|
||||
#endif
|
||||
#endif /* !defined(SQLITE_AMALGAMATION) */
|
||||
|
||||
/* Macro to check for 4-byte alignment. Only used inside of assert() */
|
||||
#ifdef SQLITE_DEBUG
|
||||
# define FOUR_BYTE_ALIGNED(X) ((((char*)(X) - (char*)0) & 3)==0)
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
@@ -471,16 +476,17 @@ struct RtreeMatchArg {
|
||||
** at run-time.
|
||||
*/
|
||||
#ifndef SQLITE_BYTEORDER
|
||||
#if defined(i386) || defined(__i386__) || defined(_M_IX86) || \
|
||||
defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \
|
||||
defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \
|
||||
defined(__arm__)
|
||||
# define SQLITE_BYTEORDER 1234
|
||||
#elif defined(sparc) || defined(__ppc__)
|
||||
# define SQLITE_BYTEORDER 4321
|
||||
#else
|
||||
# define SQLITE_BYTEORDER 0 /* 0 means "unknown at compile-time" */
|
||||
#endif
|
||||
# if defined(i386) || defined(__i386__) || defined(_M_IX86) || \
|
||||
defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \
|
||||
defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \
|
||||
defined(__ARMEL__) || defined(__AARCH64EL__) || defined(_M_ARM64)
|
||||
# define SQLITE_BYTEORDER 1234
|
||||
# elif defined(sparc) || defined(__ppc__) || \
|
||||
defined(__ARMEB__) || defined(__AARCH64EB__)
|
||||
# define SQLITE_BYTEORDER 4321
|
||||
# else
|
||||
# define SQLITE_BYTEORDER 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
@@ -501,7 +507,7 @@ static int readInt16(u8 *p){
|
||||
return (p[0]<<8) + p[1];
|
||||
}
|
||||
static void readCoord(u8 *p, RtreeCoord *pCoord){
|
||||
assert( (((sqlite3_uint64)p)&3)==0 ); /* p is always 4-byte aligned */
|
||||
assert( FOUR_BYTE_ALIGNED(p) );
|
||||
#if SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300
|
||||
pCoord->u = _byteswap_ulong(*(u32*)p);
|
||||
#elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000
|
||||
@@ -555,7 +561,7 @@ static void writeInt16(u8 *p, int i){
|
||||
}
|
||||
static int writeCoord(u8 *p, RtreeCoord *pCoord){
|
||||
u32 i;
|
||||
assert( (((sqlite3_uint64)p)&3)==0 ); /* p is always 4-byte aligned */
|
||||
assert( FOUR_BYTE_ALIGNED(p) );
|
||||
assert( sizeof(RtreeCoord)==4 );
|
||||
assert( sizeof(u32)==4 );
|
||||
#if SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000
|
||||
@@ -1283,7 +1289,7 @@ static void rtreeNonleafConstraint(
|
||||
assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE
|
||||
|| p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_TRUE
|
||||
|| p->op==RTREE_FALSE );
|
||||
assert( (((sqlite3_uint64)pCellData)&3)==0 ); /* 4-byte aligned */
|
||||
assert( FOUR_BYTE_ALIGNED(pCellData) );
|
||||
switch( p->op ){
|
||||
case RTREE_TRUE: return; /* Always satisfied */
|
||||
case RTREE_FALSE: break; /* Never satisfied */
|
||||
@@ -1336,7 +1342,7 @@ static void rtreeLeafConstraint(
|
||||
|| p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_TRUE
|
||||
|| p->op==RTREE_FALSE );
|
||||
pCellData += 8 + p->iCoord*4;
|
||||
assert( (((sqlite3_uint64)pCellData)&3)==0 ); /* 4-byte aligned */
|
||||
assert( FOUR_BYTE_ALIGNED(pCellData) );
|
||||
RTREE_DECODE_COORD(eInt, pCellData, xN);
|
||||
switch( p->op ){
|
||||
case RTREE_TRUE: return; /* Always satisfied */
|
||||
@@ -1906,7 +1912,20 @@ static int rtreeFilter(
|
||||
p->pInfo->nCoord = pRtree->nDim2;
|
||||
p->pInfo->anQueue = pCsr->anQueue;
|
||||
p->pInfo->mxLevel = pRtree->iDepth + 1;
|
||||
}else if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
|
||||
}else if( eType==SQLITE_INTEGER ){
|
||||
sqlite3_int64 iVal = sqlite3_value_int64(argv[ii]);
|
||||
#ifdef SQLITE_RTREE_INT_ONLY
|
||||
p->u.rValue = iVal;
|
||||
#else
|
||||
p->u.rValue = (double)iVal;
|
||||
if( iVal>=((sqlite3_int64)1)<<48
|
||||
|| iVal<=-(((sqlite3_int64)1)<<48)
|
||||
){
|
||||
if( p->op==RTREE_LT ) p->op = RTREE_LE;
|
||||
if( p->op==RTREE_GT ) p->op = RTREE_GE;
|
||||
}
|
||||
#endif
|
||||
}else if( eType==SQLITE_FLOAT ){
|
||||
#ifdef SQLITE_RTREE_INT_ONLY
|
||||
p->u.rValue = sqlite3_value_int64(argv[ii]);
|
||||
#else
|
||||
@@ -2037,11 +2056,12 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
|
||||
|| p->op==SQLITE_INDEX_CONSTRAINT_MATCH)
|
||||
){
|
||||
u8 op;
|
||||
u8 doOmit = 1;
|
||||
switch( p->op ){
|
||||
case SQLITE_INDEX_CONSTRAINT_EQ: op = RTREE_EQ; break;
|
||||
case SQLITE_INDEX_CONSTRAINT_GT: op = RTREE_GT; break;
|
||||
case SQLITE_INDEX_CONSTRAINT_EQ: op = RTREE_EQ; doOmit = 0; break;
|
||||
case SQLITE_INDEX_CONSTRAINT_GT: op = RTREE_GT; doOmit = 0; break;
|
||||
case SQLITE_INDEX_CONSTRAINT_LE: op = RTREE_LE; break;
|
||||
case SQLITE_INDEX_CONSTRAINT_LT: op = RTREE_LT; break;
|
||||
case SQLITE_INDEX_CONSTRAINT_LT: op = RTREE_LT; doOmit = 0; break;
|
||||
case SQLITE_INDEX_CONSTRAINT_GE: op = RTREE_GE; break;
|
||||
case SQLITE_INDEX_CONSTRAINT_MATCH: op = RTREE_MATCH; break;
|
||||
default: op = 0; break;
|
||||
@@ -2050,7 +2070,7 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
|
||||
zIdxStr[iIdx++] = op;
|
||||
zIdxStr[iIdx++] = (char)(p->iColumn - 1 + '0');
|
||||
pIdxInfo->aConstraintUsage[ii].argvIndex = (iIdx/2);
|
||||
pIdxInfo->aConstraintUsage[ii].omit = 1;
|
||||
pIdxInfo->aConstraintUsage[ii].omit = doOmit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -756,4 +756,32 @@ do_execsql_test 20.4 {
|
||||
SELECT * FROM t1 JOIN t0 ON true RIGHT JOIN rt0 ON x0>a WHERE x0 = 0;
|
||||
} {- - 0 0.0 0.0}
|
||||
|
||||
# 2023-05-19 https://sqlite.org/forum/forumpost/da61c4a1b5b4af19
|
||||
# Do not omit constraints that involve equality comparisons of
|
||||
# floating-point values.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 21.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING rtree(id, x0, x1);
|
||||
INSERT INTO t1 VALUES(0, 1, 9223372036854775807);
|
||||
SELECT count(*) FROM t1 WHERE x1=9223372036854775807;
|
||||
} {0}
|
||||
do_execsql_test 21.1 {
|
||||
SELECT x1=9223372036854775807 FROM t1;
|
||||
} {0}
|
||||
|
||||
# 2023-05-22 https://sqlite.org/forum/forumpost/da70ee0d0d
|
||||
# Round-off error associated with using large integer constraints on
|
||||
# a rtree search.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 22.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING rtree ( id, x0, x1 );
|
||||
INSERT INTO t1 VALUES (123, 9223372036854775799, 9223372036854775800);
|
||||
SELECT id FROM t1 WHERE x0 > 9223372036854775807;
|
||||
} {123}
|
||||
do_execsql_test 22.1 {
|
||||
SELECT id, x0 > 9223372036854775807 AS 'a0' FROM t1;
|
||||
} {123 1}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -41,30 +41,6 @@ proc scksum {db dbname} {
|
||||
return [md5 $txt]
|
||||
}
|
||||
|
||||
proc do_diff_test {tn setup} {
|
||||
reset_db
|
||||
forcedelete test.db2
|
||||
execsql { ATTACH 'test.db2' AS aux }
|
||||
execsql $setup
|
||||
|
||||
sqlite3session S db main
|
||||
foreach tbl [db eval {SELECT name FROM sqlite_master WHERE type='table'}] {
|
||||
S attach $tbl
|
||||
S diff aux $tbl
|
||||
}
|
||||
|
||||
set C [S changeset]
|
||||
S delete
|
||||
|
||||
sqlite3 db2 test.db2
|
||||
sqlite3changeset_apply db2 $C ""
|
||||
uplevel do_test $tn.1 [list {execsql { PRAGMA integrity_check } db2}] ok
|
||||
db2 close
|
||||
|
||||
set cksum [scksum db main]
|
||||
uplevel do_test $tn.2 [list {scksum db aux}] [list $cksum]
|
||||
}
|
||||
|
||||
# Ensure that the diff produced by comparing the current contents of [db]
|
||||
# with itself is empty.
|
||||
proc do_empty_diff_test {tn} {
|
||||
|
||||
@@ -52,6 +52,7 @@ proc do_conflict_test {tn args} {
|
||||
proc bgerror {args} { set ::background_error $args }
|
||||
|
||||
sqlite3session S db main
|
||||
S object_config rowid 1
|
||||
foreach t $O(-tables) { S attach $t }
|
||||
execsql $O(-sql)
|
||||
|
||||
@@ -81,6 +82,7 @@ proc changeset_from_sql {sql {dbname main}} {
|
||||
}
|
||||
set rc [catch {
|
||||
sqlite3session S db $dbname
|
||||
S object_config rowid 1
|
||||
db eval "SELECT name FROM $dbname.sqlite_master WHERE type = 'table'" {
|
||||
S attach $name
|
||||
}
|
||||
@@ -138,6 +140,7 @@ proc do_then_apply_sql {args} {
|
||||
proc xConflict args { incr ::n_conflict ; return "OMIT" }
|
||||
set rc [catch {
|
||||
sqlite3session S db $dbname
|
||||
S object_config rowid 1
|
||||
db eval "SELECT name FROM $dbname.sqlite_master WHERE type = 'table'" {
|
||||
S attach $name
|
||||
}
|
||||
@@ -162,6 +165,8 @@ proc do_then_apply_sql {args} {
|
||||
|
||||
proc do_iterator_test {tn tbl_list sql res} {
|
||||
sqlite3session S db main
|
||||
S object_config rowid 1
|
||||
|
||||
if {[llength $tbl_list]==0} { S attach * }
|
||||
foreach t $tbl_list {S attach $t}
|
||||
|
||||
@@ -171,6 +176,7 @@ proc do_iterator_test {tn tbl_list sql res} {
|
||||
foreach v $res { lappend r $v }
|
||||
|
||||
set x [list]
|
||||
# set ::c [S changeset] ; execsql_pp { SELECT quote($::c) }
|
||||
sqlite3session_foreach c [S changeset] { lappend x $c }
|
||||
uplevel do_test $tn [list [list set {} $x]] [list $r]
|
||||
|
||||
@@ -245,3 +251,49 @@ proc number_name {n} {
|
||||
if {$txt==""} {set txt zero}
|
||||
return $txt
|
||||
}
|
||||
|
||||
proc scksum {db dbname} {
|
||||
|
||||
if {$dbname=="temp"} {
|
||||
set master sqlite_temp_master
|
||||
} else {
|
||||
set master $dbname.sqlite_master
|
||||
}
|
||||
|
||||
set alltab [$db eval "SELECT name FROM $master WHERE type='table'"]
|
||||
set txt [$db eval "SELECT * FROM $master ORDER BY type,name,sql"]
|
||||
foreach tab $alltab {
|
||||
set cols [list]
|
||||
db eval "PRAGMA $dbname.table_info = $tab" x {
|
||||
lappend cols "quote($x(name))"
|
||||
}
|
||||
set cols [join $cols ,]
|
||||
append txt [db eval "SELECT $cols FROM $dbname.$tab ORDER BY $cols"]
|
||||
}
|
||||
return [md5 $txt]
|
||||
}
|
||||
|
||||
proc do_diff_test {tn setup} {
|
||||
reset_db
|
||||
forcedelete test.db2
|
||||
execsql { ATTACH 'test.db2' AS aux }
|
||||
execsql $setup
|
||||
|
||||
sqlite3session S db main
|
||||
S object_config rowid 1
|
||||
foreach tbl [db eval {SELECT name FROM sqlite_master WHERE type='table'}] {
|
||||
S attach $tbl
|
||||
S diff aux $tbl
|
||||
}
|
||||
|
||||
set C [S changeset]
|
||||
S delete
|
||||
|
||||
sqlite3 db2 test.db2
|
||||
sqlite3changeset_apply db2 $C ""
|
||||
uplevel do_test $tn.1 [list {execsql { PRAGMA integrity_check } db2}] ok
|
||||
db2 close
|
||||
|
||||
set cksum [scksum db main]
|
||||
uplevel do_test $tn.2 [list {scksum db aux}] [list $cksum]
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ source [file join [file dirname [info script]] session_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !session {finish_test; return}
|
||||
|
||||
set testprefix sessionnoop
|
||||
set testprefix sessionnoop2
|
||||
|
||||
foreach {tn wo} {
|
||||
1 ""
|
||||
@@ -120,11 +120,11 @@ do_test $tn.2.2 {
|
||||
}
|
||||
} {}
|
||||
|
||||
db2 close
|
||||
|
||||
}]
|
||||
}
|
||||
|
||||
db2 close
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
|
||||
@@ -84,6 +84,7 @@ proc do_rebase_test {tn sql1 sql2 conflict_handler {testsql ""} {testres ""}} {
|
||||
db eval BEGIN
|
||||
|
||||
sqlite3session S1 db main
|
||||
S1 object_config rowid 1
|
||||
S1 attach *
|
||||
execsql $sql1 db
|
||||
set c1 [S1 changeset]
|
||||
@@ -91,6 +92,7 @@ proc do_rebase_test {tn sql1 sql2 conflict_handler {testsql ""} {testres ""}} {
|
||||
|
||||
if {$i==1} {
|
||||
sqlite3session S2 db2 main
|
||||
S2 object_config rowid 1
|
||||
S2 attach *
|
||||
execsql $sql2 db2
|
||||
set c2 [S2 changeset]
|
||||
@@ -100,6 +102,7 @@ proc do_rebase_test {tn sql1 sql2 conflict_handler {testsql ""} {testres ""}} {
|
||||
foreach sql [split $sql2 ";"] {
|
||||
if {[string is space $sql]} continue
|
||||
sqlite3session S2 db2 main
|
||||
S2 object_config rowid 1
|
||||
S2 attach *
|
||||
execsql $sql db2
|
||||
lappend c2 [S2 changeset]
|
||||
@@ -341,6 +344,79 @@ do_rebase_test 2.2.3 {
|
||||
OMIT
|
||||
} { SELECT * FROM t2 WHERE z='B' } { 1 one B }
|
||||
|
||||
|
||||
reset_db
|
||||
do_execsql_test 2.3.0 {
|
||||
CREATE TABLE t1 (b TEXT);
|
||||
INSERT INTO t1(rowid, b) VALUES(1, 'one');
|
||||
INSERT INTO t1(rowid, b) VALUES(2, 'two');
|
||||
INSERT INTO t1(rowid, b) VALUES(3, 'three');
|
||||
}
|
||||
do_rebase_test 2.3.1 {
|
||||
UPDATE t1 SET b = 'two.1' WHERE rowid=2
|
||||
} {
|
||||
UPDATE t1 SET b = 'two.2' WHERE rowid=2;
|
||||
} {
|
||||
OMIT
|
||||
} { SELECT rowid, * FROM t1 } {1 one 2 two.1 3 three}
|
||||
|
||||
do_rebase_test 2.3.2 {
|
||||
UPDATE t1 SET b = 'two.1' WHERE rowid=2
|
||||
} {
|
||||
UPDATE t1 SET b = 'two.2' WHERE rowid=2;
|
||||
} {
|
||||
REPLACE
|
||||
} { SELECT rowid, * FROM t1 } {1 one 2 two.2 3 three}
|
||||
|
||||
do_rebase_test 2.3.3 {
|
||||
DELETE FROM t1 WHERE rowid=3
|
||||
} {
|
||||
DELETE FROM t1 WHERE rowid=3;
|
||||
} {
|
||||
OMIT
|
||||
} { SELECT rowid, * FROM t1 } {1 one 2 two}
|
||||
|
||||
do_rebase_test 2.3.4 {
|
||||
DELETE FROM t1 WHERE rowid=1
|
||||
} {
|
||||
UPDATE t1 SET b='one.2' WHERE rowid=1
|
||||
} {
|
||||
OMIT
|
||||
} { SELECT rowid, * FROM t1 } {2 two 3 three}
|
||||
|
||||
do_rebase_test 2.3.6 {
|
||||
UPDATE t1 SET b='three.1' WHERE rowid=3
|
||||
} {
|
||||
DELETE FROM t1 WHERE rowid=3;
|
||||
} {
|
||||
OMIT
|
||||
} { SELECT rowid, * FROM t1 } {1 one 2 two 3 three.1}
|
||||
|
||||
do_rebase_test 2.3.7 {
|
||||
UPDATE t1 SET b='three.1' WHERE rowid=3
|
||||
} {
|
||||
DELETE FROM t1 WHERE rowid=3;
|
||||
} {
|
||||
REPLACE
|
||||
} { SELECT rowid, * FROM t1 } {1 one 2 two}
|
||||
|
||||
do_rebase_test 2.3.8 {
|
||||
INSERT INTO t1(rowid, b) VALUES(4, 'four.1')
|
||||
} {
|
||||
INSERT INTO t1(rowid, b) VALUES(4, 'four.2');
|
||||
} {
|
||||
REPLACE
|
||||
} { SELECT rowid, * FROM t1 } {1 one 2 two 3 three 4 four.2}
|
||||
|
||||
do_rebase_test 2.3.9 {
|
||||
INSERT INTO t1(rowid, b) VALUES(4, 'four.1')
|
||||
} {
|
||||
INSERT INTO t1(rowid, b) VALUES(4, 'four.2');
|
||||
} {
|
||||
OMIT
|
||||
} { SELECT rowid, * FROM t1 } {1 one 2 two 3 three 4 four.1}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 3.0 {
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
# 2011 Mar 16
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# The focus of this file is testing the session module.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] session_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !session {finish_test; return}
|
||||
|
||||
set testprefix sessionrowid
|
||||
|
||||
do_execsql_test 0.0 {
|
||||
CREATE TABLE t1(a, b);
|
||||
}
|
||||
|
||||
foreach {tn rowid bEmpty} {
|
||||
1 0 1
|
||||
2 1 0
|
||||
3 -1 1
|
||||
} {
|
||||
do_test 0.$tn {
|
||||
sqlite3session S db main
|
||||
if {$rowid>=0} { S object_config rowid $rowid }
|
||||
S attach t1
|
||||
execsql { INSERT INTO t1 VALUES(1, 2); }
|
||||
expr [string length [S changeset]]==0
|
||||
} $bEmpty
|
||||
S delete
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a, b);
|
||||
}
|
||||
|
||||
do_iterator_test 1.1 t1 {
|
||||
INSERT INTO t1 VALUES('i', 'one');
|
||||
} {
|
||||
{INSERT t1 0 X.. {} {i 1 t i t one}}
|
||||
}
|
||||
|
||||
do_execsql_test 1.2 {
|
||||
SELECT rowid, * FROM t1
|
||||
} {1 i one}
|
||||
|
||||
do_iterator_test 1.3 t1 {
|
||||
UPDATE t1 SET b='two'
|
||||
} {
|
||||
{UPDATE t1 0 X.. {i 1 {} {} t one} {{} {} {} {} t two}}
|
||||
}
|
||||
|
||||
do_iterator_test 1.4 t1 {
|
||||
DELETE FROM t1;
|
||||
} {
|
||||
{DELETE t1 0 X.. {i 1 t i t two} {}}
|
||||
}
|
||||
|
||||
do_iterator_test 1.5 t1 {
|
||||
INSERT INTO t1(rowid, a, b) VALUES(14, 'hello', 'world');
|
||||
INSERT INTO t1(rowid, a, b) VALUES(NULL, 'yes', 'no');
|
||||
INSERT INTO t1(rowid, a, b) VALUES(-123, 'ii', 'iii');
|
||||
} {
|
||||
{INSERT t1 0 X.. {} {i -123 t ii t iii}}
|
||||
{INSERT t1 0 X.. {} {i 15 t yes t no}}
|
||||
{INSERT t1 0 X.. {} {i 14 t hello t world}}
|
||||
}
|
||||
|
||||
do_iterator_test 1.6 t1 {
|
||||
UPDATE t1 SET a='deluxe' WHERE rowid=14;
|
||||
DELETE FROM t1 WHERE rowid=-123;
|
||||
INSERT INTO t1 VALUES('x', 'xi');
|
||||
} {
|
||||
{DELETE t1 0 X.. {i -123 t ii t iii} {}}
|
||||
{UPDATE t1 0 X.. {i 14 t hello {} {}} {{} {} t deluxe {} {}}}
|
||||
{INSERT t1 0 X.. {} {i 16 t x t xi}}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
forcedelete test.db2
|
||||
sqlite3 db2 test.db2
|
||||
|
||||
do_execsql_test 2.0 {
|
||||
CREATE TABLE t1(a, b);
|
||||
}
|
||||
do_execsql_test -db db2 2.0.1 {
|
||||
CREATE TABLE t1(a, b);
|
||||
}
|
||||
|
||||
proc xConflict {args} {
|
||||
puts "CONFLICT!"
|
||||
return "OMIT"
|
||||
}
|
||||
|
||||
do_test 2.1 {
|
||||
set C [changeset_from_sql {
|
||||
INSERT INTO t1 VALUES('abc', 'def');
|
||||
}]
|
||||
sqlite3changeset_apply db2 $C xConflict
|
||||
execsql { SELECT * FROM t1 } db2
|
||||
} {abc def}
|
||||
do_test 2.2 {
|
||||
set C [changeset_from_sql {
|
||||
UPDATE t1 SET b='hello'
|
||||
}]
|
||||
sqlite3changeset_apply db2 $C xConflict
|
||||
execsql { SELECT * FROM t1 } db2
|
||||
} {abc hello}
|
||||
do_test 2.3 {
|
||||
set C [changeset_from_sql {
|
||||
DELETE FROM t1 WHERE b='hello'
|
||||
}]
|
||||
sqlite3changeset_apply db2 $C xConflict
|
||||
execsql { SELECT * FROM t1 } db2
|
||||
} {}
|
||||
|
||||
do_test 2.4 {
|
||||
do_then_apply_sql {
|
||||
INSERT INTO t1 VALUES('i', 'one');
|
||||
INSERT INTO t1 VALUES('ii', 'two');
|
||||
INSERT INTO t1 VALUES('iii', 'three');
|
||||
INSERT INTO t1 VALUES('iv', 'four');
|
||||
}
|
||||
compare_db db db2
|
||||
} {}
|
||||
|
||||
do_test 2.5 {
|
||||
do_then_apply_sql {
|
||||
DELETE FROM t1 WHERE a='ii';
|
||||
UPDATE t1 SET b='THREE' WHERE a='iii';
|
||||
UPDATE t1 SET a='III' WHERE a='iii';
|
||||
INSERT INTO t1 VALUES('v', 'five');
|
||||
}
|
||||
compare_db db db2
|
||||
} {}
|
||||
|
||||
do_execsql_test 2.6 {SELECT * FROM t1} {i one III THREE iv four v five}
|
||||
do_execsql_test -db db2 2.7 {SELECT * FROM t1} {i one III THREE iv four v five}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
db2 close
|
||||
reset_db
|
||||
forcedelete test.db2
|
||||
sqlite3 db2 test.db2
|
||||
|
||||
set init_sql {
|
||||
CREATE TABlE t4(a, b);
|
||||
CREATE INDEX t4a ON t4(a);
|
||||
CREATE UNIQUE INDEX t4b ON t4(b);
|
||||
}
|
||||
|
||||
do_execsql_test 3.0 $init_sql
|
||||
do_execsql_test -db db2 3.0a $init_sql
|
||||
|
||||
do_execsql_test -db db2 3.1 {
|
||||
INSERT INTO t4(rowid, a, b) VALUES(43, 'hello', 'world');
|
||||
}
|
||||
do_conflict_test 3.2 -sql {
|
||||
INSERT INTO t4(rowid, a, b) VALUES(43, 'abc', 'def');
|
||||
} -tables t4 -conflicts {
|
||||
{INSERT t4 CONFLICT {i 43 t abc t def} {i 43 t hello t world}}
|
||||
}
|
||||
do_execsql_test -db db2 3.3 {
|
||||
SELECT * FROM t4
|
||||
} {hello world}
|
||||
|
||||
do_execsql_test 3.4 { DELETE FROM t4 }
|
||||
do_conflict_test 3.5 -sql {
|
||||
INSERT INTO t4(rowid, a, b) VALUES(43, 'abc', 'def');
|
||||
} -tables t4 -conflicts {
|
||||
{INSERT t4 CONFLICT {i 43 t abc t def} {i 43 t hello t world}}
|
||||
} -policy REPLACE
|
||||
do_execsql_test -db db2 3.6 {
|
||||
SELECT * FROM t4
|
||||
} {abc def}
|
||||
|
||||
do_execsql_test 3.7 { DELETE FROM t4 }
|
||||
do_conflict_test 3.8 -sql {
|
||||
INSERT INTO t4(rowid, a, b) VALUES(45, 'xyz', 'def');
|
||||
} -tables t4 -conflicts {
|
||||
{INSERT t4 CONSTRAINT {i 45 t xyz t def}}
|
||||
}
|
||||
do_execsql_test -db db2 3.9 {
|
||||
SELECT * FROM t4
|
||||
} {abc def}
|
||||
|
||||
|
||||
do_execsql_test -db db 3.10a { DELETE FROM t4 }
|
||||
do_execsql_test -db db2 3.10b { DELETE FROM t4 }
|
||||
|
||||
do_execsql_test -db db 3.11a {
|
||||
INSERT INTO t4(rowid, a, b) VALUES(111, 'one', 'one');
|
||||
INSERT INTO t4(rowid, a, b) VALUES(222, 'two', 'two');
|
||||
}
|
||||
do_execsql_test -db db2 3.11b {
|
||||
INSERT INTO t4(rowid, a, b) VALUES(111, 'one', 'blip');
|
||||
}
|
||||
|
||||
do_conflict_test 3.12 -sql {
|
||||
DELETE FROM t4 WHERE a='one';
|
||||
} -tables t4 -conflicts {
|
||||
{DELETE t4 DATA {i 111 t one t one} {i 111 t one t blip}}
|
||||
}
|
||||
do_execsql_test -db db2 3.13 {
|
||||
SELECT * FROM t4
|
||||
} {one blip}
|
||||
|
||||
do_conflict_test 3.14 -sql {
|
||||
DELETE FROM t4 WHERE a='two';
|
||||
} -tables t4 -conflicts {
|
||||
{DELETE t4 NOTFOUND {i 222 t two t two}}
|
||||
}
|
||||
do_execsql_test -db db2 3.15 {
|
||||
SELECT * FROM t4
|
||||
} {one blip}
|
||||
|
||||
do_execsql_test -db db 3.16a { DELETE FROM t4 }
|
||||
do_execsql_test -db db2 3.16b { DELETE FROM t4 }
|
||||
|
||||
do_execsql_test -db db 3.17a {
|
||||
INSERT INTO t4(rowid, a, b) VALUES(111, 'one', 'one');
|
||||
INSERT INTO t4(rowid, a, b) VALUES(222, 'two', 'two');
|
||||
}
|
||||
do_execsql_test -db db2 3.17b {
|
||||
INSERT INTO t4(rowid, a, b) VALUES(111, 'one', 'blip');
|
||||
}
|
||||
|
||||
do_conflict_test 3.18 -sql {
|
||||
UPDATE t4 SET b='xyz' WHERE a='one'
|
||||
} -tables t4 -conflicts {
|
||||
{UPDATE t4 DATA {i 111 {} {} t one} {{} {} {} {} t xyz} {i 111 t one t blip}}
|
||||
}
|
||||
do_execsql_test -db db2 3.19 {
|
||||
SELECT * FROM t4
|
||||
} {one blip}
|
||||
|
||||
do_conflict_test 3.20 -sql {
|
||||
UPDATE t4 SET b='123' WHERE a='two'
|
||||
} -tables t4 -conflicts {
|
||||
{UPDATE t4 NOTFOUND {i 222 {} {} t two} {{} {} {} {} t 123}}
|
||||
}
|
||||
do_execsql_test -db db2 3.21 {
|
||||
SELECT * FROM t4
|
||||
} {one blip}
|
||||
db2 close
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
breakpoint
|
||||
do_diff_test 4.0 {
|
||||
CREATE TABLE t1(x, y);
|
||||
CREATE TABLE aux.t1(x, y);
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
}
|
||||
|
||||
do_diff_test 4.1 {
|
||||
CREATE TABLE t1(x, y);
|
||||
CREATE TABLE aux.t1(x, y);
|
||||
INSERT INTO aux.t1 VALUES(1, 2);
|
||||
}
|
||||
|
||||
do_diff_test 4.2 {
|
||||
CREATE TABLE t1(x, y);
|
||||
CREATE TABLE aux.t1(x, y);
|
||||
INSERT INTO t1(rowid, x, y) VALUES(413, 'hello', 'there');
|
||||
INSERT INTO aux.t1(rowid, x, y) VALUES(413, 'hello', 'world');
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -113,17 +113,17 @@ do_execsql_test 3.0 {
|
||||
|
||||
do_test 3.1 {
|
||||
sqlite3session S db main
|
||||
S object_config_size -1
|
||||
S object_config size -1
|
||||
} 1
|
||||
|
||||
do_test 3.2.1 { S object_config_size 0 } 0
|
||||
do_test 3.2.2 { S object_config_size -1 } 0
|
||||
do_test 3.2.3 { S object_config_size 1 } 1
|
||||
do_test 3.2.4 { S object_config_size -1 } 1
|
||||
do_test 3.2.1 { S object_config size 0 } 0
|
||||
do_test 3.2.2 { S object_config size -1 } 0
|
||||
do_test 3.2.3 { S object_config size 1 } 1
|
||||
do_test 3.2.4 { S object_config size -1 } 1
|
||||
|
||||
do_test 3.3 { S attach t1 } {}
|
||||
do_test 3.4 { S object_config_size 1 } {SQLITE_MISUSE}
|
||||
do_test 3.4 { S object_config_size -1 } {1}
|
||||
do_test 3.4 { S object_config size 1 } {SQLITE_MISUSE}
|
||||
do_test 3.4 { S object_config size -1 } {1}
|
||||
|
||||
S delete
|
||||
|
||||
|
||||
+194
-80
@@ -25,6 +25,8 @@ typedef struct SessionInput SessionInput;
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#define SESSIONS_ROWID "_rowid_"
|
||||
|
||||
static int sessions_strm_chunk_size = SESSIONS_STRM_CHUNK_SIZE;
|
||||
|
||||
typedef struct SessionHook SessionHook;
|
||||
@@ -46,6 +48,7 @@ struct sqlite3_session {
|
||||
int bEnable; /* True if currently recording */
|
||||
int bIndirect; /* True if all changes are indirect */
|
||||
int bAutoAttach; /* True to auto-attach tables */
|
||||
int bImplicitPK; /* True to handle tables with implicit PK */
|
||||
int rc; /* Non-zero if an error has occurred */
|
||||
void *pFilterCtx; /* First argument to pass to xTableFilter */
|
||||
int (*xTableFilter)(void *pCtx, const char *zTab);
|
||||
@@ -122,6 +125,7 @@ struct SessionTable {
|
||||
char *zName; /* Local name of table */
|
||||
int nCol; /* Number of columns in table zName */
|
||||
int bStat1; /* True if this is sqlite_stat1 */
|
||||
int bRowid; /* True if this table uses rowid for PK */
|
||||
const char **azCol; /* Column names */
|
||||
u8 *abPK; /* Array of primary key flags */
|
||||
int nEntry; /* Total number of entries in hash table */
|
||||
@@ -514,6 +518,7 @@ static unsigned int sessionHashAppendType(unsigned int h, int eType){
|
||||
*/
|
||||
static int sessionPreupdateHash(
|
||||
sqlite3_session *pSession, /* Session object that owns pTab */
|
||||
i64 iRowid,
|
||||
SessionTable *pTab, /* Session table handle */
|
||||
int bNew, /* True to hash the new.* PK */
|
||||
int *piHash, /* OUT: Hash value */
|
||||
@@ -522,48 +527,53 @@ static int sessionPreupdateHash(
|
||||
unsigned int h = 0; /* Hash value to return */
|
||||
int i; /* Used to iterate through columns */
|
||||
|
||||
assert( *pbNullPK==0 );
|
||||
assert( pTab->nCol==pSession->hook.xCount(pSession->hook.pCtx) );
|
||||
for(i=0; i<pTab->nCol; i++){
|
||||
if( pTab->abPK[i] ){
|
||||
int rc;
|
||||
int eType;
|
||||
sqlite3_value *pVal;
|
||||
if( pTab->bRowid ){
|
||||
assert( pTab->nCol-1==pSession->hook.xCount(pSession->hook.pCtx) );
|
||||
h = sessionHashAppendI64(h, iRowid);
|
||||
}else{
|
||||
assert( *pbNullPK==0 );
|
||||
assert( pTab->nCol==pSession->hook.xCount(pSession->hook.pCtx) );
|
||||
for(i=0; i<pTab->nCol; i++){
|
||||
if( pTab->abPK[i] ){
|
||||
int rc;
|
||||
int eType;
|
||||
sqlite3_value *pVal;
|
||||
|
||||
if( bNew ){
|
||||
rc = pSession->hook.xNew(pSession->hook.pCtx, i, &pVal);
|
||||
}else{
|
||||
rc = pSession->hook.xOld(pSession->hook.pCtx, i, &pVal);
|
||||
}
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
if( bNew ){
|
||||
rc = pSession->hook.xNew(pSession->hook.pCtx, i, &pVal);
|
||||
}else{
|
||||
rc = pSession->hook.xOld(pSession->hook.pCtx, i, &pVal);
|
||||
}
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
|
||||
eType = sqlite3_value_type(pVal);
|
||||
h = sessionHashAppendType(h, eType);
|
||||
if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
|
||||
i64 iVal;
|
||||
if( eType==SQLITE_INTEGER ){
|
||||
iVal = sqlite3_value_int64(pVal);
|
||||
eType = sqlite3_value_type(pVal);
|
||||
h = sessionHashAppendType(h, eType);
|
||||
if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
|
||||
i64 iVal;
|
||||
if( eType==SQLITE_INTEGER ){
|
||||
iVal = sqlite3_value_int64(pVal);
|
||||
}else{
|
||||
double rVal = sqlite3_value_double(pVal);
|
||||
assert( sizeof(iVal)==8 && sizeof(rVal)==8 );
|
||||
memcpy(&iVal, &rVal, 8);
|
||||
}
|
||||
h = sessionHashAppendI64(h, iVal);
|
||||
}else if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
|
||||
const u8 *z;
|
||||
int n;
|
||||
if( eType==SQLITE_TEXT ){
|
||||
z = (const u8 *)sqlite3_value_text(pVal);
|
||||
}else{
|
||||
z = (const u8 *)sqlite3_value_blob(pVal);
|
||||
}
|
||||
n = sqlite3_value_bytes(pVal);
|
||||
if( !z && (eType!=SQLITE_BLOB || n>0) ) return SQLITE_NOMEM;
|
||||
h = sessionHashAppendBlob(h, n, z);
|
||||
}else{
|
||||
double rVal = sqlite3_value_double(pVal);
|
||||
assert( sizeof(iVal)==8 && sizeof(rVal)==8 );
|
||||
memcpy(&iVal, &rVal, 8);
|
||||
assert( eType==SQLITE_NULL );
|
||||
assert( pTab->bStat1==0 || i!=1 );
|
||||
*pbNullPK = 1;
|
||||
}
|
||||
h = sessionHashAppendI64(h, iVal);
|
||||
}else if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
|
||||
const u8 *z;
|
||||
int n;
|
||||
if( eType==SQLITE_TEXT ){
|
||||
z = (const u8 *)sqlite3_value_text(pVal);
|
||||
}else{
|
||||
z = (const u8 *)sqlite3_value_blob(pVal);
|
||||
}
|
||||
n = sqlite3_value_bytes(pVal);
|
||||
if( !z && (eType!=SQLITE_BLOB || n>0) ) return SQLITE_NOMEM;
|
||||
h = sessionHashAppendBlob(h, n, z);
|
||||
}else{
|
||||
assert( eType==SQLITE_NULL );
|
||||
assert( pTab->bStat1==0 || i!=1 );
|
||||
*pbNullPK = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -846,6 +856,7 @@ static int sessionMergeUpdate(
|
||||
*/
|
||||
static int sessionPreupdateEqual(
|
||||
sqlite3_session *pSession, /* Session object that owns SessionTable */
|
||||
i64 iRowid, /* Rowid value if pTab->bRowid */
|
||||
SessionTable *pTab, /* Table associated with change */
|
||||
SessionChange *pChange, /* Change to compare to */
|
||||
int op /* Current pre-update operation */
|
||||
@@ -853,6 +864,11 @@ static int sessionPreupdateEqual(
|
||||
int iCol; /* Used to iterate through columns */
|
||||
u8 *a = pChange->aRecord; /* Cursor used to scan change record */
|
||||
|
||||
if( pTab->bRowid ){
|
||||
if( a[0]!=SQLITE_INTEGER ) return 0;
|
||||
return sessionGetI64(&a[1])==iRowid;
|
||||
}
|
||||
|
||||
assert( op==SQLITE_INSERT || op==SQLITE_UPDATE || op==SQLITE_DELETE );
|
||||
for(iCol=0; iCol<pTab->nCol; iCol++){
|
||||
if( !pTab->abPK[iCol] ){
|
||||
@@ -875,6 +891,7 @@ static int sessionPreupdateEqual(
|
||||
rc = pSession->hook.xOld(pSession->hook.pCtx, iCol, &pVal);
|
||||
}
|
||||
assert( rc==SQLITE_OK );
|
||||
(void)rc; /* Suppress warning about unused variable */
|
||||
if( sqlite3_value_type(pVal)!=eType ) return 0;
|
||||
|
||||
/* A SessionChange object never has a NULL value in a PK column */
|
||||
@@ -997,7 +1014,8 @@ static int sessionTableInfo(
|
||||
int *pnCol, /* OUT: number of columns */
|
||||
const char **pzTab, /* OUT: Copy of zThis */
|
||||
const char ***pazCol, /* OUT: Array of column names for table */
|
||||
u8 **pabPK /* OUT: Array of booleans - true for PK col */
|
||||
u8 **pabPK, /* OUT: Array of booleans - true for PK col */
|
||||
int *pbRowid /* OUT: True if only PK is a rowid */
|
||||
){
|
||||
char *zPragma;
|
||||
sqlite3_stmt *pStmt;
|
||||
@@ -1009,6 +1027,7 @@ static int sessionTableInfo(
|
||||
u8 *pAlloc = 0;
|
||||
char **azCol = 0;
|
||||
u8 *abPK = 0;
|
||||
int bRowid = 0; /* Set to true to use rowid as PK */
|
||||
|
||||
assert( pazCol && pabPK );
|
||||
|
||||
@@ -1053,10 +1072,15 @@ static int sessionTableInfo(
|
||||
}
|
||||
|
||||
nByte = nThis + 1;
|
||||
bRowid = (pbRowid!=0);
|
||||
while( SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
nByte += sqlite3_column_bytes(pStmt, 1);
|
||||
nDbCol++;
|
||||
if( sqlite3_column_int(pStmt, 5) ) bRowid = 0;
|
||||
}
|
||||
if( nDbCol==0 ) bRowid = 0;
|
||||
nDbCol += bRowid;
|
||||
nByte += strlen(SESSIONS_ROWID);
|
||||
rc = sqlite3_reset(pStmt);
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
@@ -1078,6 +1102,14 @@ static int sessionTableInfo(
|
||||
}
|
||||
|
||||
i = 0;
|
||||
if( bRowid ){
|
||||
size_t nName = strlen(SESSIONS_ROWID);
|
||||
memcpy(pAlloc, SESSIONS_ROWID, nName+1);
|
||||
azCol[i] = (char*)pAlloc;
|
||||
pAlloc += nName+1;
|
||||
abPK[i] = 1;
|
||||
i++;
|
||||
}
|
||||
while( SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
int nName = sqlite3_column_bytes(pStmt, 1);
|
||||
const unsigned char *zName = sqlite3_column_text(pStmt, 1);
|
||||
@@ -1089,7 +1121,6 @@ static int sessionTableInfo(
|
||||
i++;
|
||||
}
|
||||
rc = sqlite3_reset(pStmt);
|
||||
|
||||
}
|
||||
|
||||
/* If successful, populate the output variables. Otherwise, zero them and
|
||||
@@ -1106,6 +1137,7 @@ static int sessionTableInfo(
|
||||
if( pzTab ) *pzTab = 0;
|
||||
sessionFree(pSession, azCol);
|
||||
}
|
||||
if( pbRowid ) *pbRowid = bRowid;
|
||||
sqlite3_finalize(pStmt);
|
||||
return rc;
|
||||
}
|
||||
@@ -1127,7 +1159,8 @@ static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){
|
||||
u8 *abPK;
|
||||
assert( pTab->azCol==0 || pTab->abPK==0 );
|
||||
pSession->rc = sessionTableInfo(pSession, pSession->db, pSession->zDb,
|
||||
pTab->zName, &pTab->nCol, 0, &pTab->azCol, &abPK
|
||||
pTab->zName, &pTab->nCol, 0, &pTab->azCol, &abPK,
|
||||
(pSession->bImplicitPK ? &pTab->bRowid : 0)
|
||||
);
|
||||
if( pSession->rc==SQLITE_OK ){
|
||||
int i;
|
||||
@@ -1199,6 +1232,7 @@ static int sessionUpdateMaxSize(
|
||||
){
|
||||
i64 nNew = 2;
|
||||
if( pC->op==SQLITE_INSERT ){
|
||||
if( pTab->bRowid ) nNew += 9;
|
||||
if( op!=SQLITE_DELETE ){
|
||||
int ii;
|
||||
for(ii=0; ii<pTab->nCol; ii++){
|
||||
@@ -1215,12 +1249,16 @@ static int sessionUpdateMaxSize(
|
||||
}else{
|
||||
int ii;
|
||||
u8 *pCsr = pC->aRecord;
|
||||
for(ii=0; ii<pTab->nCol; ii++){
|
||||
if( pTab->bRowid ){
|
||||
nNew += 9 + 1;
|
||||
pCsr += 9;
|
||||
}
|
||||
for(ii=pTab->bRowid; ii<pTab->nCol; ii++){
|
||||
int bChanged = 1;
|
||||
int nOld = 0;
|
||||
int eType;
|
||||
sqlite3_value *p = 0;
|
||||
pSession->hook.xNew(pSession->hook.pCtx, ii, &p);
|
||||
pSession->hook.xNew(pSession->hook.pCtx, ii-pTab->bRowid, &p);
|
||||
if( p==0 ){
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
@@ -1299,6 +1337,7 @@ static int sessionUpdateMaxSize(
|
||||
*/
|
||||
static void sessionPreupdateOneChange(
|
||||
int op, /* One of SQLITE_UPDATE, INSERT, DELETE */
|
||||
i64 iRowid,
|
||||
sqlite3_session *pSession, /* Session object pTab is attached to */
|
||||
SessionTable *pTab /* Table that change applies to */
|
||||
){
|
||||
@@ -1314,7 +1353,7 @@ static void sessionPreupdateOneChange(
|
||||
|
||||
/* Check the number of columns in this xPreUpdate call matches the
|
||||
** number of columns in the table. */
|
||||
if( pTab->nCol!=pSession->hook.xCount(pSession->hook.pCtx) ){
|
||||
if( (pTab->nCol-pTab->bRowid)!=pSession->hook.xCount(pSession->hook.pCtx) ){
|
||||
pSession->rc = SQLITE_SCHEMA;
|
||||
return;
|
||||
}
|
||||
@@ -1347,14 +1386,16 @@ static void sessionPreupdateOneChange(
|
||||
/* Calculate the hash-key for this change. If the primary key of the row
|
||||
** includes a NULL value, exit early. Such changes are ignored by the
|
||||
** session module. */
|
||||
rc = sessionPreupdateHash(pSession, pTab, op==SQLITE_INSERT, &iHash, &bNull);
|
||||
rc = sessionPreupdateHash(
|
||||
pSession, iRowid, pTab, op==SQLITE_INSERT, &iHash, &bNull
|
||||
);
|
||||
if( rc!=SQLITE_OK ) goto error_out;
|
||||
|
||||
if( bNull==0 ){
|
||||
/* Search the hash table for an existing record for this row. */
|
||||
SessionChange *pC;
|
||||
for(pC=pTab->apChange[iHash]; pC; pC=pC->pNext){
|
||||
if( sessionPreupdateEqual(pSession, pTab, pC, op) ) break;
|
||||
if( sessionPreupdateEqual(pSession, iRowid, pTab, pC, op) ) break;
|
||||
}
|
||||
|
||||
if( pC==0 ){
|
||||
@@ -1369,7 +1410,7 @@ static void sessionPreupdateOneChange(
|
||||
|
||||
/* Figure out how large an allocation is required */
|
||||
nByte = sizeof(SessionChange);
|
||||
for(i=0; i<pTab->nCol; i++){
|
||||
for(i=0; i<(pTab->nCol-pTab->bRowid); i++){
|
||||
sqlite3_value *p = 0;
|
||||
if( op!=SQLITE_INSERT ){
|
||||
TESTONLY(int trc = ) pSession->hook.xOld(pSession->hook.pCtx, i, &p);
|
||||
@@ -1384,6 +1425,9 @@ static void sessionPreupdateOneChange(
|
||||
rc = sessionSerializeValue(0, p, &nByte);
|
||||
if( rc!=SQLITE_OK ) goto error_out;
|
||||
}
|
||||
if( pTab->bRowid ){
|
||||
nByte += 9; /* Size of rowid field - an integer */
|
||||
}
|
||||
|
||||
/* Allocate the change object */
|
||||
pC = (SessionChange *)sessionMalloc64(pSession, nByte);
|
||||
@@ -1400,7 +1444,12 @@ static void sessionPreupdateOneChange(
|
||||
** required values and encodings have already been cached in memory.
|
||||
** It is not possible for an OOM to occur in this block. */
|
||||
nByte = 0;
|
||||
for(i=0; i<pTab->nCol; i++){
|
||||
if( pTab->bRowid ){
|
||||
pC->aRecord[0] = SQLITE_INTEGER;
|
||||
sessionPutI64(&pC->aRecord[1], iRowid);
|
||||
nByte = 9;
|
||||
}
|
||||
for(i=0; i<(pTab->nCol-pTab->bRowid); i++){
|
||||
sqlite3_value *p = 0;
|
||||
if( op!=SQLITE_INSERT ){
|
||||
pSession->hook.xOld(pSession->hook.pCtx, i, &p);
|
||||
@@ -1515,9 +1564,10 @@ static void xPreUpdate(
|
||||
pSession->rc = sessionFindTable(pSession, zName, &pTab);
|
||||
if( pTab ){
|
||||
assert( pSession->rc==SQLITE_OK );
|
||||
sessionPreupdateOneChange(op, pSession, pTab);
|
||||
assert( op==SQLITE_UPDATE || iKey1==iKey2 );
|
||||
sessionPreupdateOneChange(op, iKey1, pSession, pTab);
|
||||
if( op==SQLITE_UPDATE ){
|
||||
sessionPreupdateOneChange(SQLITE_INSERT, pSession, pTab);
|
||||
sessionPreupdateOneChange(SQLITE_INSERT, iKey2, pSession, pTab);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1556,6 +1606,7 @@ static void sessionPreupdateHooks(
|
||||
typedef struct SessionDiffCtx SessionDiffCtx;
|
||||
struct SessionDiffCtx {
|
||||
sqlite3_stmt *pStmt;
|
||||
int bRowid;
|
||||
int nOldOff;
|
||||
};
|
||||
|
||||
@@ -1564,17 +1615,17 @@ struct SessionDiffCtx {
|
||||
*/
|
||||
static int sessionDiffOld(void *pCtx, int iVal, sqlite3_value **ppVal){
|
||||
SessionDiffCtx *p = (SessionDiffCtx*)pCtx;
|
||||
*ppVal = sqlite3_column_value(p->pStmt, iVal+p->nOldOff);
|
||||
*ppVal = sqlite3_column_value(p->pStmt, iVal+p->nOldOff+p->bRowid);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
static int sessionDiffNew(void *pCtx, int iVal, sqlite3_value **ppVal){
|
||||
SessionDiffCtx *p = (SessionDiffCtx*)pCtx;
|
||||
*ppVal = sqlite3_column_value(p->pStmt, iVal);
|
||||
*ppVal = sqlite3_column_value(p->pStmt, iVal+p->bRowid);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
static int sessionDiffCount(void *pCtx){
|
||||
SessionDiffCtx *p = (SessionDiffCtx*)pCtx;
|
||||
return p->nOldOff ? p->nOldOff : sqlite3_column_count(p->pStmt);
|
||||
return (p->nOldOff ? p->nOldOff : sqlite3_column_count(p->pStmt)) - p->bRowid;
|
||||
}
|
||||
static int sessionDiffDepth(void *pCtx){
|
||||
(void)pCtx;
|
||||
@@ -1653,14 +1704,16 @@ static char *sessionExprCompareOther(
|
||||
static char *sessionSelectFindNew(
|
||||
const char *zDb1, /* Pick rows in this db only */
|
||||
const char *zDb2, /* But not in this one */
|
||||
int bRowid,
|
||||
const char *zTbl, /* Table name */
|
||||
const char *zExpr
|
||||
){
|
||||
const char *zSel = (bRowid ? SESSIONS_ROWID ", *" : "*");
|
||||
char *zRet = sqlite3_mprintf(
|
||||
"SELECT * FROM \"%w\".\"%w\" WHERE NOT EXISTS ("
|
||||
"SELECT %s FROM \"%w\".\"%w\" WHERE NOT EXISTS ("
|
||||
" SELECT 1 FROM \"%w\".\"%w\" WHERE %s"
|
||||
")",
|
||||
zDb1, zTbl, zDb2, zTbl, zExpr
|
||||
zSel, zDb1, zTbl, zDb2, zTbl, zExpr
|
||||
);
|
||||
return zRet;
|
||||
}
|
||||
@@ -1674,7 +1727,9 @@ static int sessionDiffFindNew(
|
||||
char *zExpr
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
char *zStmt = sessionSelectFindNew(zDb1, zDb2, pTab->zName,zExpr);
|
||||
char *zStmt = sessionSelectFindNew(
|
||||
zDb1, zDb2, pTab->bRowid, pTab->zName, zExpr
|
||||
);
|
||||
|
||||
if( zStmt==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
@@ -1685,8 +1740,10 @@ static int sessionDiffFindNew(
|
||||
SessionDiffCtx *pDiffCtx = (SessionDiffCtx*)pSession->hook.pCtx;
|
||||
pDiffCtx->pStmt = pStmt;
|
||||
pDiffCtx->nOldOff = 0;
|
||||
pDiffCtx->bRowid = pTab->bRowid;
|
||||
while( SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
sessionPreupdateOneChange(op, pSession, pTab);
|
||||
i64 iRowid = (pTab->bRowid ? sqlite3_column_int64(pStmt, 0) : 0);
|
||||
sessionPreupdateOneChange(op, iRowid, pSession, pTab);
|
||||
}
|
||||
rc = sqlite3_finalize(pStmt);
|
||||
}
|
||||
@@ -1696,6 +1753,27 @@ static int sessionDiffFindNew(
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a comma-separated list of the fully-qualified (with both database
|
||||
** and table name) column names from table pTab. e.g.
|
||||
**
|
||||
** "main"."t1"."a", "main"."t1"."b", "main"."t1"."c"
|
||||
*/
|
||||
static char *sessionAllCols(
|
||||
const char *zDb,
|
||||
SessionTable *pTab
|
||||
){
|
||||
int ii;
|
||||
char *zRet = 0;
|
||||
for(ii=0; ii<pTab->nCol; ii++){
|
||||
zRet = sqlite3_mprintf("%z%s\"%w\".\"%w\".\"%w\"",
|
||||
zRet, (zRet ? ", " : ""), zDb, pTab->zName, pTab->azCol[ii]
|
||||
);
|
||||
if( !zRet ) break;
|
||||
}
|
||||
return zRet;
|
||||
}
|
||||
|
||||
static int sessionDiffFindModified(
|
||||
sqlite3_session *pSession,
|
||||
SessionTable *pTab,
|
||||
@@ -1710,11 +1788,13 @@ static int sessionDiffFindModified(
|
||||
if( zExpr2==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
char *z1 = sessionAllCols(pSession->zDb, pTab);
|
||||
char *z2 = sessionAllCols(zFrom, pTab);
|
||||
char *zStmt = sqlite3_mprintf(
|
||||
"SELECT * FROM \"%w\".\"%w\", \"%w\".\"%w\" WHERE %s AND (%z)",
|
||||
pSession->zDb, pTab->zName, zFrom, pTab->zName, zExpr, zExpr2
|
||||
"SELECT %s,%s FROM \"%w\".\"%w\", \"%w\".\"%w\" WHERE %s AND (%z)",
|
||||
z1, z2, pSession->zDb, pTab->zName, zFrom, pTab->zName, zExpr, zExpr2
|
||||
);
|
||||
if( zStmt==0 ){
|
||||
if( zStmt==0 || z1==0 || z2==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
sqlite3_stmt *pStmt;
|
||||
@@ -1725,12 +1805,15 @@ static int sessionDiffFindModified(
|
||||
pDiffCtx->pStmt = pStmt;
|
||||
pDiffCtx->nOldOff = pTab->nCol;
|
||||
while( SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
sessionPreupdateOneChange(SQLITE_UPDATE, pSession, pTab);
|
||||
i64 iRowid = (pTab->bRowid ? sqlite3_column_int64(pStmt, 0) : 0);
|
||||
sessionPreupdateOneChange(SQLITE_UPDATE, iRowid, pSession, pTab);
|
||||
}
|
||||
rc = sqlite3_finalize(pStmt);
|
||||
}
|
||||
sqlite3_free(zStmt);
|
||||
}
|
||||
sqlite3_free(zStmt);
|
||||
sqlite3_free(z1);
|
||||
sqlite3_free(z2);
|
||||
}
|
||||
|
||||
return rc;
|
||||
@@ -1769,9 +1852,12 @@ int sqlite3session_diff(
|
||||
int bHasPk = 0;
|
||||
int bMismatch = 0;
|
||||
int nCol; /* Columns in zFrom.zTbl */
|
||||
int bRowid = 0;
|
||||
u8 *abPK;
|
||||
const char **azCol = 0;
|
||||
rc = sessionTableInfo(0, db, zFrom, zTbl, &nCol, 0, &azCol, &abPK);
|
||||
rc = sessionTableInfo(0, db, zFrom, zTbl, &nCol, 0, &azCol, &abPK,
|
||||
pSession->bImplicitPK ? &bRowid : 0
|
||||
);
|
||||
if( rc==SQLITE_OK ){
|
||||
if( pTo->nCol!=nCol ){
|
||||
bMismatch = 1;
|
||||
@@ -2320,7 +2406,7 @@ static int sessionAppendUpdate(
|
||||
/* If at least one field has been modified, this is not a no-op. */
|
||||
if( bChanged ) bNoop = 0;
|
||||
|
||||
/* Add a field to the old.* record. This is omitted if this modules is
|
||||
/* Add a field to the old.* record. This is omitted if this module is
|
||||
** currently generating a patchset. */
|
||||
if( bPatchset==0 ){
|
||||
if( bChanged || abPK[i] ){
|
||||
@@ -2422,6 +2508,7 @@ static int sessionSelectStmt(
|
||||
int bIgnoreNoop,
|
||||
const char *zDb, /* Database name */
|
||||
const char *zTab, /* Table name */
|
||||
int bRowid,
|
||||
int nCol, /* Number of columns in table */
|
||||
const char **azCol, /* Names of table columns */
|
||||
u8 *abPK, /* PRIMARY KEY array */
|
||||
@@ -2430,7 +2517,7 @@ static int sessionSelectStmt(
|
||||
int rc = SQLITE_OK;
|
||||
char *zSql = 0;
|
||||
const char *zSep = "";
|
||||
const char *zCols = "*";
|
||||
const char *zCols = bRowid ? SESSIONS_ROWID ", *" : "*";
|
||||
int nSql = -1;
|
||||
int i;
|
||||
|
||||
@@ -2449,7 +2536,6 @@ static int sessionSelectStmt(
|
||||
zCols = "tbl, ?2, stat";
|
||||
}else{
|
||||
for(i=0; i<nCol; i++){
|
||||
|
||||
if( abPK[i] ){
|
||||
sessionAppendStr(&pkfield, zSep, &rc);
|
||||
sessionAppendStr(&pkvar, zSep, &rc);
|
||||
@@ -2656,10 +2742,18 @@ static int sessionGenerateChangeset(
|
||||
sqlite3_stmt *pSel = 0; /* SELECT statement to query table pTab */
|
||||
int nRewind = buf.nBuf; /* Initial size of write buffer */
|
||||
int nNoop; /* Size of buffer after writing tbl header */
|
||||
int bRowid = 0;
|
||||
|
||||
/* Check the table schema is still Ok. */
|
||||
rc = sessionTableInfo(0, db, pSession->zDb, zName, &nCol, 0,&azCol,&abPK);
|
||||
if( !rc && (pTab->nCol!=nCol || memcmp(abPK, pTab->abPK, nCol)) ){
|
||||
rc = sessionTableInfo(
|
||||
0, db, pSession->zDb, zName, &nCol, 0, &azCol, &abPK,
|
||||
(pSession->bImplicitPK ? &bRowid : 0)
|
||||
);
|
||||
if( rc==SQLITE_OK && (
|
||||
pTab->nCol!=nCol
|
||||
|| pTab->bRowid!=bRowid
|
||||
|| memcmp(abPK, pTab->abPK, nCol)
|
||||
)){
|
||||
rc = SQLITE_SCHEMA;
|
||||
}
|
||||
|
||||
@@ -2669,7 +2763,7 @@ static int sessionGenerateChangeset(
|
||||
/* Build and compile a statement to execute: */
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sessionSelectStmt(
|
||||
db, 0, pSession->zDb, zName, nCol, azCol, abPK, &pSel
|
||||
db, 0, pSession->zDb, zName, bRowid, nCol, azCol, abPK, &pSel
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2753,7 +2847,7 @@ int sqlite3session_changeset(
|
||||
int rc;
|
||||
|
||||
if( pnChangeset==0 || ppChangeset==0 ) return SQLITE_MISUSE;
|
||||
rc = sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset,ppChangeset);
|
||||
rc = sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset, ppChangeset);
|
||||
assert( rc || pnChangeset==0
|
||||
|| pSession->bEnableSize==0 || *pnChangeset<=pSession->nMaxChangesetSize
|
||||
);
|
||||
@@ -2871,6 +2965,19 @@ int sqlite3session_object_config(sqlite3_session *pSession, int op, void *pArg){
|
||||
break;
|
||||
}
|
||||
|
||||
case SQLITE_SESSION_OBJCONFIG_ROWID: {
|
||||
int iArg = *(int*)pArg;
|
||||
if( iArg>=0 ){
|
||||
if( pSession->pTable ){
|
||||
rc = SQLITE_MISUSE;
|
||||
}else{
|
||||
pSession->bImplicitPK = (iArg!=0);
|
||||
}
|
||||
}
|
||||
*(int*)pArg = pSession->bImplicitPK;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
rc = SQLITE_MISUSE;
|
||||
}
|
||||
@@ -3129,15 +3236,19 @@ static int sessionReadRecord(
|
||||
}
|
||||
}
|
||||
if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
|
||||
sqlite3_int64 v = sessionGetI64(aVal);
|
||||
if( eType==SQLITE_INTEGER ){
|
||||
sqlite3VdbeMemSetInt64(apOut[i], v);
|
||||
if( (pIn->nData-pIn->iNext)<8 ){
|
||||
rc = SQLITE_CORRUPT_BKPT;
|
||||
}else{
|
||||
double d;
|
||||
memcpy(&d, &v, 8);
|
||||
sqlite3VdbeMemSetDouble(apOut[i], d);
|
||||
sqlite3_int64 v = sessionGetI64(aVal);
|
||||
if( eType==SQLITE_INTEGER ){
|
||||
sqlite3VdbeMemSetInt64(apOut[i], v);
|
||||
}else{
|
||||
double d;
|
||||
memcpy(&d, &v, 8);
|
||||
sqlite3VdbeMemSetDouble(apOut[i], d);
|
||||
}
|
||||
pIn->iNext += 8;
|
||||
}
|
||||
pIn->iNext += 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3860,6 +3971,7 @@ struct SessionApplyCtx {
|
||||
u8 bRebaseStarted; /* If table header is already in rebase */
|
||||
u8 bRebase; /* True to collect rebase information */
|
||||
u8 bIgnoreNoop; /* True to ignore no-op conflicts */
|
||||
int bRowid;
|
||||
};
|
||||
|
||||
/* Number of prepared UPDATE statements to cache. */
|
||||
@@ -4110,8 +4222,9 @@ static int sessionSelectRow(
|
||||
const char *zTab, /* Table name */
|
||||
SessionApplyCtx *p /* Session changeset-apply context */
|
||||
){
|
||||
/* TODO */
|
||||
return sessionSelectStmt(db, p->bIgnoreNoop,
|
||||
"main", zTab, p->nCol, p->azCol, p->abPK, &p->pSelect
|
||||
"main", zTab, p->bRowid, p->nCol, p->azCol, p->abPK, &p->pSelect
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4807,6 +4920,7 @@ static int sessionChangesetApply(
|
||||
sApply.bStat1 = 0;
|
||||
sApply.bDeferConstraints = 1;
|
||||
sApply.bRebaseStarted = 0;
|
||||
sApply.bRowid = 0;
|
||||
memset(&sApply.constraints, 0, sizeof(SessionBuffer));
|
||||
|
||||
/* If an xFilter() callback was specified, invoke it now. If the
|
||||
@@ -4826,8 +4940,8 @@ static int sessionChangesetApply(
|
||||
int i;
|
||||
|
||||
sqlite3changeset_pk(pIter, &abPK, 0);
|
||||
rc = sessionTableInfo(0,
|
||||
db, "main", zNew, &sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK
|
||||
rc = sessionTableInfo(0, db, "main", zNew,
|
||||
&sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK, &sApply.bRowid
|
||||
);
|
||||
if( rc!=SQLITE_OK ) break;
|
||||
for(i=0; i<sApply.nCol; i++){
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user