├── .github └── workflows │ ├── linux.yml │ └── windows.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── build.sh ├── clean.sh ├── io_protocol.md ├── logo-200.png └── src ├── conn.c ├── conn.h ├── conn_test.c ├── conn_test.h ├── handler.c ├── handler.h ├── handler_test.c ├── handler_test.h ├── loop.c ├── loop.h ├── main.c ├── mem.c ├── mem.h ├── mem_test.c ├── mem_test.h ├── sqlite3.c ├── sqlite3.h ├── util.c └── util.h /.github/workflows/linux.yml: -------------------------------------------------------------------------------- 1 | name: Linux 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | 14 | - name: Build 15 | run: | 16 | echo "build start" 17 | pwd 18 | ls -al 19 | chmod a+x clean.sh build.sh 20 | ./clean.sh 21 | ./build.sh 22 | echo "executing test" 23 | bin/sqinn test 24 | echo "build done" 25 | 26 | - name: Upload Artifact 27 | uses: actions/upload-artifact@v4 28 | with: 29 | name: dist-linux 30 | path: dist 31 | 32 | -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: Windows 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | 7 | jobs: 8 | build: 9 | runs-on: windows-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | 14 | - name: Setup MSVC 15 | uses: ilammy/msvc-dev-cmd@v1 16 | 17 | - name: Compile 18 | run: | 19 | echo "build start" 20 | dir 21 | cl /Fe:sqinn.exe src\*.c 22 | dir 23 | echo "executing test" 24 | .\sqinn.exe test 25 | md dist 26 | copy sqinn.exe dist 27 | echo "build done" 28 | 29 | - name: Upload Artifact 30 | uses: actions/upload-artifact@v4 31 | with: 32 | name: dist-windows 33 | path: dist 34 | 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.db 3 | *.o 4 | bin/ 5 | dist/ 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "stdlib.h": "c" 4 | } 5 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![Sqinn](logo-200.png "Sqinn") 3 | 4 | 5 | > [!NOTE] 6 | > This work is sponsored by Monibot - Website, Server and Application Monitoring. 7 | > Try out Monibot at [https://monibot.io](https://monibot.io?ref=sqinn). 8 | 9 | 10 | Sqinn is an alternative to the SQLite C API. Sqinn reads requests from stdin, 11 | forwards the request to SQLite, and writes a response to stdout. It is used in 12 | programming environments that do not allow calling C API functions. 13 | 14 | The [SQLite database library](https://www.sqlite.org) is written in C and 15 | provides an API for using it in C/C++. There are many language bindings. If you 16 | cannot or do not want to use one of the available language bindings, and your 17 | programming language allows the creation of subprocesses (fork/exec), an option 18 | might be to communicate with SQLite over stdin/stdout, using Sqinn. 19 | 20 | One example is [Go](https://golang.org): There exist a bunch of Go libraries 21 | for reading and writing SQLite databases. Most of them use `cgo` to call the 22 | SQLite C API functions. While this works (very well, indeed), it has drawbacks: 23 | First, you have to have gcc installed on your development system. Second, cgo 24 | slows down the Go compilation process. Third, cross compiling a cgo program to 25 | another platform (say from Linux to MacOS) is hard to setup. 26 | 27 | Sqinn provides functions that map to SQLite functions, like `sqlite3_open()`, 28 | `sqlite3_prepare()`, `sqlite3_bind()`, and so on. If you have not read the 29 | [Introduction to the SQLite C/C++ 30 | Interface](https://www.sqlite.org/cintro.html), now it's a good time. It's a 31 | 5-minute read and shows the basic workings of SQLite. 32 | 33 | Marshalling requests and responses back and forth between process boundaries 34 | is, of course, slow. To improve performance, Sqinn provides functions that lets 35 | you call multiple SQLite functions in one request/response cycle. 36 | 37 | All function calls and the binary IO protocol used for marshalling request and 38 | response data is described in [io\_protocol.md](io_protocol.md). 39 | 40 | For the Go (Golang) language binding, see 41 | , for benchmarks, see 42 | . 43 | 44 | 45 | Compiling with gcc 46 | ------------------------------------------------------------------------------- 47 | 48 | See the included `build.sh` script for compiling Sqinn with `gcc`. I have 49 | tested it on the following platforms: 50 | 51 | - Windows 10 amd64, using Mingw64 gcc (tdm64-gcc-9.2.0 from https://jmeubank.github.io/tdm-gcc/) 52 | - Windows 10 amd64, using MSVC 53 | - Debian Linux 11/12 amd64 54 | 55 | The releases page contains a tar file with pre-built binaries for Windows amd64 56 | and Linux amd64, see . 57 | 58 | If you want to compile Sqinn, have gcc installed and follow the steps: 59 | 60 | $ git clone https://github.com/cvilsmeier/sqinn 61 | $ cd sqinn 62 | $ chmod a+x build.sh clean.sh 63 | $ ./build.sh 64 | 65 | The build script creates a `bin` subdirectory that the build results go into. 66 | Test it with: 67 | 68 | $ bin/sqinn test 69 | 70 | 71 | Command line usage 72 | ------------------------------------------------------------------------------- 73 | 74 | There isn't really one. Sqinn is not used by humans, it's used by other 75 | programs. That said: 76 | 77 | $ sqinn help 78 | Sqinn is SQLite over stdin/stdout 79 | 80 | Usage: 81 | 82 | sqinn [options...] [command] 83 | 84 | Commands are: 85 | 86 | help show this help page 87 | version print Sqinn version 88 | sqlite_version print SQLite library version 89 | test execute built-in unit tests 90 | bench execute built-in benchmarks 91 | 92 | Options are: 93 | 94 | -db db file, used for test and bench 95 | commands. Default is ":memory:" 96 | 97 | When invoked without a command, Sqinn will read (await) requests 98 | from stdin, print responses to stdout and output error messages 99 | on stderr. 100 | 101 | 102 | Limitations 103 | ------------------------------------------------------------------------------- 104 | 105 | ### Single threaded 106 | 107 | Sqinn is single threaded. It serves requests one after another. 108 | 109 | 110 | ### API subset 111 | 112 | Sqinn supports only a subset of the many functions that the SQLite C/C++ API 113 | provides. Interruption of SQL operations, backup functions, vfs and 114 | extension functions are not supported, among others. 115 | 116 | 117 | ### Single statement 118 | 119 | As of now, Sqinn does not support multiple active statements at a 120 | time. If a caller tries to prepare a statement while another one is still 121 | active (i.e. un-finalized), Sqinn will bark. 122 | 123 | 124 | Changelog 125 | ------------------------------------------------------------------------------- 126 | 127 | See version in src/util.h 128 | 129 | ### v1.1.42 130 | 131 | - SQLite Version 3.49.2 (2025-05-07) 132 | 133 | ### v1.1.41 134 | 135 | - SQLite Version 3.49.1 (2025-02-18) 136 | 137 | ### v1.1.40 138 | 139 | - SQLite Version 3.49.0 (2025-02-06) 140 | 141 | ### v1.1.39 142 | 143 | - SQLite Version 3.48.0 (2025-01-14) 144 | 145 | ### v1.1.38 146 | 147 | - SQLite Version 3.47.2 (2024-12-07) 148 | 149 | ### v1.1.37 150 | 151 | - SQLite Version 3.47.1 (2024-11-25) 152 | 153 | ### v1.1.36 154 | 155 | - SQLite Version 3.47.0 (2024-10-21) 156 | 157 | ### v1.1.35 158 | 159 | - SQLite Version 3.46.1 (2024-08-13) 160 | 161 | ### v1.1.34 162 | 163 | - SQLite Version 3.46.0 (2024-05-23) 164 | 165 | ### v1.1.33 166 | 167 | - SQLite Version 3.45.3 (2024-04-15) 168 | 169 | ### v1.1.32 170 | 171 | - SQLite Version 3.45.2 (2024-03-12) 172 | 173 | ### v1.1.31 174 | 175 | - SQLite Version 3.45.0 (2024-01-15) 176 | 177 | ### v1.1.30 178 | 179 | - SQLite Version 3.44.2 (2023-11-24) 180 | 181 | ### v1.1.29 182 | 183 | - SQLite Version 3.44.0 (2023-11-01) 184 | 185 | ### v1.1.28 186 | 187 | - SQLite Version 3.43.2 (2023-10-10) 188 | 189 | ### v1.1.27 190 | 191 | - SQLite Version 3.43.1 (2023-09-11) 192 | 193 | ### v1.1.26 194 | 195 | - SQLite Version 3.43.0 (2023-08-24) 196 | 197 | ### v1.1.25 198 | 199 | - SQLite Version 3.42.0 (2023-05-16) 200 | 201 | ### v1.1.24 202 | 203 | - SQLite Version 3.41.2 (2023-03-22) 204 | 205 | ### v1.1.23 206 | 207 | - SQLite Version 3.41.1 (2023-03-10) 208 | 209 | ### v1.1.22 210 | 211 | - SQLite Version 3.41.0 (2023-02-21) 212 | 213 | ### v1.1.21 214 | 215 | - SQLite Version 3.40.1 (2022-12-28) 216 | 217 | ### v1.1.20 218 | 219 | - SQLite Version 3.40.0 (2022-11-16) 220 | 221 | ### v1.1.19 222 | 223 | - SQLite Version 3.39.4 (2022-09-29) 224 | 225 | ### v1.1.18 226 | 227 | - SQLite Version 3.39.2 (2022-07-21) 228 | 229 | ### v1.1.17 230 | 231 | - SQLite Version 3.39.0 (2022-06-25) 232 | 233 | ### v1.1.16 234 | 235 | - SQLite Version 3.38.5 (2022-05-06) 236 | 237 | ### v1.1.15 238 | 239 | - SQLite Version 3.38.3 (2022-04-27) 240 | 241 | ### v1.1.14 242 | 243 | - SQLite Version 3.38.2 (2022-03-26) 244 | 245 | ### v1.1.13 246 | 247 | - SQLite Version 3.38.1 (2022-03-12) 248 | 249 | ### v1.1.12 250 | 251 | - SQLite Version 3.38.0 (2022-02-22) 252 | 253 | ### v1.1.11 254 | 255 | - SQLite Version 3.37.2 (2022-01-06) 256 | 257 | ### v1.1.10 258 | 259 | - SQLite Version 3.37.0 260 | 261 | ### v1.1.9 (2021-06-29) 262 | 263 | - sqlite 3.36.0 (2021-06-18) 264 | 265 | ### v1.1.8 (2021-04-23) 266 | 267 | - sqlite 3.35.5 (2021-04-19) 268 | 269 | ### v1.1.7 (2021-04-03) 270 | 271 | - sqlite 3.35.4 (2021-04-02) 272 | 273 | ### v1.1.6 (2021-03-20) 274 | 275 | - fix version tests 276 | 277 | ### v1.1.5 (2021-03-18) 278 | 279 | - sqlite 3.35.2 (2021-03-17) 280 | 281 | ### v1.1.4 (2021-01-29) 282 | 283 | - fixed version number 284 | 285 | ### v1.1.3 (2021-01-25) 286 | 287 | - sqlite v3.34.1 (2021-01-20) 288 | 289 | ### v1.1.2 (2020-11-06) 290 | 291 | - sqlite v3.33.0 292 | 293 | ### v1.1.1 (2020-06-22) 294 | 295 | - fix unsuccessful FC_EXEC/QUERY leaked prepared statement 296 | - sqlite v3.32.3 297 | 298 | ### v1.1.0 (2020-06-14) 299 | 300 | - fast IEEE 745 encoding for double values 301 | 302 | ### v1.0.0 (2020-06-10) 303 | 304 | - first version 305 | 306 | 307 | Contributing 308 | ------------------------------------------------------------------------------- 309 | 310 | I will reject most PRs and feature requests. Why? Because I use sqinn for my 311 | own projects, and I want it to be fast, secure, robust and easy to maintain. 312 | I cannot include every feature under the sun. 313 | I give it away for free, so everybody can adjust it to his or her own needs. 314 | 315 | 316 | License 317 | ------------------------------------------------------------------------------- 318 | 319 | This is free and unencumbered software released into the public domain. 320 | 321 | Anyone is free to copy, modify, publish, use, compile, sell, or 322 | distribute this software, either in source code form or as a compiled 323 | binary, for any purpose, commercial or non-commercial, and by any 324 | means. 325 | 326 | In jurisdictions that recognize copyright laws, the author or authors 327 | of this software dedicate any and all copyright interest in the 328 | software to the public domain. We make this dedication for the benefit 329 | of the public at large and to the detriment of our heirs and 330 | successors. We intend this dedication to be an overt act of 331 | relinquishment in perpetuity of all present and future rights to this 332 | software under copyright law. 333 | 334 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 335 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 336 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 337 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 338 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 339 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 340 | OTHER DEALINGS IN THE SOFTWARE. 341 | 342 | For more information, please refer to 343 | 344 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # fail fast 4 | set -e 5 | 6 | # must be in 7 | if [ ! -f "src/main.c" ]; then 8 | echo src/main.c not found - cannot build 9 | exit 1 10 | fi 11 | 12 | mkdir -p bin 13 | 14 | if [ ! -f "bin/sqlite3.o" ]; then 15 | echo build sqlite3.o 16 | gcc -std=c99 -O1 -o bin/sqlite3.o -c src/sqlite3.c -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION 17 | fi 18 | 19 | FLAGS="-std=c99 -Wall -O1" 20 | case `uname -s` in 21 | *Linux*) 22 | FLAGS="$FLAGS -static" 23 | ;; 24 | *MINGW*) 25 | FLAGS="$FLAGS -static" 26 | ;; 27 | esac 28 | 29 | echo "build sqinn using FLAGS $FLAGS" 30 | gcc $FLAGS -o bin/sqinn \ 31 | src/util.c \ 32 | src/mem.c \ 33 | src/mem_test.c \ 34 | src/conn.c \ 35 | src/conn_test.c \ 36 | src/handler.c \ 37 | src/handler_test.c \ 38 | src/loop.c \ 39 | src/main.c \ 40 | bin/sqlite3.o 41 | 42 | mkdir -p dist 43 | 44 | cp bin/sqinn dist/ 45 | 46 | -------------------------------------------------------------------------------- /clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -rf bin/ 4 | rm -rf dist/ 5 | rm -f *.db 6 | rm -f *.exe 7 | -------------------------------------------------------------------------------- /io_protocol.md: -------------------------------------------------------------------------------- 1 | 2 | IO PROTOCOL 3 | =============================================================================== 4 | 5 | Sqinn reads a request telegram from stdin, processes the request, outputs a 6 | response telegram to stdout, and waits for the next request telegram. A 7 | telegram consists of a 4-byte size N, followed by a payload of N bytes. The 8 | payload is either a request or a response. If N is zero, or a read error occurs 9 | while reading the 4-byte size N or the payload, sqinn terminates. 10 | 11 | A requests starts with a one-byte function code, followed by a variable number 12 | of argument values. The function code tells sqinn which sqlite function to 13 | execute. Function Codes are described in section "Function Codes". Data types 14 | used to encode the argumen values are described in section "Data Types". 15 | 16 | A response starts with a byte that is nonzero (true) if the function was 17 | successful and zero (false) if not. If the function was successful, a variable 18 | number of return values follows. If the function was not successful, a error 19 | message (string value) follows, describing the error that occurred. 20 | 21 | Example: 22 | 23 | For opening a sqlite database file, function code 1 (FC_OPEN) is used. The 24 | single argument to FC_OPEN is the filename. A sample request looks like this: 25 | 26 | [ 0] 0x0A FC_OPEN 27 | [ 1] 0x00 4-byte size of filename plus terminating null (8 in this case) 28 | [ 2] 0x00 29 | [ 3] 0x00 30 | [ 4] 0x08 31 | [ 5] 0x74 7 bytes filename "test.db" 32 | [ 6] 0x65 33 | [ 7] 0x73 34 | [ 8] 0x74 35 | [ 9] 0x2e 36 | [10] 0x64 37 | [11] 0x62 38 | [12] 0x00 1 null byte 39 | 40 | Byte 0 is set to 10, which is the numerical code for FC_OPEN. Bytes 1 to 12 41 | contain the only argument to FC_OPEN, which is the filename to be opened. 42 | 43 | For sending the request to Sqinn's stdin, the request has to be preceded by its 44 | 4-byte size N, so that Sqinn knows how many bytes it must read before it can 45 | start processing the request. In our example N is decimal 13 (0x0C), so 46 | the complete telegram will look like this: 47 | 48 | [ 0] 0x00 4-byte size of subsequent request payload (13 in this case) 49 | [ 1] 0x00 50 | [ 2] 0x00 51 | [ 3] 0x0C 52 | [ 4] 0x0A FC_OPEN 53 | [ 5] 0x00 4-byte size of filename plus terminating null (8 in this case) 54 | [ 6] 0x00 55 | [ 7] 0x00 56 | [ 8] 0x08 57 | [ 9] 0x74 7 bytes filename "test.db" 58 | [10] 0x65 59 | [11] 0x73 60 | [12] 0x74 61 | [13] 0x2e 62 | [14] 0x64 63 | [15] 0x62 64 | [16] 0x00 1 null byte 65 | 66 | If the file could be opened, sqinn will output the following response payload: 67 | 68 | [0] 0x01 bool 1 "ok" 69 | 70 | If a error occurred, sqinn will output an error: 71 | 72 | [ 0] 0x00 bool 0 "not ok" 73 | [ 1] 0x00 4 bytes size of error message plus terminating null byte (10 i this case) 74 | [ 2] 0x00 75 | [ 3] 0x00 76 | [ 4] 0x0A 77 | [ 5] 'n' 9 bytes error message "not found" 78 | [ 5] 'o' 79 | [ 6] 't' 80 | [ 7] ' ' 81 | [ 8] 'f' 82 | [ 9] 'o' 83 | [10] 'u' 84 | [11] 'n' 85 | [12] 'd' 86 | [13] 0x00 1 null byte 87 | 88 | Before writing the reponse to stdout, Sqinn will write the 4-byte size N of the 89 | response, so that consumers of Sqinn's stdout know how many bytes to read to 90 | have a complete response. 91 | 92 | In the following sections, we describe the data types used to encode and 93 | decode function arguments and response values. Then we describe the function 94 | codes provided by sqinn. 95 | 96 | 97 | Data Types 98 | ------------------------------------------------------------------------------ 99 | 100 | ### byte 101 | 102 | A byte is encoded as itself (a single byte). 103 | 104 | Examples: 105 | 106 | (byte)1 [0x01] 107 | 108 | 109 | ### bool 110 | 111 | A bool is encoded as a single byte. A value of zero means false, a 112 | non-zero value means true. 113 | 114 | Examples: 115 | 116 | (bool)1 [0x01] 117 | (bool)0 [0x00] 118 | 119 | 120 | ### int32 121 | 122 | A int32 is a 32-bit (4-byte) signed integer value. It is encoded as 4 123 | bytes, MSB first (big endian). 124 | 125 | Examples: 126 | 127 | (int32)1 [0x00, 0x00, 0x00, 0x01] 128 | (int32)2 [0x00, 0x00, 0x00, 0x02] 129 | (int32)128 [0x00, 0x00, 0x00, 0x80] 130 | (int32)256 [0x00, 0x00, 0x01, 0x00] 131 | (int32)-1 [0xFF, 0xFF, 0xFF, 0xFF] 132 | 133 | 134 | ### int64 135 | 136 | A int64 is a 64-bit (8-byte) signed integer value. It is encoded as 8 137 | bytes, MSB first (big endian). 138 | 139 | Examples: 140 | 141 | (int64)1 [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01] 142 | (int64)-1 [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] 143 | 144 | 145 | ### string 146 | 147 | A string is a size-prefixed C-type UTF-8 string. The size is calculated as 148 | the number of bytes (not characters) plus the terminating null byte. The 149 | empty string has a size of 1, counting only the terminating null byte. The 150 | size is encoded as a int32, then follow the content bytes, including 151 | the terminating null byte. The maximum string size is 2,147,483,647 152 | (2^31-1) bytes. 153 | 154 | Examples: 155 | 156 | (string)"" [0x00, 0x00, 0x00, 0x01, 0x00] 157 | (string)"A" [0x00, 0x00, 0x00, 0x02, 0x41, 0x00] 158 | (string)"123" [0x00, 0x00, 0x00, 0x04, 0x31, 0x32, 0x33, 0x00] 159 | 160 | 161 | ### double_ieee 162 | 163 | A double_ieee is a 64-bit floating point, ecoded as a IEEE 745 binary64 8-byte 164 | sequence. 165 | 166 | Examples: 167 | 168 | (double)2 [0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] 169 | (double)-2 [0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] 170 | 171 | 172 | ### double_str 173 | 174 | A double_str is a 64-bit floating point. Since not all programming 175 | environments understand the IEEE-745 format, sqinn encodes a double_str value as a 176 | string. The string encoding rules above apply for double values also. 177 | 178 | Examples: 179 | 180 | (double)1.2 [0x00, 0x00, 0x00, 0x04, 0x31, 0x2E, 0x32, 0x00] 181 | 182 | 183 | ### blob 184 | 185 | A blob is a size-prefixed array of bytes. The size is encoded as a int32, 186 | then follow the content bytes. The maximum blob size is 2,147,483,647 187 | (2^31-1) bytes. Blobs may, other than strings, contain null bytes. 188 | 189 | Examples: 190 | 191 | (blob)[0x61, 0x00, 0x10] [0x00, 0x00, 0x00, 0x03, 0x61, 0x00, 0x10] 192 | (blob)[] [0x00, 0x00, 0x00, 0x00] 193 | 194 | 195 | Functions 196 | ------------------------------------------------------------------------------ 197 | 198 | This section lists all functions understood by sqinn, with arguments and 199 | return values. The numerical values of all function codes and value types are 200 | enumerated in `src/handler.h`. For a better understanding of the provided 201 | functions, we recommend reading SQLite's Introduction to its C/C++ interface: 202 | [https://www.sqlite.org/cintro.html](https://www.sqlite.org/cintro.html) 203 | 204 | The first byte of each request is a function code. The rest of the request 205 | encodes the function arguments, if any. 206 | 207 | A response starts with a bool that indicates success (true) or failure (false). 208 | For success, the rest of the response encodes the result values, if any. For 209 | failure, the rest of the response is a string that holds an error message. 210 | Since error reponses all look the same, we leave them out in the following 211 | function descriptions. 212 | 213 | 214 | ### FC_SQINN_VERSION 215 | 216 | This function returns the version of sqinn as a string, e.g. "1.0.0". 217 | 218 | Request: 219 | 220 | byte FC_SQINN_VERSION 221 | 222 | Response (Success): 223 | 224 | bool true true means "success" 225 | string version_string e.g. "1.0.0" 226 | 227 | 228 | ### FC_IO_VERSION 229 | 230 | This function returns the current IO protocol version as a byte. The version is 231 | currently always 1 but may change in the future. 232 | 233 | Request: 234 | 235 | byte FC_IO_VERSION 236 | 237 | Response (Success): 238 | 239 | bool true true means "success" 240 | byte version currently always 1 241 | 242 | 243 | ### FC_SQLITE_VERSION 244 | 245 | This function returns the version of the sqlite library as a string, e.g. 246 | "3.32.1". 247 | 248 | Request: 249 | 250 | byte FC_SQLITE_VERSION 251 | 252 | Response (Success): 253 | 254 | bool true 255 | string version_string e.g. "3.32.1" 256 | 257 | 258 | ### FC_OPEN 259 | 260 | This function opens a database. 261 | 262 | Request: 263 | 264 | byte FC_OPEN 265 | string filename e.g. "/home/alice/my.db" or ":memory:" 266 | 267 | Response (Success): 268 | 269 | bool true 270 | 271 | 272 | ### FC_PREPARE 273 | 274 | This function prepares a statement. 275 | 276 | Request: 277 | 278 | byte FC_PREPARE 279 | string sql e.g. "SELECT id,name FROM users" 280 | 281 | Response (Success): 282 | 283 | bool true 284 | 285 | 286 | ### FC_BIND 287 | 288 | This function binds parameter values. 289 | 290 | Request: 291 | 292 | byte FC_BIND 293 | int32 iparam The parameter number (1,2,...) 294 | byte value_type VAL_NULL, VAL_INT, VAL_STRING, ... 295 | any value any can be an int32, int64, double, 296 | string or blob value, depending on the 297 | given value_type. If value_type is 298 | VAL_NULL, value is omitted. 299 | 300 | Response (Success): 301 | 302 | bool true 303 | 304 | 305 | ### FC_STEP 306 | 307 | This advances a prepared statement to its next row or to completion. 308 | 309 | Request: 310 | 311 | byte FC_STEP 312 | int32 iparam The parameter number (1,2,...) 313 | byte value_type VAL_NULL, VAL_INT, VAL_STRING, ... 314 | any value any can be an int32, int64, double, 315 | string or blob value, depending on the 316 | given value_type. If value_type is 317 | VAL_NULL, value is omitted. 318 | 319 | Response (Success): 320 | 321 | bool true 322 | bool more true if another row is available. In 323 | that case, column values can be 324 | fetched with FC_COLUMN. 325 | 326 | 327 | ### FC_RESET 328 | 329 | Reset a prepared statement before reusing it. 330 | 331 | Request: 332 | 333 | byte FC_RESET 334 | 335 | Response (Success): 336 | 337 | bool true 338 | 339 | 340 | ### FC_CHANGES 341 | 342 | Count the number of rows modified by the last executed statement. 343 | 344 | Request: 345 | 346 | byte FC_CHANGES 347 | 348 | Response (Success): 349 | 350 | bool true 351 | int32 count the number of rows modified 352 | 353 | 354 | ### FC_COLUMN 355 | 356 | Retrieve a column value. When retrieving a column, the caller has to specify 357 | the type of the value to retrieve. If the value type matches the column type, 358 | no type conversion is done. Otherwise, sqlite will convert the column value to 359 | the specified value type. See the SQLite documentation for more information 360 | how sqlite does type conversion. 361 | 362 | Request: 363 | 364 | byte FC_COLUMN 365 | int32 icol the column index (0,1,...) 366 | byte val_type the type of the column value 367 | 368 | Response (Success): 369 | 370 | bool true 371 | bool set true if the column value was set (not 372 | NULL), false if it was NULL. 373 | any value An int23, int64, double, string or 374 | blob value, depending on the val_type 375 | argument. 376 | 377 | Please note: If `set` is false, the value result is omitted. 378 | 379 | 380 | ### FC_FINALIZE 381 | 382 | Finalizes a statement. 383 | 384 | Request: 385 | 386 | byte FC_FINALIZE 387 | 388 | Response (Success): 389 | 390 | bool true 391 | 392 | 393 | ### FC_CLOSE 394 | 395 | Closes a database. 396 | 397 | Request: 398 | 399 | byte FC_CLOSE 400 | 401 | Response (Success): 402 | 403 | bool true 404 | 405 | 406 | ### FC_EXEC 407 | 408 | Executes a statement multiple times. 409 | 410 | Request: 411 | 412 | byte FC_EXEC 413 | string sql 414 | int32 niterations number of iterations 415 | int32 nparams number of bind parameters per iteration 416 | for 0 to (niterations * nparams): 417 | byte val_type type of parameter value 418 | any value 419 | 420 | Response (Success): 421 | 422 | bool true 423 | for 0 to niterations: 424 | int32 changes the change counter for the n'th iteration 425 | 426 | Exec is used to combine invocations of prepare, bind, step, changes, reset and 427 | finalize in one request/response cycle. Let's make an example. Say you want to 428 | insert three users. The user table has two columns: `id` and `name`. The 429 | request would then look like this: 430 | 431 | 1: byte FC_EXEC 432 | 2: string "INSERT INTO users (id,name) VALUES(?,?)" 433 | 3: int32 3 434 | 4: int32 2 435 | 5: byte VAL_INT 436 | 6: int32 1 437 | 7: byte VAL_TEXT 438 | 8: string "Alice" 439 | 9: byte VAL_INT 440 | 10: int32 2 441 | 11: byte VAL_TEXT 442 | 12: string "Bob" 443 | 13: byte VAL_INT 444 | 14: int32 3 445 | 15: byte VAL_NULL 446 | 447 | - Line 1 is the function code. 448 | - Line 2 is the sql text. 449 | - Line 3 is `niterations`, the number of iterations, in this case the given 450 | INSERT statement will be executed three times. 451 | - Line 4 is `nparams`, the number of bind parameters per iteration. In this 452 | case, each INSERT iteration needs 2 parameters, one for the id column and one 453 | for the name. 454 | - Lines 5-8 contain the values for the first INSERT call: A user with id 1 and 455 | name Alice. 456 | - Lines 9-12 contain the values for the next INSERT call: A user with id 2 and 457 | name Bob. 458 | - Lines 13-15 contain the values for the third INSERT call: A user with id 3 459 | and no name (name parameter is NULL). 460 | 461 | If `niterations` is zero, the statement is not called at all. This is allowed. 462 | However, preparing a statement, running it zero times, and the finalizing that 463 | statement doesn't make much sense and should be avoided. 464 | 465 | The `nparams` argument is allowed to be zero. In that case, no parameters are 466 | bound, and the statement is executed as-is. This is often the case if you want 467 | to execute statements other than INSERT, UPDATE or DELETE, for example "BEGIN 468 | TRANSACTION" or "COMMIT", or you have statements without bind parameters, e.g. 469 | "DELETE FROM users". 470 | 471 | For the above request, the response might look like this: 472 | 473 | 1: bool 1 474 | 2: int32 1 475 | 3: int32 1 476 | 4: int32 1 477 | 478 | - Line 1 indicates success (true) 479 | - Line 2 is the change counter for the first iteration: 1 row was updated by 480 | that sql operation. 481 | - Line 3 is the change counter for the second iteration: 1 row was updated by 482 | that sql operation. 483 | - Line 4 is the change counter for the third iteration: 1 row was updated by 484 | that sql operation. 485 | 486 | 487 | ### FC_QUERY 488 | 489 | Execute a query and fetch all row values in one go. 490 | 491 | Request: 492 | 493 | byte FC_QUERY 494 | string sql 495 | int32 nparams number of bind parameters 496 | for 0 to nparams: 497 | byte val_type type of parameter value 498 | any value the parameter value 499 | int32 ncols number of columns per row 500 | for 0 to ncols: 501 | byte val_type type of column value 502 | 503 | Response (Success): 504 | 505 | bool true 506 | int32 nrows number of result rows 507 | for 0 to (nrows * ncols): 508 | bool set true is value is set, false if NULL 509 | any value optional, only present if set is true 510 | 511 | Query is used to combine invocations of prepare, bind, step, column and 512 | finalize into one function call. Let's make an example. Say you want to query 513 | all users with id greater than 42. The user table has two columns, an integer 514 | column `id` and a text column `name`. The request would then look like this: 515 | 516 | 1: byte FC_QUERY 517 | 2: string "SELECT id,name FROM users WHERE id > ? ORDER BY id" 518 | 3: int32 1 519 | 4: byte VAL_INT 520 | 5: int32 42 521 | 6: int32 2 522 | 7: byte VAL_INT 523 | 8: byte VAL_TEXT 524 | 525 | - Line 1 is the function code. 526 | - Line 2 is the query sql. 527 | - Line 3 is `nparams`, the number of bind paramters. In this case we have one 528 | bind parameter. 529 | - Line 4 is the type of the bind parameter. 530 | - Line 5 holds its value. 531 | - Line 6 is the `ncols`, the number of columns per result row. 532 | - Lines 7-8 contain the expected column types. 533 | 534 | Let's assume the user table contained the following values: 535 | 536 | +---------+--------------+ 537 | | id | name | 538 | +---------+--------------+ 539 | | 13 | Thirteen | 540 | | 37 | Thirtyseven | 541 | | 42 | Fourtytwo | 542 | | 51 | Fiftyone | 543 | | 73 | Seventythree | 544 | | 81 | (NULL) | 545 | +---------+--------------+ 546 | 547 | The response would then look like this: 548 | 549 | 1: bool true 550 | 2: int32 3 551 | 3: byte VAL_INT 552 | 4: int32 51 553 | 5: byte VAL_TEXT 554 | 6: string "Fiftyone" 555 | 7: byte VAL_INT 556 | 8: int32 73 557 | 9: byte VAL_TEXT 558 | 10: string "Seventythree" 559 | 11: byte VAL_INT 560 | 12: int32 81 561 | 13: byte VAL_NULL 562 | 563 | - Line 1 contains the success flag, true in this case. 564 | - Line 2 `nrows` indicates that the query yielded 3 result rows. 565 | - Line 3 contains the type of the first column of the first row. 566 | - Line 4 contains the value of the first column of the first row. 567 | - Lines 5-6 contains the type and value of the second column of the first row. 568 | - Lines 7-8 contains the type and value of the first column of the second row. 569 | - Lines 9-10 contains the type and value of the second column of the second 570 | row. 571 | - Lines 11-12 contains the type and value of the first column of the third 572 | row. 573 | - Line 14 contains the type of the second column of the third row. Since the 574 | type is NULL, no value follows. 575 | 576 | -------------------------------------------------------------------------------- /logo-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvilsmeier/sqinn/406501ae9bc0a4da17f68f1cde8e006e04299b13/logo-200.png -------------------------------------------------------------------------------- /src/conn.c: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | #include "mem.h" 3 | #include "conn.h" 4 | 5 | const char *lib_version() { 6 | return sqlite3_libversion(); 7 | } 8 | 9 | conn *conn_new() { 10 | conn *this = MEM_MALLOC(sizeof(conn)); 11 | this->db = NULL; 12 | this->stmt = NULL; 13 | return this; 14 | } 15 | 16 | void conn_free(conn *this) { 17 | MEM_FREE(this); 18 | } 19 | 20 | int conn_open(conn *this, const char *filename, char *errmsg, int maxerrmsg) { 21 | if (this->db) { 22 | snprintf(errmsg, maxerrmsg, "cannot open, db is already open"); 23 | return SQLITE_ERROR; 24 | } 25 | int err = sqlite3_open_v2(filename, &this->db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL); 26 | if (err != SQLITE_OK) { 27 | snprintf(errmsg, maxerrmsg, "sqlite3_open_v2: err=%d, msg=%s", err, sqlite3_errmsg(this->db)); 28 | } 29 | return err; 30 | } 31 | 32 | int conn_prepare(conn *this, const char *sql, char *errmsg, int maxerrmsg) { 33 | if (this->stmt) { 34 | snprintf(errmsg, maxerrmsg, "cannot prepare, stmt is already set, must finalize first"); 35 | return SQLITE_ERROR; 36 | } 37 | int err = sqlite3_prepare_v2(this->db, sql, -1, &this->stmt, 0); 38 | if (err != SQLITE_OK) { 39 | this->stmt = NULL; 40 | snprintf(errmsg, maxerrmsg, "sqlite3_prepare_v2: err=%d, msg=%s", err, sqlite3_errmsg(this->db)); 41 | } 42 | return err; 43 | } 44 | 45 | int conn_bind_null(conn *this, int iparam, char *errmsg, int maxerrmsg) { 46 | int err = sqlite3_bind_null(this->stmt, iparam); 47 | if (err != SQLITE_OK) { 48 | snprintf(errmsg, maxerrmsg, "sqlite3_bind_null: err=%d, msg=%s", err, sqlite3_errmsg(this->db)); 49 | } 50 | return err; 51 | } 52 | 53 | int conn_bind_int(conn *this, int iparam, int value, char *errmsg, int maxerrmsg) { 54 | int err = sqlite3_bind_int(this->stmt, iparam, value); 55 | if (err != SQLITE_OK) { 56 | snprintf(errmsg, maxerrmsg, "sqlite3_bind_int: err=%d, msg=%s", err, sqlite3_errmsg(this->db)); 57 | } 58 | return err; 59 | } 60 | 61 | int conn_bind_int64(conn *this, int iparam, int64 value, char *errmsg, int maxerrmsg) { 62 | int err = sqlite3_bind_int64(this->stmt, iparam, (sqlite_int64)value); 63 | if (err != SQLITE_OK) { 64 | snprintf(errmsg, maxerrmsg, "sqlite3_bind_int64: err=%d, msg=%s", err, sqlite3_errmsg(this->db)); 65 | } 66 | return err; 67 | } 68 | 69 | int conn_bind_double(conn *this, int iparam, double value, char *errmsg, int maxerrmsg) { 70 | int err = sqlite3_bind_double(this->stmt, iparam, value); 71 | if (err != SQLITE_OK) { 72 | snprintf(errmsg, maxerrmsg, "sqlite3_bind_double: err=%d, msg=%s", err, sqlite3_errmsg(this->db)); 73 | } 74 | return err; 75 | } 76 | 77 | int conn_bind_text(conn *this, int iparam, const char *value, char *errmsg, int maxerrmsg) { 78 | int err = sqlite3_bind_text(this->stmt, iparam, value, -1, SQLITE_TRANSIENT); 79 | if (err != SQLITE_OK) { 80 | snprintf(errmsg, maxerrmsg, "conn_bind_text: err=%d, msg=%s", err, sqlite3_errmsg(this->db)); 81 | } 82 | return err; 83 | } 84 | 85 | int conn_bind_blob(conn *this, int iparam, const byte *value, int len, char *errmsg, int maxerrmsg) { 86 | int err = sqlite3_bind_blob(this->stmt, iparam, value, len, SQLITE_TRANSIENT); 87 | if (err != SQLITE_OK) { 88 | snprintf(errmsg, maxerrmsg, "conn_bind_blob: err=%d, msg=%s", err, sqlite3_errmsg(this->db)); 89 | } 90 | return err; 91 | } 92 | 93 | int conn_step(conn *this, bool *more, char *errmsg, int maxerrmsg) { 94 | int err = sqlite3_step(this->stmt); 95 | if (err == SQLITE_OK || err == SQLITE_ROW || err == SQLITE_DONE) { 96 | if (more != NULL) { 97 | *more = (err == SQLITE_ROW) ? TRUE : FALSE; 98 | } 99 | err = SQLITE_OK; 100 | } else { 101 | snprintf(errmsg, maxerrmsg, "sqlite3_step: err=%d, msg=%s", err, sqlite3_errmsg(this->db)); 102 | } 103 | return err; 104 | } 105 | 106 | int conn_reset(conn *this, char *errmsg, int maxerrmsg) { 107 | int err = sqlite3_reset(this->stmt); 108 | if (err != SQLITE_OK) { 109 | snprintf(errmsg, maxerrmsg, "sqlite3_reset: err=%d, msg=%s", err, sqlite3_errmsg(this->db)); 110 | } 111 | return err; 112 | } 113 | 114 | void conn_changes(conn *this, int *pchanges) { 115 | *pchanges = sqlite3_changes(this->db); 116 | } 117 | 118 | void conn_column_int(conn *this, int icol, bool *set, int *value) { 119 | *value = sqlite3_column_int(this->stmt, icol); 120 | *set = TRUE; 121 | if (!*value) { 122 | int type = sqlite3_column_type(this->stmt, icol); 123 | if (type == SQLITE_NULL) { 124 | *set = FALSE; 125 | } 126 | } 127 | } 128 | 129 | void conn_column_int64(conn *this, int icol, bool *set, int64 *value) { 130 | *value = (int64)sqlite3_column_int64(this->stmt, icol); 131 | *set = TRUE; 132 | if (!*value) { 133 | int type = sqlite3_column_type(this->stmt, icol); 134 | if (type == SQLITE_NULL) { 135 | *set = FALSE; 136 | } 137 | } 138 | } 139 | 140 | void conn_column_double(conn *this, int icol, bool *set, double *value) { 141 | *value = sqlite3_column_double(this->stmt, icol); 142 | *set = TRUE; 143 | if (*value == (double)0) { 144 | int type = sqlite3_column_type(this->stmt, icol); 145 | if (type == SQLITE_NULL) { 146 | *set = FALSE; 147 | } 148 | } 149 | } 150 | 151 | void conn_column_text(conn *this, int icol, bool *set, const char **pvalue) { 152 | const char *val = (const char *)sqlite3_column_text(this->stmt, icol); 153 | *pvalue = val; 154 | *set = (val != NULL); 155 | } 156 | 157 | void conn_column_blob(conn *this, int icol, bool *set, const byte **pvalue, int *len) { 158 | const byte *val = (const byte *)sqlite3_column_blob(this->stmt, icol); 159 | *pvalue = val; 160 | *set = (val != NULL); 161 | if (val) { 162 | *len = sqlite3_column_bytes(this->stmt, icol); 163 | } 164 | } 165 | 166 | void conn_finalize(conn *this) { 167 | sqlite3_finalize(this->stmt); 168 | this->stmt = NULL; 169 | } 170 | 171 | int conn_close(conn *this, char *errmsg, int maxerrmsg) { 172 | int err = sqlite3_close(this->db); 173 | if (err == SQLITE_OK) { 174 | this->db = NULL; 175 | } else { 176 | snprintf(errmsg, maxerrmsg, "sqlite3_close: err=%d, msg=%s", err, sqlite3_errmsg(this->db)); 177 | } 178 | return err; 179 | } 180 | -------------------------------------------------------------------------------- /src/conn.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONN_H 2 | #define _CONN_H 3 | 4 | #include "sqlite3.h" 5 | 6 | const char *lib_version(); 7 | 8 | 9 | typedef struct { 10 | sqlite3 *db; 11 | sqlite3_stmt *stmt; 12 | } conn; 13 | 14 | conn *conn_new(); 15 | void conn_free(conn *this); 16 | int conn_open(conn *this, const char *filename, char *errmsg, int maxerrmsg); 17 | int conn_prepare(conn *this, const char *sql, char *errmsg, int maxerrmsg); 18 | int conn_bind_null(conn *this, int iparam, char *errmsg, int maxerrmsg); 19 | int conn_bind_int(conn *this, int iparam, int value, char *errmsg, int maxerrmsg); 20 | int conn_bind_int64(conn *this, int iparam, int64 value, char *errmsg, int maxerrmsg); 21 | int conn_bind_double(conn *this, int iparam, double value, char *errmsg, int maxerrmsg); 22 | int conn_bind_text(conn *this, int iparam, const char *value, char *errmsg, int maxerrmsg); 23 | int conn_bind_blob(conn *this, int iparam, const byte *value, int blob_size, char *errmsg, int maxerrmsg); 24 | int conn_step(conn *this, bool *more, char *errmsg, int maxerrmsg); 25 | int conn_reset(conn *this, char *errmsg, int maxerrmsg); 26 | void conn_changes(conn *this, int *pchanges); 27 | void conn_column_int(conn *this, int icol, bool *set, int *value); 28 | void conn_column_int64(conn *this, int icol, bool *set, int64 *value); 29 | void conn_column_double(conn *this, int icol, bool *set, double *value); 30 | void conn_column_text(conn *this, int icol, bool *set, const char **pvalue); 31 | void conn_column_blob(conn *this, int icol, bool *set, const byte **pvalue, int *len); 32 | void conn_finalize(conn *this); 33 | int conn_close(conn *this, char *errmsg, int maxerrmsg); 34 | 35 | #endif // _CONN_H 36 | -------------------------------------------------------------------------------- /src/conn_test.c: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | #include "conn.h" 3 | 4 | #define MAX_ERRMSG 128 5 | 6 | void _prepare_step_finalize(conn *con, const char *sql, char *errmsg, int maxerrmsg) { 7 | int err = conn_prepare(con, sql, errmsg, maxerrmsg); 8 | ASSERT(!err, "prepare err %s", errmsg); 9 | bool more; 10 | err = conn_step(con, &more, errmsg, maxerrmsg); 11 | ASSERT(!err, "step err '%s'", errmsg); 12 | ASSERT(!more, "wrong more %d", more); 13 | conn_finalize(con); 14 | } 15 | 16 | void test_conn(const char *dbfile) { 17 | INFO("TEST %s\n", __func__); 18 | DEBUG(" dbfile=%s\n", dbfile); 19 | char errmsg[MAX_ERRMSG+1]; 20 | conn *con = conn_new(); 21 | int err = conn_open(con, dbfile, errmsg, MAX_ERRMSG); 22 | ASSERT(!err, "expected !err but was %d", err); 23 | // prepare schema 24 | err = conn_prepare(con, "DROP TABLE IF EXISTS users", errmsg, MAX_ERRMSG); 25 | ASSERT(!err, "wrong err %d", err); 26 | err = conn_step(con, NULL, errmsg, MAX_ERRMSG); 27 | ASSERT(!err, "wrong err %d", err); 28 | conn_finalize(con); 29 | err = conn_prepare(con, "CREATE TABLE users (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR, age INTEGER, rating REAL)", errmsg, MAX_ERRMSG); 30 | ASSERT(!err, "wrong err %d", err); 31 | err = conn_step(con, NULL, errmsg, MAX_ERRMSG); 32 | ASSERT(!err, "wrong err %d", err); 33 | conn_finalize(con); 34 | // insert users without parameters 35 | err = conn_prepare(con, "INSERT INTO users (id,name,age,rating) VALUES(1, 'Alice', 31, 0.1)", errmsg, MAX_ERRMSG); 36 | ASSERT(!err, "wrong err %d", err); 37 | err = conn_step(con, NULL, errmsg, MAX_ERRMSG); 38 | ASSERT(!err, "wrong err %d", err); 39 | int changes; 40 | conn_changes(con, &changes); 41 | ASSERT(changes==1, "wrong changes %d", changes); 42 | conn_finalize(con); 43 | // insert users with parameters 44 | err = conn_prepare(con, "INSERT INTO users (id,name,age,rating) VALUES(?,?,?,?)", errmsg, MAX_ERRMSG); 45 | ASSERT(!err, "wrong err %d", err); 46 | int icol = 1; 47 | err = conn_bind_int(con, icol++, 2, errmsg, MAX_ERRMSG); 48 | ASSERT(!err, "wrong err %d", err); 49 | err = conn_bind_text(con, icol++, "Bob", errmsg, MAX_ERRMSG); 50 | ASSERT(!err, "wrong err %d", err); 51 | err = conn_bind_int(con, icol++, 32, errmsg, MAX_ERRMSG); 52 | ASSERT(!err, "wrong err %d", err); 53 | err = conn_bind_double(con, icol++, 0.2, errmsg, MAX_ERRMSG); 54 | ASSERT(!err, "wrong err %d", err); 55 | err = conn_step(con, NULL, errmsg, MAX_ERRMSG); 56 | ASSERT(!err, "wrong err %d", err); 57 | conn_changes(con, &changes); 58 | ASSERT(changes==1, "wrong changes %d", changes); 59 | err = conn_reset(con, errmsg, MAX_ERRMSG); 60 | ASSERT(!err, "wrong err %d", err); 61 | icol = 1; 62 | err = conn_bind_int(con, icol++, 3, errmsg, MAX_ERRMSG); 63 | ASSERT(!err, "wrong err %d", err); 64 | err = conn_bind_null(con, icol++, errmsg, MAX_ERRMSG); 65 | ASSERT(!err, "wrong err %d", err); 66 | err = conn_bind_null(con, icol++, errmsg, MAX_ERRMSG); 67 | ASSERT(!err, "wrong err %d", err); 68 | err = conn_bind_null(con, icol++, errmsg, MAX_ERRMSG); 69 | ASSERT(!err, "wrong err %d", err); 70 | err = conn_step(con, NULL, errmsg, MAX_ERRMSG); 71 | ASSERT(!err, "wrong err %d", err); 72 | conn_changes(con, &changes); 73 | ASSERT(changes==1, "wrong changes %d", changes); 74 | err = conn_reset(con, errmsg, MAX_ERRMSG); 75 | ASSERT(!err, "wrong err %d", err); 76 | conn_finalize(con); 77 | // select users 78 | err = conn_prepare(con, "SELECT id,name,age,rating FROM users ORDER BY id", errmsg, MAX_ERRMSG); 79 | ASSERT(!err, "wrong err %d", err); 80 | bool more; 81 | err = conn_step(con, &more, errmsg, MAX_ERRMSG); 82 | ASSERT(!err, "wrong err %d", err); 83 | size_t nrows = 0; 84 | while(more) { 85 | nrows++; 86 | bool set; 87 | int id=0; 88 | const char *name=NULL; 89 | int age=0; 90 | double rating=0; 91 | icol=0; 92 | conn_column_int(con, icol++, &set, &id); 93 | conn_column_text(con, icol++, &set, &name); 94 | conn_column_int(con, icol++, &set, &age); 95 | conn_column_double(con, icol++, &set, &rating); 96 | // DEBUG("id=%d, name='%s', age=%d, rating=%g\n", id, name, age, rating); 97 | err = conn_step(con, &more, errmsg, MAX_ERRMSG); 98 | ASSERT(!err, "wrong err %d", err); 99 | } 100 | ASSERT(nrows == 3, "want 3 rows but have %d", (int)nrows); 101 | conn_finalize(con); 102 | // delete users 103 | err = conn_prepare(con, "DELETE FROM users WHERE name = 'does_not_exist'", errmsg, MAX_ERRMSG); 104 | ASSERT(!err, "wrong err %d", err); 105 | err = conn_step(con, NULL, errmsg, MAX_ERRMSG); 106 | ASSERT(!err, "wrong err %d", err); 107 | conn_changes(con, &changes); 108 | ASSERT(changes==0, "wrong changes %d", changes); 109 | conn_finalize(con); 110 | err = conn_prepare(con, "DELETE FROM users WHERE id >= 0", errmsg, MAX_ERRMSG); 111 | ASSERT(!err, "wrong err %d", err); 112 | err = conn_step(con, NULL, errmsg, MAX_ERRMSG); 113 | ASSERT(!err, "wrong err %d", err); 114 | conn_changes(con, &changes); 115 | ASSERT(changes==3, "wrong changes %d", changes); 116 | conn_finalize(con); 117 | // close db 118 | err = conn_close(con, errmsg, MAX_ERRMSG); 119 | ASSERT(!err, "wrong err %d", err); 120 | conn_free(con); 121 | INFO("TEST %s OK\n", __func__); 122 | } 123 | 124 | void bench_conn_users(const char *dbfile, int nusers) { 125 | INFO("BENCH %s\n", __func__); 126 | DEBUG(" dbfile=%s, nusers=%d\n", dbfile, nusers); 127 | char errmsg[MAX_ERRMSG+1]; 128 | conn *con = conn_new(); 129 | conn_open(con, dbfile, errmsg, MAX_ERRMSG); 130 | // prepare schema 131 | _prepare_step_finalize(con, "DROP TABLE IF EXISTS users", errmsg, MAX_ERRMSG); 132 | _prepare_step_finalize(con, "CREATE TABLE users (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR, age INTEGER, rating REAL)", errmsg, MAX_ERRMSG); 133 | double tstart = mono_time(); 134 | // insert 135 | _prepare_step_finalize(con, "BEGIN", errmsg, MAX_ERRMSG); 136 | conn_prepare(con, "INSERT INTO users (id, name, age, rating) VALUES(?,?,?,?)", errmsg, MAX_ERRMSG); 137 | for (int i=0 ; icon = conn_new(); 11 | return this; 12 | } 13 | 14 | void handler_free(handler *this) { 15 | conn_free(this->con); 16 | MEM_FREE(this); 17 | } 18 | 19 | void _write_ok_or_err(int err, const char *errmsg, dbuf *resp) { 20 | if(!err) { 21 | dbuf_write_bool(resp, TRUE); 22 | } else { 23 | dbuf_write_bool(resp, FALSE); 24 | dbuf_write_string(resp, errmsg); 25 | } 26 | } 27 | 28 | int _bind_param(conn *con, int iparam, dbuf *req, char *errmsg, int maxerrmsg) { 29 | byte val_type = dbuf_read_byte(req); 30 | switch (val_type) { 31 | case VAL_NULL: 32 | { 33 | return conn_bind_null(con, iparam, errmsg, maxerrmsg); 34 | } 35 | case VAL_INT: 36 | { 37 | int ival = dbuf_read_int32(req); 38 | return conn_bind_int(con, iparam, ival, errmsg, maxerrmsg); 39 | } 40 | case VAL_INT64: 41 | { 42 | int64 ival = dbuf_read_int64(req); 43 | return conn_bind_int64(con, iparam, ival, errmsg, maxerrmsg); 44 | } 45 | case VAL_DOUBLE_STR: 46 | { 47 | double dval = dbuf_read_double_str(req); 48 | return conn_bind_double(con, iparam, dval, errmsg, maxerrmsg); 49 | } 50 | case VAL_DOUBLE_IEEE: 51 | { 52 | double dval = dbuf_read_double_ieee(req); 53 | return conn_bind_double(con, iparam, dval, errmsg, maxerrmsg); 54 | } 55 | case VAL_TEXT: 56 | { 57 | const char *sval = dbuf_read_string(req); 58 | return conn_bind_text(con, iparam, sval, errmsg, maxerrmsg); 59 | } 60 | case VAL_BLOB: 61 | { 62 | int len; 63 | const byte *bval = dbuf_read_blob(req, &len); 64 | return conn_bind_blob(con, iparam, bval, len, errmsg, maxerrmsg); 65 | } 66 | } 67 | snprintf(errmsg, maxerrmsg, "unknown bind val_type %d", val_type); 68 | INFO("%s\n", errmsg); 69 | return SQLITE_ERROR; 70 | } 71 | 72 | int _column(conn *con, byte val_type, int icol, dbuf *dest, char *errmsg, int maxerrmsg) { 73 | bool set; 74 | switch (val_type) { 75 | case VAL_INT: { 76 | int ival; 77 | conn_column_int(con, icol, &set, &ival); 78 | dbuf_write_bool(dest, set); 79 | if (set) { 80 | dbuf_write_int32(dest, ival); 81 | } 82 | return SQLITE_OK; 83 | } 84 | case VAL_INT64: { 85 | int64 ival; 86 | conn_column_int64(con, icol, &set, &ival); 87 | dbuf_write_bool(dest, set); 88 | if (set) { 89 | dbuf_write_int64(dest, ival); 90 | } 91 | return SQLITE_OK; 92 | } 93 | case VAL_DOUBLE_STR: { 94 | double dval; 95 | conn_column_double(con, icol, &set, &dval); 96 | dbuf_write_bool(dest, set); 97 | if (set) { 98 | dbuf_write_double_str(dest, dval); 99 | } 100 | return SQLITE_OK; 101 | } 102 | case VAL_DOUBLE_IEEE: { 103 | double dval; 104 | conn_column_double(con, icol, &set, &dval); 105 | dbuf_write_bool(dest, set); 106 | if (set) { 107 | dbuf_write_double_ieee(dest, dval); 108 | } 109 | return SQLITE_OK; 110 | } 111 | case VAL_TEXT: { 112 | const char *sval; 113 | conn_column_text(con, icol, &set, &sval); 114 | dbuf_write_bool(dest, set); 115 | if (set) { 116 | dbuf_write_string(dest, sval); 117 | } 118 | return SQLITE_OK; 119 | } 120 | case VAL_BLOB: { 121 | const byte *bval; 122 | int len; 123 | conn_column_blob(con, icol, &set, &bval, &len); 124 | dbuf_write_bool(dest, set); 125 | if (set) { 126 | dbuf_write_blob(dest, bval, len); 127 | } 128 | return SQLITE_OK; 129 | } 130 | } 131 | snprintf(errmsg, maxerrmsg, "unknown column col_type %d", val_type); 132 | INFO("%s\n", errmsg); 133 | return SQLITE_ERROR; 134 | } 135 | 136 | int _fetch_rows(conn *con, dbuf *req, int *nrows, dbuf *vbuf, char *errmsg, int maxerrmsg) { 137 | int ncols = dbuf_read_int32(req); 138 | byte *val_types = (byte *)MEM_MALLOC(ncols*sizeof(byte)); 139 | for (int icol=0 ; icolcon; 162 | byte fc = dbuf_read_byte(req); 163 | switch (fc) { 164 | case FC_SQINN_VERSION: 165 | { 166 | dbuf_write_bool(resp, TRUE); 167 | dbuf_write_string(resp, SQINN_VERSION); 168 | } 169 | break; 170 | case FC_IO_VERSION: 171 | { 172 | dbuf_write_bool(resp, TRUE); 173 | dbuf_write_byte(resp, IO_VERSION); 174 | } 175 | break; 176 | case FC_SQLITE_VERSION: 177 | { 178 | const char *v = lib_version(con); 179 | dbuf_write_bool(resp, TRUE); 180 | dbuf_write_string(resp, v); 181 | } 182 | break; 183 | case FC_OPEN: 184 | { 185 | const char *filename = dbuf_read_string(req); 186 | int err = conn_open(con, filename, errmsg, MAX_ERRMSG); 187 | _write_ok_or_err(err, errmsg, resp); 188 | } 189 | break; 190 | case FC_PREPARE: 191 | { 192 | const char *sql = dbuf_read_string(req); 193 | int err = conn_prepare(con, sql, errmsg, MAX_ERRMSG); 194 | _write_ok_or_err(err, errmsg, resp); 195 | } 196 | break; 197 | case FC_BIND: 198 | { 199 | int iparam = dbuf_read_int32(req); 200 | int err = _bind_param(con, iparam, req, errmsg, MAX_ERRMSG); 201 | _write_ok_or_err(err, errmsg, resp); 202 | } 203 | break; 204 | case FC_STEP: 205 | { 206 | bool more; 207 | int err = conn_step(con, &more, errmsg, MAX_ERRMSG); 208 | _write_ok_or_err(err, errmsg, resp); 209 | if(!err) { 210 | dbuf_write_bool(resp, more); 211 | } 212 | } 213 | break; 214 | case FC_RESET: 215 | { 216 | int err = conn_reset(con, errmsg, MAX_ERRMSG); 217 | _write_ok_or_err(err, errmsg, resp); 218 | } 219 | break; 220 | case FC_CHANGES: 221 | { 222 | int changes; 223 | conn_changes(con, &changes); 224 | dbuf_write_bool(resp, TRUE); 225 | dbuf_write_int32(resp, changes); 226 | } 227 | break; 228 | case FC_COLUMN: 229 | { 230 | int icol = dbuf_read_int32(req); 231 | byte col_type = dbuf_read_byte(req); 232 | dbuf *vbuf = dbuf_new(); 233 | int err = _column(con, col_type, icol, vbuf, errmsg, MAX_ERRMSG); 234 | _write_ok_or_err(err, errmsg, resp); 235 | if(!err) { 236 | dbuf_write_dbuf(resp, vbuf); 237 | } 238 | dbuf_free(vbuf); 239 | } 240 | break; 241 | case FC_FINALIZE: 242 | { 243 | conn_finalize(con); 244 | _write_ok_or_err(SQLITE_OK, errmsg, resp); 245 | } 246 | break; 247 | case FC_CLOSE: 248 | { 249 | int err = conn_close(con, errmsg, MAX_ERRMSG); 250 | _write_ok_or_err(err, errmsg, resp); 251 | } 252 | break; 253 | case FC_EXEC: 254 | { 255 | const char *sql = dbuf_read_string(req); 256 | bool prepared = FALSE; 257 | int err = conn_prepare(con, sql, errmsg, MAX_ERRMSG); 258 | if (!err) { 259 | prepared = TRUE; 260 | } 261 | int niterations = 0; 262 | int *changes = NULL; 263 | if (!err) { 264 | niterations = dbuf_read_int32(req); 265 | int nparams = dbuf_read_int32(req); 266 | changes = MEM_MALLOC(niterations * sizeof(int)); 267 | for (int iit = 0; !err && iit < niterations; iit++) { 268 | for (int iparam = 1; !err && iparam <= nparams; iparam++) { 269 | err = _bind_param(con, iparam, req, errmsg, MAX_ERRMSG); 270 | } 271 | if (!err) { 272 | err = conn_step(con, NULL, errmsg, MAX_ERRMSG); 273 | } 274 | if (!err) { 275 | conn_changes(con, &changes[iit]); 276 | } 277 | if (!err) { 278 | if (iit < niterations-1) { 279 | err = conn_reset(con, errmsg, MAX_ERRMSG); 280 | } 281 | } 282 | } 283 | } 284 | if (prepared) { 285 | conn_finalize(con); 286 | } 287 | _write_ok_or_err(err, errmsg, resp); 288 | if (!err) { 289 | for (int iit = 0; iit < niterations; iit++) { 290 | dbuf_write_int32(resp, changes[iit]); 291 | } 292 | } 293 | MEM_FREE(changes); 294 | } 295 | break; 296 | case FC_QUERY: 297 | { 298 | dbuf *vbuf = dbuf_new(); 299 | const char *sql = dbuf_read_string(req); 300 | bool prepared = FALSE; 301 | int err = conn_prepare(con, sql, errmsg, MAX_ERRMSG); 302 | if (!err) { 303 | prepared = TRUE; 304 | int nparams = dbuf_read_int32(req); 305 | for (int iparam=1 ; !err && iparam<=nparams ; iparam++) { 306 | err = _bind_param(con, iparam, req, errmsg, MAX_ERRMSG); 307 | } 308 | } 309 | int nrows = 0; 310 | if (!err) { 311 | err = _fetch_rows(con, req, &nrows, vbuf, errmsg, MAX_ERRMSG); 312 | } 313 | if (prepared) { 314 | conn_finalize(con); 315 | } 316 | _write_ok_or_err(err, errmsg, resp); 317 | if (!err) { 318 | dbuf_write_int32(resp, nrows); 319 | dbuf_write_dbuf(resp, vbuf); 320 | } 321 | dbuf_free(vbuf); 322 | } 323 | break; 324 | default: 325 | { 326 | snprintf(errmsg, MAX_ERRMSG, "unknown function code %u", fc); 327 | dbuf_write_bool(resp, FALSE); 328 | dbuf_write_string(resp, errmsg); 329 | } 330 | break; 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /src/handler.h: -------------------------------------------------------------------------------- 1 | #ifndef _HANDLER_H 2 | #define _HANDLER_H 3 | 4 | // function codes 5 | 6 | #define FC_SQINN_VERSION 1 7 | #define FC_IO_VERSION 2 8 | #define FC_SQLITE_VERSION 3 9 | #define FC_OPEN 10 10 | #define FC_PREPARE 11 11 | #define FC_BIND 12 12 | #define FC_STEP 13 13 | #define FC_RESET 14 14 | #define FC_CHANGES 15 15 | #define FC_COLUMN 16 16 | #define FC_FINALIZE 17 17 | #define FC_CLOSE 18 18 | #define FC_EXEC 51 19 | #define FC_QUERY 52 20 | 21 | // value types 22 | 23 | #define VAL_NULL 0 24 | #define VAL_INT 1 25 | #define VAL_INT64 2 26 | #define VAL_DOUBLE_STR 3 27 | #define VAL_TEXT 4 28 | #define VAL_BLOB 5 29 | #define VAL_DOUBLE_IEEE 6 30 | 31 | // handler struct 32 | 33 | typedef struct { 34 | conn *con; 35 | } handler; 36 | 37 | handler *handler_new(); 38 | void handler_free(handler *this); 39 | void handler_handle(handler *this, dbuf *req, dbuf *resp); 40 | 41 | #endif // _HANDLER_H 42 | 43 | -------------------------------------------------------------------------------- /src/handler_test.c: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | #include "mem.h" 3 | #include "conn.h" 4 | #include "handler.h" 5 | #include "handler_test.h" 6 | 7 | 8 | void test_handler_versions() { 9 | INFO("TEST %s\n", __func__); 10 | handler *hd = handler_new(); 11 | dbuf *req = dbuf_new(); 12 | dbuf *resp = dbuf_new(); 13 | // sqinn version 14 | dbuf_reset(req); 15 | dbuf_reset(resp); 16 | dbuf_write_byte(req, FC_SQINN_VERSION); 17 | handler_handle(hd, req, resp); 18 | ASSERT0(dbuf_read_bool(resp), "was not ok"); 19 | const char *sqinn_version = dbuf_read_string(resp); 20 | ASSERT(strcmp(sqinn_version, SQINN_VERSION)==0, "wrong sqinn_version %s", sqinn_version); 21 | // io protocol version 22 | dbuf_reset(req); 23 | dbuf_reset(resp); 24 | dbuf_write_byte(req, FC_IO_VERSION); 25 | handler_handle(hd, req, resp); 26 | ASSERT0(dbuf_read_bool(resp), "was not ok"); 27 | byte io_version = dbuf_read_byte(resp); 28 | ASSERT(io_version==1, "wrong io_version %d", io_version); 29 | // sqlite version 30 | dbuf_reset(req); 31 | dbuf_reset(resp); 32 | dbuf_write_byte(req, FC_SQLITE_VERSION); 33 | handler_handle(hd, req, resp); 34 | ASSERT0(dbuf_read_bool(resp), "was not ok"); 35 | const char *sqlite_version = dbuf_read_string(resp); 36 | ASSERT(strcmp(sqlite_version, "3.49.2")==0, "wrong sqlite_version %s", sqlite_version); 37 | // cleanup 38 | dbuf_free(req); 39 | dbuf_free(resp); 40 | handler_free(hd); 41 | INFO("TEST %s OK\n", __func__); 42 | } 43 | 44 | void _prep_step_fin(handler *hd, dbuf *req, dbuf *resp, const char *sql) { 45 | dbuf_reset(req); 46 | dbuf_reset(resp); 47 | dbuf_write_byte(req, FC_PREPARE); 48 | dbuf_write_string(req, sql); 49 | handler_handle(hd, req, resp); 50 | ASSERT0(dbuf_read_bool(resp), "was not ok"); 51 | dbuf_reset(req); 52 | dbuf_reset(resp); 53 | dbuf_write_byte(req, FC_STEP); 54 | handler_handle(hd, req, resp); 55 | ASSERT0(dbuf_read_bool(resp), "was not ok"); 56 | bool more = dbuf_read_bool(resp); 57 | ASSERT(!more, "wrong more %d", more); 58 | dbuf_reset(req); 59 | dbuf_reset(resp); 60 | dbuf_write_byte(req, FC_FINALIZE); 61 | handler_handle(hd, req, resp); 62 | ASSERT0(dbuf_read_bool(resp), "was not ok"); 63 | } 64 | 65 | void test_handler_functions(const char *dbfile) { 66 | INFO("TEST %s\n", __func__); 67 | DEBUG(" dbfile=%s\n", dbfile); 68 | handler *hd = handler_new(); 69 | dbuf *req = dbuf_new(); 70 | dbuf *resp = dbuf_new(); 71 | bool ok; 72 | // open db 73 | { 74 | dbuf_reset(req); 75 | dbuf_reset(resp); 76 | dbuf_write_byte(req, FC_OPEN); 77 | dbuf_write_string(req, dbfile); 78 | handler_handle(hd, req, resp); 79 | ok = dbuf_read_bool(resp); 80 | ASSERT(ok, "expected ok but was %d", ok); 81 | } 82 | // prepare schema 83 | { 84 | _prep_step_fin(hd, req, resp, "DROP TABLE IF EXISTS users"); 85 | _prep_step_fin(hd, req, resp, "CREATE TABLE users (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR, age BIGINT, rating REAL, balance REAL, image BLOB)"); 86 | } 87 | // insert user 88 | int id = 1; 89 | const char *name = "Alice"; 90 | int64 age = ((int64)2 << 62) + 3; 91 | double rating = 13.24; 92 | double balance = 32.0; 93 | byte image[128]; 94 | { 95 | _prep_step_fin(hd, req, resp, "BEGIN"); 96 | dbuf_reset(req); 97 | dbuf_reset(resp); 98 | dbuf_write_byte(req, FC_PREPARE); 99 | dbuf_write_string(req, "INSERT INTO users (id, name, age, rating, balance, image) VALUES (?, ?, ?, ?, ?, ?);"); 100 | handler_handle(hd, req, resp); 101 | ok = dbuf_read_bool(resp); 102 | ASSERT(ok, "expected ok but was %d", ok); 103 | // bind id 104 | dbuf_reset(req); 105 | dbuf_reset(resp); 106 | dbuf_write_byte(req, FC_BIND); 107 | dbuf_write_int32(req, 1); // iparam 108 | dbuf_write_byte(req, VAL_INT); 109 | dbuf_write_int32(req, id); 110 | handler_handle(hd, req, resp); 111 | ok = dbuf_read_bool(resp); 112 | ASSERT(ok, "expected ok but was %d", ok); 113 | // bind name 114 | dbuf_reset(req); 115 | dbuf_reset(resp); 116 | dbuf_write_byte(req, FC_BIND); 117 | dbuf_write_int32(req, 2); // iparam 118 | dbuf_write_byte(req, VAL_TEXT); 119 | dbuf_write_string(req, name); 120 | handler_handle(hd, req, resp); 121 | ok = dbuf_read_bool(resp); 122 | ASSERT(ok, "expected ok but was %d", ok); 123 | // bind age 124 | dbuf_reset(req); 125 | dbuf_reset(resp); 126 | dbuf_write_byte(req, FC_BIND); 127 | dbuf_write_int32(req, 3); // iparam 128 | dbuf_write_byte(req, VAL_INT64); 129 | dbuf_write_int64(req, age); 130 | handler_handle(hd, req, resp); 131 | ok = dbuf_read_bool(resp); 132 | ASSERT(ok, "expected ok but was %d", ok); 133 | // bind rating 134 | dbuf_reset(req); 135 | dbuf_reset(resp); 136 | dbuf_write_byte(req, FC_BIND); 137 | dbuf_write_int32(req, 4); // iparam 138 | dbuf_write_byte(req, VAL_DOUBLE_STR); 139 | dbuf_write_double_str(req, rating); 140 | handler_handle(hd, req, resp); 141 | ok = dbuf_read_bool(resp); 142 | ASSERT(ok, "expected ok but was %d", ok); 143 | // bind balance 144 | dbuf_reset(req); 145 | dbuf_reset(resp); 146 | dbuf_write_byte(req, FC_BIND); 147 | dbuf_write_int32(req, 5); // iparam 148 | dbuf_write_byte(req, VAL_DOUBLE_IEEE); 149 | dbuf_write_double_ieee(req, balance); 150 | handler_handle(hd, req, resp); 151 | ok = dbuf_read_bool(resp); 152 | ASSERT(ok, "expected ok but was %d", ok); 153 | // bind image 154 | for (int i = 0; i < (sizeof(image) / sizeof(image[0])); i++) { 155 | image[i] = (byte)i; 156 | } 157 | dbuf_reset(req); 158 | dbuf_reset(resp); 159 | dbuf_write_byte(req, FC_BIND); 160 | dbuf_write_int32(req, 6); 161 | dbuf_write_byte(req, VAL_BLOB); 162 | dbuf_write_blob(req, image, sizeof(image)); // image blob 163 | handler_handle(hd, req, resp); 164 | ok = dbuf_read_bool(resp); 165 | ASSERT(ok, "expected ok but was %d", ok); 166 | // step 167 | dbuf_reset(req); 168 | dbuf_reset(resp); 169 | dbuf_write_byte(req, FC_STEP); 170 | handler_handle(hd, req, resp); 171 | ok = dbuf_read_bool(resp); 172 | ASSERT(ok, "expected ok but was %d", ok); 173 | bool more = dbuf_read_bool(resp); 174 | ASSERT(!more, "expected !more but was %d", more); 175 | // reset 176 | dbuf_reset(req); 177 | dbuf_reset(resp); 178 | dbuf_write_byte(req, FC_RESET); 179 | handler_handle(hd, req, resp); 180 | ok = dbuf_read_bool(resp); 181 | ASSERT(ok, "expected ok but was %d", ok); 182 | // changes 183 | dbuf_reset(req); 184 | dbuf_reset(resp); 185 | dbuf_write_byte(req, FC_CHANGES); 186 | handler_handle(hd, req, resp); 187 | ok = dbuf_read_bool(resp); 188 | ASSERT(ok, "expected ok but was %d", ok); 189 | int changes = dbuf_read_int32(resp); 190 | ASSERT(changes==1, "wrong changes %d", changes); 191 | // finalize 192 | dbuf_reset(req); 193 | dbuf_reset(resp); 194 | dbuf_write_byte(req, FC_FINALIZE); 195 | handler_handle(hd, req, resp); 196 | ok = dbuf_read_bool(resp); 197 | ASSERT(ok, "expected ok but was %d", ok); 198 | // commit 199 | _prep_step_fin(hd, req, resp, "COMMIT"); 200 | } 201 | // select user 202 | { 203 | dbuf_reset(req); 204 | dbuf_reset(resp); 205 | dbuf_write_byte(req, FC_PREPARE); 206 | dbuf_write_string(req, "SELECT id, name, age, rating, balance, image FROM users ORDER BY id;"); 207 | handler_handle(hd, req, resp); 208 | ok = dbuf_read_bool(resp); 209 | ASSERT(ok, "expected ok but was %d", ok); 210 | dbuf_reset(req); 211 | dbuf_reset(resp); 212 | dbuf_write_byte(req, FC_STEP); 213 | handler_handle(hd, req, resp); 214 | ok = dbuf_read_bool(resp); 215 | ASSERT(ok, "expected ok but was %d", ok); 216 | bool more = dbuf_read_bool(resp); 217 | ASSERT(more, "expected more but was %d\n", more); 218 | // fetch id 219 | dbuf_reset(req); 220 | dbuf_reset(resp); 221 | dbuf_write_byte(req, FC_COLUMN); 222 | dbuf_write_int32(req, 0); 223 | dbuf_write_byte(req, VAL_INT); 224 | handler_handle(hd, req, resp); 225 | ok = dbuf_read_bool(resp); 226 | ASSERT(ok, "expected ok but was %d", ok); 227 | ASSERT0(dbuf_read_bool(resp), "id not set"); 228 | int sel_id = dbuf_read_int32(resp); 229 | ASSERT(sel_id==id, "wrong sel_id %d", sel_id); 230 | // fetch name 231 | dbuf_reset(req); 232 | dbuf_reset(resp); 233 | dbuf_write_byte(req, FC_COLUMN); 234 | dbuf_write_int32(req, 1); 235 | dbuf_write_byte(req, VAL_TEXT); 236 | handler_handle(hd, req, resp); 237 | ok = dbuf_read_bool(resp); 238 | ASSERT(ok, "expected ok but was %d", ok); 239 | ASSERT0(dbuf_read_bool(resp), "was not set"); 240 | const char *sel_name = dbuf_read_string(resp); 241 | ASSERT(strcmp(sel_name, name)==0, "wrong sel_name '%s'", sel_name); 242 | // fetch age 243 | dbuf_reset(req); 244 | dbuf_reset(resp); 245 | dbuf_write_byte(req, FC_COLUMN); 246 | dbuf_write_int32(req, 2); 247 | dbuf_write_byte(req, VAL_INT64); 248 | handler_handle(hd, req, resp); 249 | ok = dbuf_read_bool(resp); 250 | ASSERT(ok, "expected ok but was %d", ok); 251 | ASSERT0(dbuf_read_bool(resp), "was not set"); 252 | int64 sel_age = dbuf_read_int64(resp); 253 | ASSERT(sel_age==age, "wrong sel_age %I64d", sel_age); 254 | // fetch rating 255 | dbuf_reset(req); 256 | dbuf_reset(resp); 257 | dbuf_write_byte(req, FC_COLUMN); 258 | dbuf_write_int32(req, 3); 259 | dbuf_write_byte(req, VAL_DOUBLE_STR); 260 | handler_handle(hd, req, resp); 261 | ok = dbuf_read_bool(resp); 262 | ASSERT(ok, "expected ok but was %d", ok); 263 | ASSERT0(dbuf_read_bool(resp), "was not set"); 264 | double sel_rating = dbuf_read_double_str(resp); 265 | ASSERT(sel_rating == rating, "wrong sel_rating %g", sel_rating); 266 | // fetch balance 267 | dbuf_reset(req); 268 | dbuf_reset(resp); 269 | dbuf_write_byte(req, FC_COLUMN); 270 | dbuf_write_int32(req, 4); 271 | dbuf_write_byte(req, VAL_DOUBLE_IEEE); 272 | handler_handle(hd, req, resp); 273 | ok = dbuf_read_bool(resp); 274 | ASSERT(ok, "expected ok but was %d", ok); 275 | ASSERT0(dbuf_read_bool(resp), "was not set"); 276 | double sel_balance = dbuf_read_double_ieee(resp); 277 | ASSERT(sel_balance == balance, "wrong sel_balance %g", sel_balance); 278 | // fetch image 279 | dbuf_reset(req); 280 | dbuf_reset(resp); 281 | dbuf_write_byte(req, FC_COLUMN); 282 | dbuf_write_int32(req, 5); 283 | dbuf_write_byte(req, VAL_BLOB); 284 | handler_handle(hd, req, resp); 285 | ok = dbuf_read_bool(resp); 286 | ASSERT(ok, "expected ok but was %d", ok); 287 | ASSERT0(dbuf_read_bool(resp), "was not set"); 288 | int blob_size; 289 | const byte *sel_image = dbuf_read_blob(resp, &blob_size); 290 | ASSERT(sel_image != NULL, "wrong sel_image %p", sel_image); 291 | ASSERT(blob_size == sizeof(image), "wrong blob_size %d", blob_size); 292 | for (int i=0 ; i=? AND name LIKE ? ORDER BY id"); 428 | dbuf_write_int32(req, 2); // nparams 429 | dbuf_write_byte(req, VAL_INT); // param 1 type 430 | dbuf_write_int32(req, 1); // param 1 value 431 | dbuf_write_byte(req, VAL_TEXT); // param 2 type 432 | dbuf_write_string(req, "%"); // param 2 value 433 | dbuf_write_int32(req, 4); // ncols 434 | dbuf_write_byte(req, VAL_INT); // col 0 type 435 | dbuf_write_byte(req, VAL_TEXT); // col 1 type 436 | dbuf_write_byte(req, VAL_INT); // col 2 type 437 | dbuf_write_byte(req, VAL_DOUBLE_STR); // col 3 type 438 | handler_handle(hd, req, resp); 439 | bool ok = dbuf_read_bool(resp); 440 | ASSERT(ok, "expected ok but ok was %d", ok); 441 | int nrows = dbuf_read_int32(resp); 442 | ASSERT(nrows == nusers, "want %d rows, have %d\n", nusers, nrows); 443 | for (int i=0 ; isz > 0) { 42 | handler_handle(hd, req, resp); 43 | // write resp 44 | byte sz_buf[4]; 45 | encode_int32(resp->sz, sz_buf); 46 | fwrite(sz_buf, 1, 4, stdout); 47 | fwrite(resp->buf, 1, resp->sz, stdout); 48 | fflush(stdout); 49 | } else { 50 | quit = TRUE; 51 | } 52 | } 53 | dbuf_free(req); 54 | dbuf_free(resp); 55 | handler_free(hd); 56 | } 57 | -------------------------------------------------------------------------------- /src/loop.h: -------------------------------------------------------------------------------- 1 | #ifndef _LOOP_H 2 | #define _LOOP_H 3 | 4 | void loop(); 5 | 6 | #endif // _LOOP_H 7 | 8 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "util.h" 4 | #include "mem.h" 5 | #include "mem_test.h" 6 | #include "conn.h" 7 | #include "conn_test.h" 8 | #include "handler.h" 9 | #include "handler_test.h" 10 | #include "loop.h" 11 | 12 | void test_platform() { 13 | INFO("TEST %s\n", __func__); 14 | DEBUG("sizeof(int)=%d\n", sizeof(int)); 15 | DEBUG("sizeof(size_t)=%d\n", sizeof(size_t)); 16 | DEBUG("sizeof(int64)=%d\n", sizeof(int64)); 17 | DEBUG("FMT_PRId64=%s\n", FMT_PRId64); 18 | DEBUG("FMT_PRIu64=%s\n", FMT_PRIu64); 19 | ASSERT(sizeof(int)==4, "expected int to be 4 bytes but was %d", sizeof(size_t)); 20 | ASSERT(sizeof(size_t)==8 || sizeof(size_t)==4, "expected size_t to be 8 or 4 bytes but was %"FMT_PRIu64, sizeof(size_t)); 21 | ASSERT(sizeof(int64)==8, "expected int64 to be 8 bytes but was %"FMT_PRIu64, sizeof(int64)); 22 | ASSERT(sizeof(double)==8, "expected double to be 8 bytes but was %"FMT_PRIu64, sizeof(double)); 23 | double dbl = -2; // in IEEE 745 it's hex(C000 0000 0000 0000) 24 | byte dbl0 = ((char*)&dbl)[0]; 25 | byte dbl7 = ((char*)&dbl)[7]; 26 | ASSERT(dbl0== 0x00, "expected double -2 [0] 0x00 but was %02X", dbl0); 27 | ASSERT(dbl7== 0xC0, "expected double -2 [7] 0xC0 but was %02X", dbl7); 28 | char buf[64]; 29 | int64 x = ((int64)1 << 63); 30 | snprintf(buf, sizeof(buf), "%"FMT_PRId64, x); 31 | ASSERT(strcmp(buf, "-9223372036854775808")==0, "expected x to be '-9223372036854775808' but was '%s'", buf); 32 | x = ((int64)1 << 62); 33 | snprintf(buf, sizeof(buf), "%"FMT_PRId64, x); 34 | ASSERT(strcmp(buf, "4611686018427387904")==0, "expected x to be '4611686018427387904' but was '%s'", buf); 35 | x = 36 | ((int64)0x7F) << 56 | 37 | ((int64)0xFF) << 48 | 38 | ((int64)0xFF) << 40 | 39 | ((int64)0xFF) << 32 | 40 | ((int64)0xFF) << 24 | 41 | ((int64)0xFF) << 16 | 42 | ((int64)0xFF) << 8 | 43 | ((int64)0xFF) << 0; 44 | snprintf(buf, sizeof(buf), "%"FMT_PRId64, x); 45 | ASSERT(strcmp(buf, "9223372036854775807")==0, "expected x to be '9223372036854775807' but was '%s'", buf); 46 | INFO("TEST %s OK\n", __func__); 47 | } 48 | 49 | int main(int argc, char **argv) { 50 | char dbfile[128]; 51 | strcpy(dbfile, ":memory:"); 52 | for (int i=0 ; i> 24)) & 0xFF; 45 | b[1] = ((byte)(value >> 16)) & 0xFF; 46 | b[2] = ((byte)(value >> 8)) & 0xFF; 47 | b[3] = ((byte)(value >> 0)) & 0xFF; 48 | } 49 | 50 | // dbuf: dynamic byte buffer 51 | 52 | #define MAX_RETAIN 10*1024 53 | 54 | dbuf *dbuf_new() { 55 | dbuf *this = (dbuf *)MEM_MALLOC(sizeof(dbuf)); 56 | this->cap = 64; 57 | this->sz = 0; 58 | this->buf = MEM_MALLOC(this->cap*sizeof(byte)); 59 | this->off = 0; 60 | return this; 61 | } 62 | 63 | void dbuf_free(dbuf *this) { 64 | MEM_FREE(this->buf); 65 | MEM_FREE(this); 66 | } 67 | 68 | void dbuf_reset(dbuf *this) { 69 | if (this->cap > MAX_RETAIN) { 70 | this->cap = MAX_RETAIN; 71 | this->buf = MEM_REALLOC(this->buf, MAX_RETAIN*sizeof(byte)); 72 | } 73 | this->sz = 0; 74 | this->off = 0; 75 | } 76 | 77 | void _dbuf_ensure_cap(dbuf *this, size_t min_cap) { 78 | size_t new_cap = this->cap; 79 | while (new_cap < min_cap) { 80 | new_cap *= 2; 81 | } 82 | if (new_cap <= this->cap) { 83 | return; 84 | } 85 | // INFO("dbuf cap now %d\n", (int)new_cap); 86 | this->buf = MEM_REALLOC(this->buf, new_cap*sizeof(byte)); 87 | } 88 | 89 | void dbuf_write_bytes(dbuf *this, const void *buf, size_t len) { 90 | _dbuf_ensure_cap(this, this->sz + len); 91 | memcpy(&this->buf[this->sz], buf, len); 92 | this->sz += len; 93 | } 94 | 95 | byte *_dbuf_read_bytes(dbuf *this, size_t len) { 96 | size_t left = this->sz - this->off; 97 | ASSERT(left >= len, "dbuf read beyond limit: left=%"FMT_PRIu64", len=%"FMT_PRIu64, left, len); 98 | byte *p = &this->buf[this->off]; 99 | this->off += len; 100 | return p; 101 | } 102 | 103 | void dbuf_write_dbuf(dbuf *this, const dbuf *that) { 104 | dbuf_write_bytes(this, that->buf, that->sz); 105 | } 106 | 107 | void dbuf_write_byte(dbuf *this, byte value) { 108 | byte b = value; 109 | dbuf_write_bytes(this, &b, 1); 110 | } 111 | 112 | byte dbuf_read_byte(dbuf *this) { 113 | byte *p = _dbuf_read_bytes(this, 1); 114 | return *p; 115 | } 116 | 117 | void dbuf_write_bool(dbuf *this, bool value) { 118 | byte b = (value ? 1 :0); 119 | dbuf_write_bytes(this, &b, 1); 120 | } 121 | 122 | bool dbuf_read_bool(dbuf *this) { 123 | byte *p = _dbuf_read_bytes(this, 1); 124 | return *p ? TRUE : FALSE; 125 | } 126 | 127 | void dbuf_write_int32(dbuf *this, int value) { 128 | byte b[4]; 129 | encode_int32(value, b); 130 | dbuf_write_bytes(this, b, 4); 131 | } 132 | 133 | int dbuf_read_int32(dbuf *this) { 134 | byte *p = _dbuf_read_bytes(this, 4); 135 | return decode_int32(p); 136 | } 137 | 138 | void dbuf_write_int64(dbuf *this, int64 value) { 139 | byte b[8]; 140 | b[0] = ((byte)(value >> 56)) & 0xFF; 141 | b[1] = ((byte)(value >> 48)) & 0xFF; 142 | b[2] = ((byte)(value >> 40)) & 0xFF; 143 | b[3] = ((byte)(value >> 32)) & 0xFF; 144 | b[4] = ((byte)(value >> 24)) & 0xFF; 145 | b[5] = ((byte)(value >> 16)) & 0xFF; 146 | b[6] = ((byte)(value >> 8)) & 0xFF; 147 | b[7] = ((byte)(value >> 0)) & 0xFF; 148 | dbuf_write_bytes(this, b, 8); 149 | } 150 | 151 | int64 dbuf_read_int64(dbuf *this) { 152 | byte *p = _dbuf_read_bytes(this, 8); 153 | return 154 | ((int64) p[0]) << 56 | 155 | ((int64) p[1]) << 48 | 156 | ((int64) p[2]) << 40 | 157 | ((int64) p[3]) << 32 | 158 | ((int64) p[4]) << 24 | 159 | ((int64) p[5]) << 16 | 160 | ((int64) p[6]) << 8 | 161 | ((int64) p[7]) << 0 ; 162 | } 163 | 164 | void dbuf_write_string(dbuf *this, const char *str) { 165 | size_t len = strlen(str); 166 | dbuf_write_int32(this, len+1); 167 | dbuf_write_bytes(this, (byte*)str, len+1); 168 | } 169 | 170 | const char *dbuf_read_string(dbuf *this) { 171 | int size = dbuf_read_int32(this); 172 | byte *p = _dbuf_read_bytes(this, size); 173 | return (const char *)p; 174 | } 175 | 176 | void dbuf_write_blob(dbuf *this, const byte *data, int len) { 177 | dbuf_write_int32(this, len); 178 | dbuf_write_bytes(this, data, len); 179 | } 180 | 181 | const byte *dbuf_read_blob(dbuf *this, int *len) { 182 | *len = dbuf_read_int32(this); 183 | const byte *data = _dbuf_read_bytes(this, *len); 184 | return data; 185 | } 186 | 187 | void dbuf_write_double_str(dbuf *this, double value) { 188 | char tmp[64]; 189 | snprintf(tmp, sizeof(tmp)-1, "%g", value); 190 | dbuf_write_string(this, tmp); 191 | } 192 | 193 | double dbuf_read_double_str(dbuf *this) { 194 | const char *str = dbuf_read_string(this); 195 | return strtod(str, NULL); 196 | } 197 | 198 | void dbuf_write_double_ieee(dbuf *this, double value) { 199 | byte *byte_ptr = (byte *)&value; 200 | byte buf[8]; 201 | buf[0] = byte_ptr[7]; 202 | buf[1] = byte_ptr[6]; 203 | buf[2] = byte_ptr[5]; 204 | buf[3] = byte_ptr[4]; 205 | buf[4] = byte_ptr[3]; 206 | buf[5] = byte_ptr[2]; 207 | buf[6] = byte_ptr[1]; 208 | buf[7] = byte_ptr[0]; 209 | dbuf_write_bytes(this, buf, 8); 210 | } 211 | 212 | double dbuf_read_double_ieee(dbuf *this) { 213 | byte *byte_ptr = _dbuf_read_bytes(this, 8); 214 | byte buf[8]; 215 | buf[0] = byte_ptr[7]; 216 | buf[1] = byte_ptr[6]; 217 | buf[2] = byte_ptr[5]; 218 | buf[3] = byte_ptr[4]; 219 | buf[4] = byte_ptr[3]; 220 | buf[5] = byte_ptr[2]; 221 | buf[6] = byte_ptr[1]; 222 | buf[7] = byte_ptr[0]; 223 | double *double_ptr = (double *)buf; 224 | return *double_ptr; 225 | } 226 | -------------------------------------------------------------------------------- /src/mem.h: -------------------------------------------------------------------------------- 1 | #ifndef _MEM_H 2 | #define _MEM_H 3 | 4 | #define MEM_MALLOC(size) mem_malloc(__FILE__, __LINE__, (size)) 5 | #define MEM_REALLOC(ptr, size) mem_realloc(__FILE__, __LINE__, (ptr), (size)) 6 | #define MEM_FREE(p) mem_free(__FILE__, __LINE__, (p)) 7 | 8 | void *mem_malloc(const char* file, int line, size_t size); 9 | void *mem_realloc(const char* file, int line, void *ptr, size_t new_size); 10 | void mem_free(const char* file, int line, void *p); 11 | int mem_usage(); 12 | 13 | int decode_int32(byte b[4]); 14 | void encode_int32(int value, byte b[4]); 15 | 16 | typedef struct { 17 | size_t cap; // capacity of buf 18 | size_t sz; // size of bytes in buf 19 | byte *buf; // byte buffer in memory 20 | int off; // read offset 21 | } dbuf; 22 | 23 | dbuf *dbuf_new(); 24 | void dbuf_free(dbuf *this); 25 | void dbuf_reset(dbuf *this); 26 | void dbuf_write_bytes(dbuf *this, const void *buf, size_t len); 27 | void dbuf_write_dbuf(dbuf *this, const dbuf *that); 28 | void dbuf_write_byte(dbuf *this, byte value); 29 | byte dbuf_read_byte(dbuf *this); 30 | void dbuf_write_bool(dbuf *this, bool value); 31 | bool dbuf_read_bool(dbuf *this); 32 | void dbuf_write_int32(dbuf *this, int value); 33 | int dbuf_read_int32(dbuf *this); 34 | void dbuf_write_int64(dbuf *this, int64 value); 35 | int64 dbuf_read_int64(dbuf *this); 36 | void dbuf_write_string(dbuf *this, const char *str); 37 | const char *dbuf_read_string(dbuf *this); 38 | void dbuf_write_blob(dbuf *this, const byte *data, int len); 39 | const byte *dbuf_read_blob(dbuf *this, int *len); 40 | void dbuf_write_double_str(dbuf *this, double value); 41 | double dbuf_read_double_str(dbuf *this); 42 | void dbuf_write_double_ieee(dbuf *this, double value); 43 | double dbuf_read_double_ieee(dbuf *this); 44 | 45 | #endif // _MEM_H 46 | 47 | -------------------------------------------------------------------------------- /src/mem_test.c: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | #include "mem.h" 3 | #include "mem_test.h" 4 | 5 | void test_dbuf() { 6 | INFO("TEST %s\n", __func__); 7 | dbuf *req = dbuf_new(); 8 | // write bool 9 | dbuf_write_bool(req, TRUE); 10 | // read bool 11 | bool yes = dbuf_read_bool(req); 12 | ASSERT(yes, "want TRUE but have %d", yes); 13 | // write byte 14 | dbuf_write_byte(req, 13); 15 | // read byte 16 | byte thirteen = dbuf_read_byte(req); 17 | ASSERT(thirteen==13, "want 13 but have %d", thirteen); 18 | // write int32 19 | dbuf_write_int32(req, 2<<30); 20 | // read int32 21 | int giga= dbuf_read_int32(req); 22 | ASSERT(giga==2<<30, "want 2<<30 but have %d", giga); 23 | // write double as string 24 | dbuf_write_double_str(req, 0.02); 25 | // read double as string 26 | double dbl_str = dbuf_read_double_str(req); 27 | ASSERT(dbl_str==0.02, "want 0.02 but have %g", dbl_str); 28 | // write double as ieee 29 | dbuf_write_double_ieee(req, 128.5); 30 | // read double as ieee 31 | double dbl_ieee = dbuf_read_double_ieee(req); 32 | ASSERT(dbl_ieee==128.5, "want 128.5 but have %g", dbl_ieee); 33 | // write string 34 | dbuf_write_string(req, "Hello"); 35 | // read string 36 | const char *str = dbuf_read_string(req); 37 | ASSERT(strcmp(str, "Hello") == 0, "want 'Hello' but have '%s'", str); 38 | // check memory 39 | ASSERT(req->sz == 33, "wrong req->sz %d", req->sz); 40 | ASSERT(req->off == 33, "wrong req->off %d", req->off); 41 | // bool 42 | ASSERT(req->buf[ 0] == 0x01, "wrong mem %02X", req->buf[ 0]); // bool 43 | // byte 44 | ASSERT(req->buf[ 1] == 0x0D, "wrong mem %02X", req->buf[ 1]); // byte 45 | // int32 46 | ASSERT(req->buf[ 2] == 0x80, "wrong mem %02X", req->buf[ 2]); // int32 47 | ASSERT(req->buf[ 3] == 0x00, "wrong mem %02X", req->buf[ 3]); 48 | ASSERT(req->buf[ 4] == 0x00, "wrong mem %02X", req->buf[ 4]); 49 | ASSERT(req->buf[ 5] == 0x00, "wrong mem %02X", req->buf[ 5]); 50 | // double_str 51 | ASSERT(req->buf[ 6] == 0x00, "wrong mem %02X", req->buf[ 6]); // double_str (len) 52 | ASSERT(req->buf[ 7] == 0x00, "wrong mem %02X", req->buf[ 7]); 53 | ASSERT(req->buf[ 8] == 0x00, "wrong mem %02X", req->buf[ 8]); 54 | ASSERT(req->buf[ 9] == 0x05, "wrong mem %02X", req->buf[ 9]); 55 | ASSERT(req->buf[10] == 0x30, "wrong mem %02X", req->buf[10]); // double_str (content) 56 | ASSERT(req->buf[11] == 0x2E, "wrong mem %02X", req->buf[11]); 57 | ASSERT(req->buf[12] == 0x30, "wrong mem %02X", req->buf[12]); 58 | ASSERT(req->buf[13] == 0x32, "wrong mem %02X", req->buf[13]); 59 | ASSERT(req->buf[14] == 0x00, "wrong mem %02X", req->buf[14]); 60 | // double_ieee 61 | ASSERT(req->buf[15] == 0x40, "wrong mem %02X", req->buf[15]); 62 | ASSERT(req->buf[16] == 0x60, "wrong mem %02X", req->buf[16]); 63 | ASSERT(req->buf[17] == 0x10, "wrong mem %02X", req->buf[17]); 64 | ASSERT(req->buf[18] == 0x00, "wrong mem %02X", req->buf[18]); 65 | ASSERT(req->buf[19] == 0x00, "wrong mem %02X", req->buf[19]); 66 | ASSERT(req->buf[20] == 0x00, "wrong mem %02X", req->buf[20]); 67 | ASSERT(req->buf[21] == 0x00, "wrong mem %02X", req->buf[21]); 68 | ASSERT(req->buf[22] == 0x00, "wrong mem %02X", req->buf[22]); 69 | // string 70 | ASSERT(req->buf[23] == 0x00, "wrong mem %02X", req->buf[23]); 71 | ASSERT(req->buf[24] == 0x00, "wrong mem %02X", req->buf[24]); 72 | ASSERT(req->buf[25] == 0x00, "wrong mem %02X", req->buf[25]); 73 | ASSERT(req->buf[26] == 0x06, "wrong mem %02X", req->buf[26]); 74 | ASSERT(req->buf[27] == 0x48, "wrong mem %02X", req->buf[27]); 75 | ASSERT(req->buf[28] == 0x65, "wrong mem %02X", req->buf[28]); 76 | ASSERT(req->buf[29] == 0x6C, "wrong mem %02X", req->buf[29]); 77 | ASSERT(req->buf[30] == 0x6C, "wrong mem %02X", req->buf[30]); 78 | ASSERT(req->buf[31] == 0x6F, "wrong mem %02X", req->buf[31]); 79 | ASSERT(req->buf[32] == 0x00, "wrong mem %02X", req->buf[32]); 80 | // cleanup 81 | dbuf_free(req); 82 | INFO("TEST %s OK\n", __func__); 83 | } 84 | 85 | // see https://en.wikipedia.org/wiki/Double-precision_floating-point_format 86 | void test_dbuf_double() { 87 | INFO("TEST %s\n", __func__); 88 | dbuf *req = dbuf_new(); 89 | // write 90 | dbuf_write_double_ieee(req, -2); 91 | ASSERT(req->sz == 8, "wrong %d", req->sz); 92 | ASSERT(req->buf[0] == 0xC0, "wrong %02X", req->buf[0]); 93 | ASSERT(req->buf[1] == 0x00, "wrong %02X", req->buf[1]); 94 | ASSERT(req->buf[2] == 0x00, "wrong %02X", req->buf[2]); 95 | ASSERT(req->buf[3] == 0x00, "wrong %02X", req->buf[3]); 96 | ASSERT(req->buf[4] == 0x00, "wrong %02X", req->buf[4]); 97 | ASSERT(req->buf[5] == 0x00, "wrong %02X", req->buf[5]); 98 | ASSERT(req->buf[6] == 0x00, "wrong %02X", req->buf[6]); 99 | ASSERT(req->buf[7] == 0x00, "wrong %02X", req->buf[7]); 100 | // write 101 | dbuf_write_double_ieee(req, 2); 102 | ASSERT(req->sz == 16, "wrong %d", req->sz); 103 | ASSERT(req->buf[ 8] == 0x40, "wrong %02X", req->buf[ 8]); 104 | ASSERT(req->buf[ 9] == 0x00, "wrong %02X", req->buf[ 9]); 105 | ASSERT(req->buf[10] == 0x00, "wrong %02X", req->buf[10]); 106 | ASSERT(req->buf[11] == 0x00, "wrong %02X", req->buf[11]); 107 | ASSERT(req->buf[12] == 0x00, "wrong %02X", req->buf[12]); 108 | ASSERT(req->buf[13] == 0x00, "wrong %02X", req->buf[13]); 109 | ASSERT(req->buf[14] == 0x00, "wrong %02X", req->buf[14]); 110 | ASSERT(req->buf[15] == 0x00, "wrong %02X", req->buf[15]); 111 | // write 112 | dbuf_write_double_ieee(req, 128.5); 113 | ASSERT(req->sz == 24, "wrong %d", req->sz); 114 | ASSERT(req->buf[16] == 0x40, "wrong %02X", req->buf[16]); 115 | ASSERT(req->buf[17] == 0x60, "wrong %02X", req->buf[17]); 116 | ASSERT(req->buf[18] == 0x10, "wrong %02X", req->buf[18]); 117 | ASSERT(req->buf[19] == 0x00, "wrong %02X", req->buf[19]); 118 | ASSERT(req->buf[20] == 0x00, "wrong %02X", req->buf[20]); 119 | ASSERT(req->buf[21] == 0x00, "wrong %02X", req->buf[21]); 120 | ASSERT(req->buf[22] == 0x00, "wrong %02X", req->buf[22]); 121 | ASSERT(req->buf[23] == 0x00, "wrong %02X", req->buf[23]); 122 | // free 123 | dbuf_free(req); 124 | INFO("TEST %s OK\n", __func__); 125 | } 126 | 127 | void bench_dbuf(int nrounds) { 128 | INFO("BENCH %s\n", __func__); 129 | DEBUG(" nrounds = %d\n", nrounds); 130 | double tstart = mono_time(); 131 | for (int r=0 ; r 2 | #include 3 | #include "util.h" 4 | 5 | void log_info(const char *file, int line, const char *fmt, ...) { 6 | // fprintf(stderr, "INFO %25s:%-3d ", file, line); 7 | fprintf(stderr, "INFO [%s:%d] ", file, line); 8 | va_list args; 9 | va_start(args, fmt); 10 | vfprintf(stderr, fmt, args); 11 | va_end(args); 12 | fflush(stderr); 13 | } 14 | 15 | void log_debug(const char *file, int line, const char *fmt, ...) { 16 | // fprintf(stderr, "DEBUG %25s:%-3d ", file, line); 17 | fprintf(stderr, "DEBUG [%s:%d] ", file, line); 18 | va_list args; 19 | va_start(args, fmt); 20 | vfprintf(stderr, fmt, args); 21 | va_end(args); 22 | fflush(stderr); 23 | } 24 | 25 | void assert(const char *file, int line, bool condition, const char *fmt, ...) { 26 | if (condition) { 27 | return; 28 | } 29 | fprintf(stderr, "ASSERTION FAILED %s:%d ", file, line); 30 | va_list args; 31 | va_start(args, fmt); 32 | vfprintf(stderr, fmt, args); 33 | va_end(args); 34 | fflush(stderr); 35 | exit(1); 36 | } 37 | 38 | double mono_time() { 39 | clock_t now = clock(); 40 | double sec = ((double)now) / CLOCKS_PER_SEC; 41 | return sec; 42 | } 43 | 44 | double mono_since(double then) { 45 | return mono_time() - then; 46 | } 47 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | #ifndef _UTIL_H 2 | #define _UTIL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | // versions 9 | 10 | #define SQINN_VERSION "1.1.42" // sync version in README 11 | #define IO_VERSION 1 12 | 13 | // types 14 | 15 | typedef unsigned char byte; 16 | typedef long long int int64; 17 | typedef char bool; 18 | #define TRUE 1 19 | #define FALSE 0 20 | 21 | // printf int64 22 | #if defined(_WIN32) || defined(__MINGW64__) 23 | #define FMT_PRId64 "I64d" 24 | #define FMT_PRIu64 "I64u" 25 | #else 26 | #define FMT_PRId64 "lld" 27 | #define FMT_PRIu64 "llu" 28 | #endif 29 | 30 | // logging 31 | 32 | void log_info(const char *file, int line, const char *fmt, ...); 33 | void log_debug(const char *file, int line, const char *fmt, ...); 34 | 35 | #define INFO(fmt, ...) log_info(__FILE__, __LINE__, fmt, __VA_ARGS__) 36 | #define DEBUG(fmt, ...) log_debug(__FILE__, __LINE__, fmt, __VA_ARGS__) 37 | 38 | // assert 39 | 40 | void assert(const char *file, int line, bool condition, const char *fmt, ...); 41 | 42 | #define ASSERT(c, fmt, ...) assert(__FILE__, __LINE__, !!(c), fmt, __VA_ARGS__) 43 | #define ASSERT0(c, fmt) assert(__FILE__, __LINE__, !!(c), fmt) 44 | 45 | // clock 46 | 47 | double mono_time(); 48 | double mono_since(double then); 49 | 50 | #endif // _UTIL_H 51 | 52 | --------------------------------------------------------------------------------