├── .gitattributes ├── .github └── workflows │ └── tg-send.yml ├── LICENSE ├── README.md ├── README_EN.md ├── examples ├── file │ └── file.ino └── test │ └── test.ino ├── keywords.txt ├── library.properties └── src ├── DBConnector.h ├── GyverDB.h ├── GyverDBFile.h └── utils ├── access.h ├── anytype.h ├── block.h ├── entry.h └── types.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/tg-send.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Telegram Message 3 | on: 4 | release: 5 | types: [published] 6 | jobs: 7 | build: 8 | name: Send Message 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: send telegram message on push 12 | uses: appleboy/telegram-action@master 13 | with: 14 | to: ${{ secrets.TELEGRAM_TO }} 15 | token: ${{ secrets.TELEGRAM_TOKEN }} 16 | disable_web_page_preview: true 17 | message: | 18 | ${{ github.event.repository.name }} v${{ github.event.release.tag_name }} 19 | ${{ github.event.release.body }} 20 | https://github.com/${{ github.repository }} 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 AlexGyver 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![latest](https://img.shields.io/github/v/release/GyverLibs/GyverDB.svg?color=brightgreen)](https://github.com/GyverLibs/GyverDB/releases/latest/download/GyverDB.zip) 2 | [![PIO](https://badges.registry.platformio.org/packages/gyverlibs/library/GyverDB.svg)](https://registry.platformio.org/libraries/gyverlibs/GyverDB) 3 | [![Foo](https://img.shields.io/badge/Website-AlexGyver.ru-blue.svg?style=flat-square)](https://alexgyver.ru/) 4 | [![Foo](https://img.shields.io/badge/%E2%82%BD%24%E2%82%AC%20%D0%9F%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B6%D0%B0%D1%82%D1%8C-%D0%B0%D0%B2%D1%82%D0%BE%D1%80%D0%B0-orange.svg?style=flat-square)](https://alexgyver.ru/support_alex/) 5 | [![Foo](https://img.shields.io/badge/README-ENGLISH-blueviolet.svg?style=flat-square)](https://github-com.translate.goog/GyverLibs/GyverDB?_x_tr_sl=ru&_x_tr_tl=en) 6 | 7 | [![Foo](https://img.shields.io/badge/ПОДПИСАТЬСЯ-НА%20ОБНОВЛЕНИЯ-brightgreen.svg?style=social&logo=telegram&color=blue)](https://t.me/GyverLibs) 8 | 9 | # GyverDB 10 | Простая база данных для Arduino: 11 | - Хранение данных в парах ключ-значение 12 | - Поддерживает все целочисленные типы, float, строки и бинарные данные 13 | - Быстрая автоматическая конвертация данных между разными типами 14 | - Быстрый доступ благодаря хэш ключам и бинарному поиску - в 10 раз быстрее библиотеки [Pairs](https://github.com/GyverLibs/Pairs) и в 11 раз быстрее Preferences (ESP32) 15 | - Компактная реализация - 8 байт на одну ячейку 16 | - Встроенный механизм автоматической записи на флешку ESP8266/ESP32 17 | 18 | ### Совместимость 19 | Совместима со всеми Arduino платформами (используются Arduino-функции) 20 | 21 | ### Зависимости 22 | - [StreamIO](https://github.com/GyverLibs/StreamIO) 23 | - [GTL](https://github.com/GyverLibs/GTL) v1.0.6+ 24 | - [StringUtils](https://github.com/GyverLibs/StringUtils) v1.4.15+ 25 | - [FOR_MACRO](https://github.com/GyverLibs/FOR_MACRO) v1.0.0+ 26 | 27 | ## Содержание 28 | - [Документация](#docs) 29 | - [Использование](#usage) 30 | - [Версии](#versions) 31 | - [Установка](#install) 32 | - [Баги и обратная связь](#feedback) 33 | 34 | 35 | 36 | ## Документация 37 | Настройки компиляции перед подключением библиотеки 38 | ```cpp 39 | #define DB_NO_UPDATES // убрать стек обновлений 40 | #define DB_NO_FLOAT // убрать поддержку float 41 | #define DB_NO_INT64 // убрать поддержку int64 42 | #define DB_NO_CONVERT // не конвертировать данные (принудительно менять тип ячейки, keepTypes не работает) 43 | ``` 44 | 45 | ### GyverDB 46 | ```cpp 47 | // конструктор 48 | // можно зарезервировать ячейки 49 | GyverDB(uint16_t reserve = 0); 50 | 51 | // не изменять тип ячейки (конвертировать данные если тип отличается) (умолч. true) 52 | void keepTypes(bool keep); 53 | 54 | // было изменение бд 55 | bool changed(); 56 | 57 | // сбросить флаг изменения бд 58 | void clearChanged(); 59 | 60 | // вывести всё содержимое БД 61 | void dump(Print& p); 62 | 63 | // полный вес БД 64 | size_t size(); 65 | 66 | // экспортный размер БД (для writeTo) 67 | size_t writeSize(); 68 | 69 | // экспортировать БД в Stream (напр. файл) 70 | bool writeTo(Stream& stream); 71 | 72 | // экспортировать БД в буфер размера writeSize() 73 | bool writeTo(uint8_t* buffer); 74 | 75 | // импортировать БД из Stream (напр. файл) 76 | bool readFrom(Stream& stream, size_t len); 77 | 78 | // импортировать БД из буфера 79 | bool readFrom(const uint8_t* buffer, size_t len); 80 | 81 | // создать ячейку. Если существует - перезаписать пустой с новым типом 82 | bool create(size_t hash, gdb::Type type, uint16_t reserve = 0); 83 | 84 | // полностью освободить память 85 | void reset(); 86 | 87 | // стереть все ячейки (не освобождает зарезервированное место) 88 | void clear(); 89 | 90 | // удалить из БД ячейки, ключей которых нет в переданном списке 91 | void cleanup(size_t* hashes, size_t len); 92 | 93 | // вывести все ключи в массив длиной length() 94 | void getKeys(size_t* hashes); 95 | 96 | // получить ячейку 97 | gdb::Entry get(size_t hash); 98 | gdb::Entry get(const Text& key); 99 | 100 | // получить ячейку по порядку 101 | gdb::Entry getN(int idx); 102 | 103 | // удалить ячейку 104 | void remove(size_t hash); 105 | void remove(const Text& key); 106 | 107 | // БД содержит ячейку с именем 108 | bool has(size_t hash); 109 | bool has(const Text& key); 110 | 111 | // записать данные (создать ячейку, если не существует). DATA - любой тип данных 112 | bool set(size_t hash, DATA data); 113 | bool set(const Text& key, DATA data); 114 | 115 | // инициализировать данные (создать ячейку и записать, если ячейка не существует). DATA - любой тип данных 116 | bool init(size_t hash, DATA data); 117 | bool init(const Text& key, DATA data); 118 | 119 | // обновить данные (если ячейка существует). DATA - любой тип данных 120 | bool update(size_t hash, DATA data); 121 | bool update(const Text& key, DATA data); 122 | 123 | // использовать стек обновлений (умолч. false) 124 | void useUpdates(bool use); 125 | 126 | // есть непрочитанные изменения 127 | bool updatesAvailable(); 128 | 129 | // пропустить необработанные обновления 130 | void skipUpdates(); 131 | 132 | // получить хеш обновления из стека 133 | size_t updateNext(); 134 | ``` 135 | 136 | ### GyverDBFile 137 | Данный класс наследует GyverDB, но умеет самостоятельно записываться в файл на флешку ESP при любом изменении и по истечении таймаута. 138 | 139 | ```cpp 140 | GyverDBFile(fs::FS* nfs = nullptr, const char* path = nullptr, uint32_t tout = 10000); 141 | 142 | // установить файловую систему и имя файла 143 | void setFS(fs::FS* nfs, const char* path); 144 | 145 | // установить таймаут записи, мс (умолч. 10000) 146 | void setTimeout(uint32_t tout = 10000); 147 | 148 | // прочитать данные 149 | bool begin(); 150 | 151 | // обновить данные в файле, если было изменение БД. Вернёт true при успешной записи 152 | bool update(); 153 | 154 | // тикер, вызывать в loop. Сам обновит данные при изменении и выходе таймаута, вернёт true 155 | bool tick(); 156 | ``` 157 | 158 | Для использования нужно запустить FS и вызывать тикер в loop: 159 | 160 | ```cpp 161 | #include 162 | #include 163 | GyverDBFile db(&LittleFS, "data.db"); 164 | 165 | void setup() { 166 | LittleFS.begin(); 167 | db.begin(); // прочитать данные из файла 168 | 169 | // для работы в таком режиме пригодится метод init(): 170 | // создаёт ячейку соответствующего типа и записывает "начальные" данные, 171 | // если такой ячейки ещё нет в БД 172 | db.init("key", 123); // int 173 | db.init("fl", 3.14); // float 174 | db.init("str", "init"); // строка 175 | } 176 | void loop() { 177 | db.tick(); 178 | } 179 | ``` 180 | 181 | - При любом изменении в БД она сама запишется в файл после выхода таймаута 182 | - БД находится в оперативной памяти для быстрого доступа, она читается из файла только при вызове `begin` 183 | - Расширение файла не важно - это больше подсказка для пользователя, что данный файл хранит БД. Файл содержит БД в *бинарном виде* - её нельзя редактировать через блокнот! 184 | 185 | ### Типы ячеек gdb::Type 186 | ```cpp 187 | None 188 | Int 189 | Uint 190 | Int64 191 | Uint64 192 | Float 193 | String 194 | Bin 195 | ``` 196 | 197 | ### Entry 198 | > Наследует класс [Text](https://github.com/GyverLibs/StringUtils?tab=readme-ov-file#text) для более удобного чтения строк 199 | 200 | ```cpp 201 | // тип ячейки 202 | gdb::Type type(); 203 | 204 | // вывести данные в буфер размера size(). Не добавляет 0-терминатор, если это строка 205 | void writeBytes(void* buf); 206 | 207 | // вывести в переменную 208 | bool writeTo(T& dest); 209 | 210 | Value toText(); 211 | String toString(); 212 | bool toBool(); 213 | int32_t toInt(); 214 | int64_t toInt64(); 215 | double toFloat(); 216 | ``` 217 | 218 | 219 | 220 | ## Использование 221 | GyverDB - динамическая база данных (БД), которая хранит данные в формате ключ-значение. По ключу можно записать данные в ячейку и прочитать их из неё: 222 | 223 | - Ключ - 29 бит число, по сути БД это массив на 2^29 ячеек 224 | - Значение - данные любого типа: числа, строки, любые бинарные данные 225 | 226 | ```cpp 227 | db[0] = 123; 228 | db[2] = 3.14; 229 | db[100] = "hello"; 230 | ``` 231 | 232 | Пока в ячейку ничего не записано - она не существует и не занимает память. К ключу следует относиться как к **уникальному идентификатору ячейки**, а не как к порядковому номеру в массиве - индексу. 233 | 234 | ### Ключи 235 | Для повышения читаемости кода вместо номеров ячеек удобнее использовать константы, например `enum`. Это очень удобно, потому что IDE подскажет список имеющихся ключей при вводе `keys::`, а значения подставит компилятор: 236 | 237 | ```cpp 238 | enum keys : size_t { 239 | key1, 240 | key2, 241 | mykey, 242 | lolkek, 243 | }; 244 | 245 | db[keys::key1] = 123; 246 | db[keys::key2] = 3.14; 247 | db[keys::mykey] = "hello"; 248 | ``` 249 | 250 | При активной разработке и хранении БД в энергонезависимой памяти (в файле, чтение при загрузке МК) данный подход неудобен, т.к. удаление или добавление ключа в середине списка приведёт к смещению нумерации и под старыми ключами окажутся новые данные. Для сохранения читаемости и уникальности каждого ключа можно использовать хэш-строки - строка при помощи специальной функции преобразуется в число, которое соответствует только этой строке. Данная возможность встроена в GyverDB - можно обращаться к ячейкам по строковому ключу: 251 | 252 | ```cpp 253 | db["key1"] = 123; 254 | ``` 255 | 256 | Для ускорения и облегчения кода можно использовать внешнюю хэш-функцию, которая выполняется на этапе компиляции и сразу превращается в число. Вместе с GyverDB идёт несколько вариантов, они равноценны: 257 | 258 | ```cpp 259 | db[SH("key1")] = 123; 260 | db["key2"_h] = 3.14; 261 | db[H(mykey)] = "hello"; 262 | ``` 263 | 264 | В этом случае enum тоже можно использовать для подсказок IDE, но чуть в другом виде: 265 | 266 | ```cpp 267 | // enum с хэшами 268 | enum keys : size_t { 269 | key1 = "key1"_h, 270 | key2 = SH("key2"), 271 | mykey = H(mykey), 272 | }; 273 | 274 | db[keys::key1] = 123; 275 | db[keys::key2] = 3.14; 276 | db[keys::mykey] = "hello"; 277 | ``` 278 | 279 | Теперь enum хранит хэши и не боится удаления или добавления ключей - они уникальны. Для более короткой записи в библиотеке есть удобный макрос: 280 | 281 | ```cpp 282 | DB_KEYS(keys, 283 | key1, 284 | key2, 285 | mykey // последняя запятая не ставится 286 | ); 287 | ``` 288 | 289 | Он развернётся в такой же хэш-enum как в примере выше. Рекомендуется использовать этот вариант как самый удобный и оптимальный. 290 | 291 | > Есть ещё `DB_KEYS_CLASS` - он создаёт `enum class`. Но такие константы нужно будет вручную кастовать к `size_t` 292 | 293 | #### Запись и чтение 294 | ```cpp 295 | GyverDB db; 296 | 297 | // ЗАПИСЬ 298 | // напрямую. При создании ячейка получит тип Int 299 | db["key1"] = 123; 300 | 301 | // эта ячейка у нас int, текст сконвертируется в число 302 | db["key1"] = "123456"; 303 | 304 | // чуть эффективнее записывать через set 305 | db.set("key1", 123321); 306 | db.set("key2", "3.14"); 307 | ``` 308 | ```cpp 309 | // ЧТЕНИЕ 310 | // ячейка сама конвертируется в тип, который стоит слева от знака = 311 | int i = db["key1"]; 312 | float f = db.get("key2"); // чуть эффективнее читать через get 313 | 314 | // любые данные "печатаются" в Print, даже бинарные 315 | Serial.println(db["key3"]); 316 | 317 | // можно сконвертировать в конкретный тип 318 | i = db["key1"].toInt(); 319 | i = db["key2"].toBool(); 320 | f = db["key3"].toFloat(); 321 | 322 | // можно сравнивать с числами 323 | db["key1"] == 123; 324 | db["key1"] >= 123; 325 | 326 | // для чисел работают составные операторы и инкремент/декремент 327 | db["key1"]++; 328 | db["key1"] += 10; 329 | db["key1"] &= 0x12; 330 | 331 | // записи типа String можно сравнивать со строками 332 | db["key2"] == "str"; 333 | 334 | // но можно и вот так, для любых типов ячеек 335 | // toText() конвертирует все типы ячеек БД во временную строку 336 | db["key1"].toText() == "12345"; 337 | ``` 338 | ```cpp 339 | // БИНАРНЫЕ 340 | // GyverDB может записать данные любого типа, даже составные (массивы, структуры) 341 | uint8_t arr[5] = {1, 2, 3, 4, 5}; 342 | db["arr"] = arr; 343 | 344 | // вывод обратно. Тип должен иметь такой же размер! 345 | uint8_t arr2[5]; 346 | db["arr"].writeTo(arr2); 347 | 348 | // вывод всей БД в Print 349 | db.dump(Serial); 350 | ``` 351 | ```cpp 352 | // СТРУКТУРЫ 353 | struct Foo { 354 | int a; 355 | float b; 356 | }; 357 | 358 | Foo foo{123, 3.14}; 359 | db["struct"] = foo; 360 | 361 | // чтение в копию 362 | Foo foo2; 363 | db["struct"].writeTo(foo2); 364 | Serial.println(foo2.b); 365 | 366 | // чтение напрямую 367 | Serial.println(static_cast(db["struct"].buffer())->a); // 123 368 | 369 | Foo& ref = *static_cast(db["struct"].buffer()); // 3.14 370 | Serial.println(ref.b); 371 | 372 | // массив структур 373 | Foo arr[] = {{123, 3.14}, {456, 2.72}}; 374 | db["arr"] = arr; 375 | 376 | Foo* p = (Foo*)db["arr"].buffer(); 377 | Serial.println(p[0].a); // 123 378 | Serial.println(p[1].b); // 2.72 379 | ``` 380 | 381 | При разработке проекта может оказаться так, что некоторые ключи "устарели" или были переименованы в процессе разработки, и ячейки по ним уже не нужны. В библиотеке есть возможность провести очистку БД: удалить все лишние ячейки и оставить только заданный список ключей. Это делается так: 382 | ```cpp 383 | // список ключей, которые надо оставить. В формате size_t в любом виде 384 | size_t hashes[] = {SH("key1"), "key2"_h, kesy::key3}; 385 | 386 | // очищаем 387 | db.cleanup(hashes, 3); 388 | 389 | // в БД останутся только ячейки, соответствующие указанным выше ключам 390 | ``` 391 | 392 | Есть 4 варианта записи в ячейку: 393 | 394 | - `create(ключ, тип)` - создать пустую ячейку указанного типа. Если ячейка с таким ключом существует - очистить и сменить тип 395 | - `init(ключ, значение)` - создать ячейку с указанным значением, если ячейки с таким ключом нет или она имеет другой тип данных. Удобно для задания начальных значений в GyverDBFile 396 | - `update(ключ, значение)` - обновить данные, если ячейка с таким ключом существует 397 | - `set(ключ, значение)` - записать данные, создав ячейку если она не существует. Аналог `db[ключ] = значение` 398 | 399 | Для инициализации можно использовать более короткий макрос: 400 | 401 | ```cpp 402 | DB_INIT( 403 | db, 404 | (keys::key1, 123), 405 | (keys::key2, 3.14), 406 | ("key3", 123321ull), 407 | ("key4", "abc") 408 | ); 409 | ``` 410 | 411 | ### Примечания 412 | - GyverDB хранит целые до 32 бит и float числа в памяти самой ячейки. 64-битные числа, строки и бинарные данные выделяются динамически 413 | - Ради компактности используется 29-битное хэширование. Этого должно хватать более чем, шанс коллизий крайне мал 414 | - Библиотека автоматически выбирает тип при записи в ячейку. Приводите тип вручную, если это нужно (например `db["key"] = 12345ull`) 415 | - По умолчанию включен параметр `keepTypes()` - сохранять тип ячейки при перезаписи. Это означает, что если ячейка была int, то при записи в неё данных другого типа они будут автоматически конвертироваться в int, даже если это строка. И наоборот 416 | - При создании пустой ячейки можно указать тип и зарезервировать место (только для строк и бинарных данных) `db.create("kek", gdb::Type::String, 100)` 417 | - `Entry` имеет автоматический доступ к строке как оператор `String`, это означает что ячейки с текстовым типом (String) можно передавать в функции, которые принимают `String`, например `WiFi.begin(db["wifi_ssid"], db["wifi_pass"]);` 418 | - Если нужно передать ячейку в функцию, принимающую `const char*` - используйте на ней `c_str()`. Это не продублирует строку в памяти, а даст к ней прямой доступ. Например `foo(db["str"].c_str())` 419 | 420 | 421 | 422 | ## Версии 423 | - v1.0 424 | - v1.0.1 упразднены целые типы 8 и 16 бит, увеличено разрешение хэша 425 | - v1.2.1 426 | 427 | 428 | ## Установка 429 | - Библиотеку можно найти по названию **GyverDB** и установить через менеджер библиотек в: 430 | - Arduino IDE 431 | - Arduino IDE v2 432 | - PlatformIO 433 | - [Скачать библиотеку](https://github.com/GyverLibs/GyverDB/archive/refs/heads/main.zip) .zip архивом для ручной установки: 434 | - Распаковать и положить в *C:\Program Files (x86)\Arduino\libraries* (Windows x64) 435 | - Распаковать и положить в *C:\Program Files\Arduino\libraries* (Windows x32) 436 | - Распаковать и положить в *Документы/Arduino/libraries/* 437 | - (Arduino IDE) автоматическая установка из .zip: *Скетч/Подключить библиотеку/Добавить .ZIP библиотеку…* и указать скачанный архив 438 | - Читай более подробную инструкцию по установке библиотек [здесь](https://alexgyver.ru/arduino-first/#%D0%A3%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0_%D0%B1%D0%B8%D0%B1%D0%BB%D0%B8%D0%BE%D1%82%D0%B5%D0%BA) 439 | ### Обновление 440 | - Рекомендую всегда обновлять библиотеку: в новых версиях исправляются ошибки и баги, а также проводится оптимизация и добавляются новые фичи 441 | - Через менеджер библиотек IDE: найти библиотеку как при установке и нажать "Обновить" 442 | - Вручную: **удалить папку со старой версией**, а затем положить на её место новую. "Замену" делать нельзя: иногда в новых версиях удаляются файлы, которые останутся при замене и могут привести к ошибкам! 443 | 444 | 445 | 446 | ## Баги и обратная связь 447 | При нахождении багов создавайте **Issue**, а лучше сразу пишите на почту [alex@alexgyver.ru](mailto:alex@alexgyver.ru) 448 | Библиотека открыта для доработки и ваших **Pull Request**'ов! 449 | 450 | При сообщении о багах или некорректной работе библиотеки нужно обязательно указывать: 451 | - Версия библиотеки 452 | - Какой используется МК 453 | - Версия SDK (для ESP) 454 | - Версия Arduino IDE 455 | - Корректно ли работают ли встроенные примеры, в которых используются функции и конструкции, приводящие к багу в вашем коде 456 | - Какой код загружался, какая работа от него ожидалась и как он работает в реальности 457 | - В идеале приложить минимальный код, в котором наблюдается баг. Не полотно из тысячи строк, а минимальный код 458 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | This is an automatic translation, may be incorrect in some places. See sources and examples! 2 | 3 | # Gyverdb 4 | Simple database for Arduino: 5 | - storage of data in the key pairs- value 6 | - supports all integer types, Float, lines and binary data 7 | - Fast automatic data conversion between different types 8 | - Fast access thanks to the hash keys and binary search - 10 times faster than the library [PAIRS] (https://github.com/gyverlibs/pairs) 9 | - compact implementation - 8 bytes per cell 10 | - Built -in automatic recording mechanism for ESP8266/ESP32 flash drive 11 | 12 | ## compatibility 13 | Compatible with all arduino platforms (used arduino functions) 14 | 15 | ### Dependencies 16 | - [gtl] (https://github.com/gyverlibs/gtl) v1.0.6+ 17 | - [Stringutils] (https://github.com/gyverlibs/stringutils) v1.4.15+ 18 | 19 | ## Content 20 | - [use] (#usage) 21 | - [versions] (#varsions) 22 | - [installation] (# Install) 23 | - [bugs and feedback] (#fedback) 24 | 25 | 26 | 27 | ## Usage 28 | Compilation settings before connecting the library 29 | `` `CPP 30 | #define db_no_updates // Remove glass updates 31 | #define db_no_float // Remove support for Float 32 | #define db_no_int64 // Remove support for int64 33 | #define db_no_convert // Do not convert data (forcibly change the type of record, KeeptyPes does not work) 34 | `` ` 35 | 36 | ## gyverdb 37 | `` `CPP 38 | // Designer 39 | // you can reserve cells 40 | Gyverdb (uint16_t reserve = 0); 41 | 42 | 43 | // Do not change the type of recording (convert data if the type is different) (silence. True) 44 | VOID Keeptypes (Bool Keep); 45 | 46 | // Use the glass updates (silence. FALSE) 47 | VOID USEUPDATES (BOOL USE); 48 | 49 | // there was a change in data.After the response, it will drop in FALSE 50 | Bool Changed (); 51 | 52 | // Determine all the contents of the database 53 | VOID DUMP (Print & P); 54 | 55 | // Full weight of the database 56 | size_t size (); 57 | 58 | // Export size of the database (for writeto) 59 | Size_t Writesize (); 60 | 61 | // export the database in Stream (e.g. file) 62 | Bool Writeto (Stream & Stream); 63 | 64 | // export the database in the size of the size of writesize () 65 | Bool Writeto (Uint8_t* Buffer); 66 | 67 | // import a database from Stream (e.g. file) 68 | Bool Readfrom (Stream & Stream, Size_t Len); 69 | 70 | // import a databar from the buffer 71 | Bool Readfrom (Consta Uint8_t* Buffer, Size_t Len); 72 | 73 | // Create a record.If exists - rewrite empty with a new type 74 | Bool Create (Size_t Hash, GDB :: Type Type, Uint16_t Reserv = 0); 75 | 76 | // completely free memory 77 | VOID Reset (); 78 | 79 | // erase all notes (does not free up the reserved place) 80 | Void Clear (); 81 | 82 | // get an entry 83 | GDB :: Entry Get (Size_t Hash); 84 | GDB :: Entry Get (Constra Text & Key); 85 | 86 | // Delete the recording 87 | VOID REMOVE (SIZE_T HASH); 88 | VOID Remove (Const Text & Key); 89 | 90 | // database contains an entry with the name 91 | Bool has (size_t hash); 92 | Bool Has (Consta Text & Key); 93 | 94 | // Write data.Data - Any Type of Data 95 | Bool Set (Size_t Hash, Data); 96 | Bool Set (Consta Text & Key Hash, Data); 97 | 98 | // initialize the data (create a cell and write down if it is not).Data - Any Type of Data 99 | Bool Set (Size_t Hash, Data); 100 | Bool Set (Consta Text & Key Hash, Data); 101 | `` ` 102 | 103 | ## gyverdbfile 104 | This class inherits Gyverdb, but knows how to independently sign up for an ESP flash drive with any change and after the time of the time 105 | `` `CPP 106 | Gyverdbfile (fs :: fs* nfs = nullptr, const char* PATH = NULLPTR, UINT32_T TOUT = 10000); 107 | 108 | // Installsystem and file name 109 | VOID setfs (fs :: fs* nfs, const char* Path); 110 | 111 | // Install the Takeout of the Records, MS (silence 1000) 112 | VOID settimeout (uint32_t tout = 10000); 113 | 114 | // Read the data 115 | Bool Begin (); 116 | 117 | // update data in the file now 118 | Bool update (); 119 | 120 | // ticker, call in LOOP.He will update the data himself when the timuta is changed and output, it will return True 121 | Bool Tick (); 122 | `` ` 123 | 124 | For use, you need to start FS and call a ticker in LOOP.With any change in the database, it itself will be written to the file after the time of the time: 125 | `` `CPP 126 | #include 127 | #include 128 | Gyverdbfile DB (& Littlefs, "DB.BIN"); 129 | 130 | VOID setup () { 131 | Littlefs.Begin (); 132 | db.begin ();// read data from the file 133 | 134 | // For work in this mode, the Init () method is very useful: 135 | // creates a record of the corresponding type and writes out "initial" data, 136 | // If such a record is not yet in the database 137 | db.init ("key", 123);// int 138 | db.init ("uint", (uint8_t) 123);// uint8 139 | db.init ("str", "");// line 140 | } 141 | VOID loop () { 142 | db.tick (); 143 | } 144 | `` ` 145 | 146 | ### Types of records 147 | `` `CPP 148 | None 149 | Int8 150 | Uint8 151 | Int16 152 | Uint16 153 | Int32 154 | Uint32 155 | Int64 156 | Uint64 157 | Float 158 | String 159 | Bin 160 | `` ` 161 | 162 | ### Entry 163 | - Inherits the class `text` for more convenient reading of lines 164 | 165 | `` `CPP 166 | // type of record 167 | GDB :: Type Type (); 168 | 169 | // Bring the data to the size of size ().Does not add a 0-terminator if this is a line 170 | VOID Writebytes (VOID* BUF); 171 | 172 | // bring to the variable 173 | Bool Writeto (T & Dest); 174 | 175 | Value Totext (); 176 | String Tostring (); 177 | Bool Tobool (); 178 | int toint (); 179 | int8_t toint8 (); 180 | int16_t toint16 (); 181 | int32_t toint32 (); 182 | int64_t toint64 (); 183 | Double Tofloat (); 184 | `` ` 185 | 186 | ### Usage 187 | The database stores the keys in the form of a hash code from the Stringutils library, to access the database you need to use a hash or a regular line, the library itself will consider the hash: 188 | `` `CPP 189 | db ["key1"];// line 190 | DB [SH ("Key2")];// Hash 191 | db ["key3" _h];// Hash 192 | `` ` 193 | 194 | Here is `SH ()` - a hash -function performed at the compilation stage.The line transferred to it does not exist in the program - at the compilation stage, it turns into the number.You can also use the literal `_H` - it does the same. 195 | 196 | Record and reading 197 | `` `CPP 198 | Gyverdb DB; 199 | 200 | // Record 201 | db ["key1"] = 1234; 202 | DB [SH ("Key2")] = 1234; 203 | db ["key3" _h] = 1234; 204 | 205 | // This cell is declared as int, the text is correctly adjusted to the number 206 | db ["key1"] = "123456"; 207 | 208 | // Reading.The library itself converts into the right type 209 | int i = db ["key1"]; 210 | Float F = DB [SH ("Key2")]; 211 | 212 | // Any data "printed", even binary 213 | Serial.println (db ["key3" _h]); 214 | 215 | // you can specify a specific type when output 216 | db ["key3" _h] .toint32 (); 217 | 218 | // can be compared with integer 219 | int i = 123; 220 | 123 == DB ["key1"]; 221 | 222 | // Comparison directly with lines works only in records with the type of string 223 | db ["key1"] == "str"; 224 | 225 | // but you can and like this, for any type of records 226 | // Totext () converts all types of database entries into a temporary line 227 | db ["key1"]. Totext () == "12345"; 228 | 229 | // gyverdb can record data from any type, even composite (arrays, structures) 230 | uint8_t arr [5] = {1, 2, 3, 4, 5}; 231 | db ["arr"] = arr; 232 | 233 | // Conclusion back.The type must have the same size! 234 | uint8_t arr2 [5]; 235 | db ["arr"]. Writeto (arr2); 236 | `` ` 237 | 238 | In a large project, remembering all the names of the keys is not very convenient, so for a more comfortable development you can make the base of the Hash keys using `enum`: 239 | `` `CPP 240 | enum keys: size_t { 241 | Key1 = SH ("Key1"), 242 | key2 = "key1" _h, 243 | Mykey = "Mykey" _H, 244 | }; 245 | `` ` 246 | 247 | And use them in the code as `keys :: key1`, for example` db [keys :: mykey] `.IDE will tell you a list of all the keys when entering `keys ::`.To reduce the recording of the key base, you can use the built -in macros: 248 | `` `CPP 249 | Db_keys (keys, 250 | Db_key (my_key), 251 | Db_key (key1), 252 | Db_key (wifi_ssid), 253 | ); 254 | `` ` 255 | Macro unfolds in the same Enum as shown above. 256 | 257 | ### Notes 258 | - Gyverdb stores whole up to 32 bits and Float numbers in the memory of the cell itself.64-bit numbers, lines and binary data are distinguished dynamically 259 | - For the sake of compactness, a 28-bit hash is used.This should be enough more than, the chance of conflicts is extremely small 260 | - The library automatically selects the type of record when recording in the cell.Bring the typemanually, if necessary (for example, `DB [" key "] = (uint32_t) 12345`) 261 | - By default, the `KeeptyPes ()` parameter is included not to change the type of recording during rewriting.This means that if the recording was Int16, then when recording data from another type in it, they will automatically convert to Int16, even if it is a line.And vice versa 262 | - When creating an empty cell, you can specify the type and reserve a place (only for lines and binary data) `DB.create (" KEK ", GDB :: type :: string, 100)` `100)` 263 | - `Entry` has automatic access to the line as an` string` operator, this means that records with a text type can be transmitted to functions that take `string`, for example` wifi.begin (db ["wifi_ssid"], db["wifi_pass"]); ` 264 | - If you need to transfer the record to the function that accepts `Const Char*` - use `c_str ()` on it.This will not duplicate the line in memory, but will give direct access to it.For example `foo (db [" str "]. C_str ())` 265 | 266 | 267 | 268 | ## versions 269 | - V1.0 270 | 271 | 272 | ## Installation 273 | - The library can be found by the name ** gyverdb ** and installed through the library manager in: 274 | - Arduino ide 275 | - Arduino ide v2 276 | - Platformio 277 | - [download the library] (https://github.com/gyverlibs/gyverdb/archive/refs/heads/main.zip) .Zip archive for manual installation: 278 | - unpack and put in * C: \ Program Files (X86) \ Arduino \ Libraries * (Windows X64) 279 | - unpack and put in * C: \ Program Files \ Arduino \ Libraries * (Windows X32) 280 | - unpack and put in *documents/arduino/libraries/ * 281 | - (Arduino id) Automatic installation from. Zip: * sketch/connect the library/add .Zip library ... * and specify downloaded archive 282 | - Read more detailed instructions for installing libraries [here] (https://alexgyver.ru/arduino-first/#%D0%A3%D1%81%D1%82%D0%B0%BD%D0%BE%BE%BE%BED0%B2%D0%BA%D0%B0_%D0%B1%D0%B8%D0%B1%D0%BB%D0%B8%D0%BE%D1%82%D0%B5%D0%BA) 283 | ### Update 284 | - I recommend always updating the library: errors and bugs are corrected in the new versions, as well as optimization and new features are added 285 | - through the IDE library manager: find the library how to install and click "update" 286 | - Manually: ** remove the folder with the old version **, and then put a new one in its place.“Replacement” cannot be done: sometimes in new versions, files that remain when replacing are deleted and can lead to errors! 287 | 288 | 289 | 290 | ## bugs and feedback 291 | Create ** Issue ** when you find the bugs, and better immediately write to the mail [alex@alexgyver.ru] (mailto: alex@alexgyver.ru) 292 | The library is open for refinement and your ** pull Request ** 'ow! 293 | 294 | When reporting about bugs or incorrect work of the library, it is necessary to indicate: 295 | - The version of the library 296 | - What is MK used 297 | - SDK version (for ESP) 298 | - version of Arduino ide 299 | - whether the built -in examples work correctly, in which the functions and designs are used, leading to a bug in your code 300 | - what code has been loaded, what work was expected from it and how it works in reality 301 | - Ideally, attach the minimum code in which the bug is observed.Not a canvas of a thousand lines, but a minimum code -------------------------------------------------------------------------------- /examples/file/file.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | GyverDBFile db(&LittleFS, "/data.db"); 6 | 7 | // имена ячеек базы данных 8 | DB_KEYS( 9 | kk, 10 | input, 11 | val12 12 | ); 13 | 14 | void setup() { 15 | #ifdef ESP32 16 | LittleFS.begin(true); 17 | #else 18 | LittleFS.begin(); 19 | #endif 20 | 21 | // запуск и инициализация полей БД 22 | db.begin(); 23 | db.init(kk::input, 0); 24 | db.init(kk::val12, "text"); 25 | } 26 | 27 | void loop() { 28 | // тикер, вызывать в лупе 29 | db.tick(); 30 | } -------------------------------------------------------------------------------- /examples/test/test.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | GyverDB db; 5 | 6 | DB_KEYS(keys, 7 | key1, 8 | arr // последнюю запятую не ставим 9 | ); 10 | 11 | void setup() { 12 | Serial.begin(115200); 13 | 14 | db["bool"_h] = true; // хеш _h 15 | db[keys::key1] = 1234; // хеш из enum 16 | db[SH("intval")] = -12321; // SH(хеш) 17 | db["uintval"] = 12345; // просто строка 18 | db["u64"] = 123456789876ULL; 19 | db["i64"] = -12345678987654321LL; 20 | db["fl"] = 3.14; 21 | db["str"] = "abcdefg"; 22 | uint8_t arr[5] = {0x09, 0x0F, 0x10, 0xa, 0xaa}; 23 | db[keys::arr] = arr; 24 | 25 | db.dump(Serial); 26 | 27 | int i = db["intval"]; // авто конверсия 28 | i = db["intval"].toBool(); // ручная 29 | db["bool"_h] == true; 30 | i = db["intval"].toFloat(); 31 | i = db["intval"].toInt(); 32 | Serial.println(db["intval"]); // печатается 33 | } 34 | 35 | void loop() { 36 | } 37 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For GyverDB 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | GyverDB KEYWORD1 10 | GyverDBFile KEYWORD1 11 | 12 | ####################################### 13 | # Methods and Functions (KEYWORD2) 14 | ####################################### 15 | 16 | 17 | ####################################### 18 | # Constants (LITERAL1) 19 | ####################################### 20 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=GyverDB 2 | version=1.3.2 3 | author=AlexGyver 4 | maintainer=AlexGyver 5 | sentence=Fast Arduino database for any type of data 6 | paragraph=Fast Arduino database for any type of data 7 | category=Data Storage 8 | url=https://github.com/GyverLibs/GyverDB 9 | depends=GTL,StringUtils,StreamIO,FOR_MACRO 10 | architectures=* 11 | -------------------------------------------------------------------------------- /src/DBConnector.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "GyverDBFile.h" 3 | 4 | #if defined(ESP8266) 5 | #include 6 | #elif defined(ESP32) 7 | #include 8 | #endif 9 | 10 | class DBConnector { 11 | typedef std::function ConnectCallback; 12 | 13 | public: 14 | // подключить БД, указать ключи логина и пароля, имя точки, таймаут в секундах и очистку SSID при выходе таймаута 15 | DBConnector(GyverDBFile* db, 16 | size_t ssid, 17 | size_t pass, 18 | const String& APname = "ESP AP", 19 | uint16_t timeout = 60, 20 | bool resetSSID = false, 21 | bool closeAP = true) : _db(db), 22 | _ssid(ssid), 23 | _pass(pass), 24 | _APname(APname), 25 | _tout(timeout * 1000ul), 26 | _resetSSID(resetSSID), 27 | _closeAP(closeAP) {} 28 | 29 | // установить имя AP 30 | void setName(const String& APname) { 31 | _APname = APname; 32 | } 33 | 34 | // установить пароль AP 35 | void setPass(const String& APpass) { 36 | _APpass = APpass; 37 | } 38 | 39 | // установить таймаут в секундах 40 | void setTimeout(uint16_t timeout) { 41 | _tout = timeout * 1000ul; 42 | } 43 | 44 | // очищать SSID в БД при неудачном подключении (умолч. выкл) 45 | void resetSSIDOnFail(bool res) { 46 | _resetSSID = res; 47 | } 48 | 49 | // автоматически отключать AP при подключении к STA (умолч. вкл) 50 | void cloasAP(bool closeAP) { 51 | _closeAP = closeAP; 52 | } 53 | 54 | // прдключить обработчик успешного подключения 55 | void onConnect(ConnectCallback cb) { 56 | _conn_cb = cb; 57 | } 58 | 59 | // прдключить обработчик ошибки (запущен AP) 60 | void onError(ConnectCallback cb) { 61 | _err_cb = cb; 62 | } 63 | 64 | // подключиться 65 | bool connect() { 66 | _db->init(_ssid, ""); 67 | _db->init(_pass, ""); 68 | 69 | if ((*_db)[_ssid].length()) { 70 | _tryConnect = true; 71 | _tmr = millis(); 72 | if (_closeAP) { 73 | WiFi.softAPdisconnect(); 74 | WiFi.mode(WIFI_STA); 75 | } else { 76 | WiFi.mode(WIFI_AP_STA); 77 | } 78 | WiFi.begin((*_db)[_ssid], (*_db)[_pass]); 79 | return 1; 80 | } else { 81 | _startAP(); 82 | } 83 | return 0; 84 | } 85 | 86 | // состояние подключения. true - подключен, false - запущена АР 87 | bool connected() { 88 | return WiFi.status() == WL_CONNECTED; 89 | } 90 | 91 | // вызывать в loop. Вернёт true при смене состояния 92 | bool tick() { 93 | if (_tryConnect) { 94 | if (WiFi.status() == WL_CONNECTED) { 95 | _tryConnect = false; 96 | if (_conn_cb) _conn_cb(); 97 | return 1; 98 | } else if (millis() - _tmr >= _tout) { 99 | _startAP(); 100 | if (_resetSSID) resetSSID(); 101 | return 1; 102 | } 103 | } 104 | return 0; 105 | } 106 | 107 | // сбросить ssid 108 | void resetSSID() { 109 | _db->set(_ssid, ""); 110 | _db->update(); 111 | } 112 | 113 | private: 114 | GyverDBFile* _db; 115 | size_t _ssid, _pass; 116 | String _APname; 117 | String _APpass; 118 | 119 | bool _tryConnect = false; 120 | uint32_t _tmr = 0, _tout; 121 | bool _resetSSID; 122 | bool _closeAP; 123 | ConnectCallback _conn_cb = nullptr; 124 | ConnectCallback _err_cb = nullptr; 125 | 126 | void _startAP() { 127 | _tryConnect = false; 128 | WiFi.disconnect(); 129 | WiFi.mode(WIFI_AP); 130 | if (_APpass.length()) WiFi.softAP(_APname, _APpass); 131 | else WiFi.softAP(_APname); 132 | if (_err_cb) _err_cb(); 133 | } 134 | }; -------------------------------------------------------------------------------- /src/GyverDB.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "utils/access.h" 8 | #include "utils/anytype.h" 9 | #include "utils/block.h" 10 | #include "utils/entry.h" 11 | 12 | // #define DB_NO_UPDATES // убрать стек обновлений 13 | // #define DB_NO_FLOAT // убрать поддержку float 14 | // #define DB_NO_INT64 // убрать поддержку int64 15 | // #define DB_NO_CONVERT // не конвертировать данные (принудительно менять тип ячейки, keepTypes не работает) 16 | 17 | class GyverDB : private gtl::stack { 18 | typedef gtl::stack ST; 19 | 20 | enum class Putmode { 21 | Set, 22 | Init, 23 | Update, 24 | }; 25 | 26 | public: 27 | using ST::capacity; 28 | using ST::length; 29 | using ST::reserve; 30 | using ST::stack; 31 | using ST::valid; 32 | using ST::operator bool; 33 | 34 | GyverDB(uint16_t reserveEntries = 0) { 35 | reserve(reserveEntries); 36 | } 37 | GyverDB(const GyverDB& db) = delete; 38 | GyverDB(GyverDB& db) { 39 | move(db); 40 | } 41 | GyverDB(GyverDB&& db) noexcept { 42 | move(db); 43 | } 44 | GyverDB& operator=(GyverDB db) { 45 | move(db); 46 | return *this; 47 | } 48 | 49 | void move(GyverDB& db) noexcept { 50 | ST::move(db); 51 | #ifndef DB_NO_UPDATES 52 | _updates.move(db._updates); 53 | #endif 54 | gtl::swap(_keepTypes, db._keepTypes); 55 | gtl::swap(_useUpdates, db._useUpdates); 56 | gtl::swap(_cache, db._cache); 57 | gtl::swap(_cache_h, db._cache_h); 58 | _change(); 59 | } 60 | 61 | ~GyverDB() { 62 | clear(); 63 | } 64 | 65 | gdb::Access operator[](size_t hash) { 66 | return gdb::Access(get(hash), hash, this, setHook); 67 | } 68 | gdb::Access operator[](const Text& key) { 69 | return (*this)[key.hash()]; 70 | } 71 | 72 | // не изменять тип ячейки (конвертировать данные если тип отличается) (умолч. true) 73 | void keepTypes(bool keep) { 74 | _keepTypes = keep; 75 | } 76 | 77 | // использовать стек обновлений (умолч. false) 78 | void useUpdates(bool use) { 79 | _useUpdates = use; 80 | } 81 | 82 | // вывести всё содержимое БД 83 | void dump(Print& p) { 84 | p.print(F("DB dump: ")); 85 | p.print(length()); 86 | p.print(F(" entries (")); 87 | p.print(size()); 88 | p.println(F(" bytes)")); 89 | for (size_t i = 0; i < length(); i++) { 90 | if (i <= 9) p.print(0); 91 | p.print(i); 92 | p.print(F(". 0x")); 93 | p.print(_buf[i].keyHash(), HEX); 94 | p.print(F(" [")); 95 | p.print(_buf[i].typeRead()); 96 | p.print(F("]: ")); 97 | p.println(gdb::Entry(_buf[i])); 98 | } 99 | } 100 | 101 | // экспортный размер БД (для writeTo) 102 | size_t writeSize() { 103 | size_t sz = 0; 104 | for (size_t i = 0; i < length(); i++) { 105 | if (!_buf[i].valid()) continue; 106 | if (_buf[i].isDynamic()) { 107 | if (!_buf[i].ptr()) continue; 108 | sz += 4 + 2; // typehash + size 109 | sz += _buf[i].size(); 110 | } else { 111 | sz += 4 + 4; // typehash + data 112 | } 113 | } 114 | sz += 2; // len 115 | return sz; 116 | } 117 | 118 | // экспортировать БД в Stream (напр. файл) 119 | template 120 | bool writeTo(T& writer) { 121 | // [db len] [hash32, value32] [hash32, size16, data...] 122 | 123 | #define _DB_WRITE(x) wr += writer.write((uint8_t*)&x, sizeof(x)) 124 | 125 | size_t wr = 0; 126 | uint16_t len = length(); 127 | _DB_WRITE(len); 128 | for (size_t i = 0; i < length(); i++) { 129 | if (!_buf[i].valid()) continue; 130 | if (_buf[i].isDynamic()) { 131 | if (!_buf[i].ptr()) continue; 132 | _DB_WRITE(_buf[i].typehash); 133 | uint16_t size = _buf[i].size(); 134 | _DB_WRITE(size); 135 | wr += writer.write((uint8_t*)_buf[i].buffer(), size); 136 | } else { 137 | _DB_WRITE(_buf[i].typehash); 138 | _DB_WRITE(_buf[i].data); 139 | } 140 | } 141 | return wr == writeSize(); 142 | } 143 | 144 | // экспортировать БД в буфер размера writeSize() 145 | bool writeTo(uint8_t* buffer) { 146 | Writer wr(buffer); 147 | return writeTo(wr); 148 | } 149 | 150 | // импортировать БД из Stream (напр. файл) 151 | bool readFrom(Stream& stream, size_t len) { 152 | return readFrom(Reader(stream, len)); 153 | } 154 | 155 | // импортировать БД из буфера 156 | bool readFrom(const uint8_t* buffer, size_t len) { 157 | return readFrom(Reader(buffer, len)); 158 | } 159 | 160 | // создать ячейку. Если существует - перезаписать пустой с новым типом 161 | bool create(size_t hash, gdb::Type type, uint16_t reserve = 0) { 162 | pos_t pos = _search(hash); 163 | if (!pos.exists) { 164 | _cache = -1; 165 | gdb::block_t block(type, hash); 166 | if (!block.init(reserve)) return 0; 167 | if (insert(pos.idx, block)) { 168 | _change(); 169 | return 1; 170 | } else { 171 | block.reset(); 172 | } 173 | } else { 174 | _setChanged(hash); 175 | _buf[pos.idx].updateType(type); 176 | return _buf[pos.idx].init(reserve); 177 | } 178 | return 0; 179 | } 180 | bool create(const Text& key, gdb::Type type, uint16_t reserve = 0) { 181 | return create(key.hash(), type, reserve); 182 | } 183 | 184 | // полностью освободить память 185 | void reset() { 186 | clear(); 187 | ST::reset(); 188 | } 189 | 190 | // стереть все ячейки (не освобождает зарезервированное место) 191 | void clear() { 192 | _cache = -1; 193 | while (length()) pop().reset(); 194 | _change(); 195 | } 196 | 197 | // удалить из БД ячейки, ключей которых нет в переданном списке 198 | void cleanup(size_t* hashes, size_t len) { 199 | _cache = -1; 200 | for (size_t i = 0; i < _len;) { 201 | size_t hash = _buf[i].keyHash(); 202 | bool found = false; 203 | for (size_t h = 0; h < len; h++) { 204 | if (hash == (hashes[h] & DB_HASH_MASK)) { 205 | i++; 206 | found = true; 207 | break; 208 | } 209 | } 210 | if (!found) { 211 | ST::remove(i); 212 | _change(); 213 | } 214 | } 215 | } 216 | 217 | // вывести все ключи в массив длиной length() 218 | void getKeys(size_t* hashes) { 219 | for (size_t i = 0; i < _len; i++) { 220 | hashes[i] = _buf[i].keyHash(); 221 | } 222 | } 223 | 224 | // было изменение бд 225 | bool changed() { 226 | return _changed; 227 | } 228 | 229 | // сбросить флаг изменения бд 230 | void clearChanged() { 231 | _changed = false; 232 | } 233 | 234 | // полный вес БД 235 | size_t size() { 236 | size_t sz = ST::size(); 237 | for (size_t i = 0; i < _len; i++) { 238 | sz += _buf[i].size(); 239 | if (_buf[i].type() == gdb::Type::String) sz++; 240 | } 241 | return sz; 242 | } 243 | 244 | // получить ячейку 245 | gdb::Entry get(size_t hash) { 246 | if (~_cache && _cache_h == hash) return gdb::Entry(_buf[_cache]); 247 | else { 248 | pos_t pos = _search(hash); 249 | if (pos.exists) { 250 | _cache = pos.idx; 251 | _cache_h = hash; 252 | return gdb::Entry(_buf[_cache]); 253 | } 254 | } 255 | return gdb::Entry(); 256 | } 257 | gdb::Entry get(const Text& key) { 258 | return get(key.hash()); 259 | } 260 | 261 | // получить ячейку по порядку 262 | gdb::Entry getN(int idx) { 263 | return (idx < (int)_len) ? gdb::Entry(_buf[idx]) : gdb::Entry(); 264 | } 265 | 266 | // удалить ячейку 267 | void remove(size_t hash) { 268 | pos_t pos = _search(hash); 269 | if (pos.exists) { 270 | _cache = -1; 271 | _buf[pos.idx].reset(); 272 | ST::remove(pos.idx); 273 | _change(); 274 | } 275 | } 276 | void remove(const Text& key) { 277 | remove(key.hash()); 278 | } 279 | 280 | // БД содержит ячейку с именем 281 | bool has(size_t hash) { 282 | return _search(hash).exists; 283 | } 284 | bool has(const Text& key) { 285 | return has(key.hash()); 286 | } 287 | 288 | // ================== SET ================== 289 | bool set(size_t hash, gdb::AnyType val) { return _put(hash, val, Putmode::Set); } 290 | bool set(const Text& key, gdb::AnyType val) { return _put(key.hash(), val, Putmode::Set); } 291 | 292 | // ================== INIT ================== 293 | bool init(size_t hash, gdb::AnyType val) { return _put(hash, val, Putmode::Init); } 294 | bool init(const Text& key, gdb::AnyType val) { return _put(key.hash(), val, Putmode::Init); } 295 | 296 | // ================== UPDATE ================== 297 | bool update(size_t hash, gdb::AnyType val) { return _put(hash, val, Putmode::Update); } 298 | bool update(const Text& key, gdb::AnyType val) { return _put(key.hash(), val, Putmode::Update); } 299 | 300 | // ===================== MISC ===================== 301 | // есть непрочитанные изменения 302 | bool updatesAvailable() { 303 | #ifndef DB_NO_UPDATES 304 | return _updates.length(); 305 | #endif 306 | return 0; 307 | } 308 | 309 | // пропустить необработанные обновления 310 | void skipUpdates() { 311 | #ifndef DB_NO_UPDATES 312 | _updates.clear(); 313 | #endif 314 | } 315 | 316 | // получить хеш обновления из стека 317 | size_t updateNext() { 318 | #ifndef DB_NO_UPDATES 319 | return _updates.pop(); 320 | #endif 321 | return 0; 322 | } 323 | 324 | virtual bool tick() { return 0; } 325 | 326 | // hook 327 | static bool setHook(void* db, size_t hash, const gdb::AnyType& val) { 328 | return ((GyverDB*)db)->_put(hash, val, Putmode::Set); 329 | } 330 | 331 | protected: 332 | bool _update = 0; 333 | 334 | private: 335 | bool _keepTypes = true; 336 | bool _useUpdates = false; 337 | bool _changed = false; 338 | int16_t _cache = -1; 339 | size_t _cache_h = 0; 340 | 341 | #ifndef DB_NO_UPDATES 342 | gtl::stack _updates; 343 | #endif 344 | 345 | void _setChanged(size_t hash) { 346 | _change(); 347 | #ifndef DB_NO_UPDATES 348 | if (_useUpdates && _updates.indexOf(hash) < 0) _updates.push(hash); 349 | #endif 350 | } 351 | 352 | struct pos_t { 353 | int idx; 354 | bool exists; 355 | }; 356 | 357 | void _change() { 358 | _changed = true; 359 | _update = true; 360 | } 361 | 362 | pos_t _search(size_t hash) { 363 | if (!length()) return pos_t{0, false}; 364 | hash &= DB_HASH_MASK; // to 29bit 365 | int low = 0, high = length() - 1; 366 | while (low <= high) { 367 | int mid = low + ((high - low) >> 1); 368 | if (_buf[mid].keyHash() == hash) return pos_t{mid, true}; 369 | if (_buf[mid].keyHash() < hash) low = mid + 1; 370 | else high = mid - 1; 371 | } 372 | return pos_t{low, false}; 373 | } 374 | 375 | bool readFrom(Reader reader) { 376 | clear(); 377 | uint16_t len = 0; 378 | if (!reader.read(len)) return 0; 379 | reserve(len); 380 | 381 | while (reader.available()) { 382 | uint32_t typehash; 383 | if (!reader.read(typehash)) return 0; 384 | 385 | gdb::block_t block; 386 | block.typehash = typehash; 387 | if (block.isDynamic()) { 388 | uint16_t size; 389 | if (!reader.read(size)) return 0; 390 | if (!block.reserve(size)) return 0; 391 | if (!reader.read(block.buffer(), size)) { 392 | block.reset(); 393 | return 0; 394 | } 395 | block.setSize(size); 396 | } else { 397 | if (!reader.read(block.data)) return 0; 398 | } 399 | 400 | if (!push(block)) { 401 | block.reset(); 402 | return 0; 403 | } 404 | } 405 | _change(); 406 | return 1; 407 | } 408 | 409 | bool _put(size_t hash, const gdb::AnyType& val, Putmode mode) { 410 | pos_t pos = _search(hash); 411 | if (pos.exists) { 412 | if (mode == Putmode::Init && _buf[pos.idx].type() == val.type) return 0; 413 | 414 | if (_buf[pos.idx].update(val.type, val.ptr, val.len, (_keepTypes && mode != Putmode::Init))) { 415 | _setChanged(hash); 416 | return 1; 417 | } 418 | } else { 419 | _cache = -1; 420 | if (mode == Putmode::Update) return 0; 421 | 422 | gdb::block_t block(val.type, hash); 423 | if (block.write(val.ptr, val.len)) { 424 | if (insert(pos.idx, block)) { 425 | _change(); 426 | return 1; 427 | } else { 428 | block.reset(); 429 | } 430 | } 431 | } 432 | return 0; 433 | } 434 | }; 435 | -------------------------------------------------------------------------------- /src/GyverDBFile.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #include "GyverDB.h" 6 | 7 | class GyverDBFile : public GyverDB { 8 | public: 9 | GyverDBFile(fs::FS* nfs = nullptr, const char* path = nullptr, uint32_t tout = 10000) { 10 | setFS(nfs, path); 11 | _tout = tout; 12 | } 13 | 14 | ~GyverDBFile() { 15 | update(); 16 | } 17 | 18 | // установить файловую систему и имя файла 19 | void setFS(fs::FS* nfs, const char* path) { 20 | _fs = nfs; 21 | _path = path; 22 | } 23 | 24 | // установить таймаут записи, мс (умолч. 10000) 25 | void setTimeout(uint32_t tout = 10000) { 26 | _tout = tout; 27 | } 28 | 29 | // прочитать данные 30 | bool begin() { 31 | bool res = false; 32 | if (_fs) { 33 | if (_fs->exists(_path)) { 34 | File file = _fs->open(_path, "r"); 35 | if (file) res = readFrom(file, file.size()); 36 | _update = false; 37 | } else { 38 | File file = _fs->open(_path, "w"); 39 | res = true; 40 | } 41 | } 42 | return res; 43 | } 44 | 45 | // обновить данные в файле, если было изменение БД. Вернёт true при успешной записи 46 | bool update() { 47 | _tmr = 0; 48 | if (!_update) return false; 49 | _update = false; 50 | File file = _fs->open(_path, "w"); 51 | return file ? writeTo(file) : 0; 52 | } 53 | 54 | // тикер, вызывать в loop. Сам обновит данные при изменении и выходе таймаута, вернёт true 55 | bool tick() { 56 | if (_update && !_tmr) { 57 | _tmr = millis(); 58 | } 59 | if (_tmr && millis() - _tmr >= _tout) { 60 | update(); 61 | return 1; 62 | } 63 | return 0; 64 | } 65 | 66 | private: 67 | fs::FS* _fs; 68 | const char* _path; 69 | uint32_t _tmr = 0, _tout = 10000; 70 | }; -------------------------------------------------------------------------------- /src/utils/access.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "anytype.h" 5 | #include "entry.h" 6 | #include "types.h" 7 | 8 | namespace gdb { 9 | 10 | typedef bool (*setHook)(void* db, size_t hash, const AnyType& val); 11 | 12 | class Access : public Entry { 13 | public: 14 | Access(Entry entry, size_t hash, void* db, setHook hook) : Entry(entry), _hash(hash), _db(db), _hook(hook) {} 15 | 16 | bool operator=(AnyType value) { 17 | return _hook(_db, _hash, value); 18 | } 19 | 20 | // incr decr 21 | template 22 | T operator++() { 23 | T value = (T)(*this) + 1; 24 | _hook(_db, _hash, AnyType(value)); 25 | return value; 26 | } 27 | 28 | template 29 | T operator++(T) { 30 | T value = (T)(*this) + 1; 31 | _hook(_db, _hash, AnyType(value)); 32 | return value; 33 | } 34 | 35 | template 36 | T operator--() { 37 | T value = (T)(*this) - 1; 38 | _hook(_db, _hash, AnyType(value)); 39 | return value; 40 | } 41 | 42 | template 43 | T operator--(T) { 44 | T value = (T)(*this) - 1; 45 | _hook(_db, _hash, AnyType(value)); 46 | return value; 47 | } 48 | 49 | // compl 50 | template 51 | T operator+=(T value) { 52 | value = (T)(*this) + value; 53 | _hook(_db, _hash, AnyType(value)); 54 | return value; 55 | } 56 | 57 | template 58 | T operator-=(T value) { 59 | value = (T)(*this) - value; 60 | _hook(_db, _hash, AnyType(value)); 61 | return value; 62 | } 63 | 64 | template 65 | T operator*=(T value) { 66 | value = (T)(*this) * value; 67 | _hook(_db, _hash, AnyType(value)); 68 | return value; 69 | } 70 | 71 | template 72 | T operator/=(T value) { 73 | value = (T)(*this) / value; 74 | _hook(_db, _hash, AnyType(value)); 75 | return value; 76 | } 77 | 78 | template 79 | T operator%=(T value) { 80 | value = (T)(*this) % value; 81 | _hook(_db, _hash, AnyType(value)); 82 | return value; 83 | } 84 | 85 | // bin 86 | template 87 | T operator|=(T value) { 88 | value = (T)(*this) | value; 89 | _hook(_db, _hash, AnyType(value)); 90 | return value; 91 | } 92 | 93 | template 94 | T operator&=(T value) { 95 | value = (T)(*this) & value; 96 | _hook(_db, _hash, AnyType(value)); 97 | return value; 98 | } 99 | 100 | private: 101 | size_t _hash; 102 | void* _db; 103 | setHook _hook; 104 | }; 105 | 106 | } // namespace gdb -------------------------------------------------------------------------------- /src/utils/anytype.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #include "types.h" 6 | 7 | class GyverDB; 8 | 9 | namespace gdb { 10 | 11 | class AnyType { 12 | #ifndef DB_NO_INT64 13 | uint64_t buf = 0; 14 | #else 15 | uint32_t buf = 0; 16 | #endif 17 | 18 | public: 19 | AnyType(const GyverDB& value) = delete; 20 | 21 | template 22 | AnyType(const T& value) : ptr(&value), len(sizeof(T)), type(Type::Bin) {} 23 | AnyType(const void* ptr, size_t len) : ptr(ptr), len(len), type(Type::Bin) {} 24 | 25 | AnyType(const char* str, size_t len) : ptr(str), len(len), type(Type::String) {} 26 | AnyType(const char* str) : AnyType(str, strlen(str)) {} 27 | AnyType(const String& str) : AnyType(str.c_str(), str.length()) {} 28 | AnyType(const Text& str) : AnyType(str.str(), str.length()) {} 29 | 30 | AnyType(signed char val) : AnyType((long)val) {} 31 | AnyType(short val) : AnyType((long)val) {} 32 | AnyType(int val) : AnyType((long)val) {} 33 | AnyType(long val) : buf(val), ptr(&buf), len(4), type(Type::Int) {} 34 | #ifndef DB_NO_INT64 35 | AnyType(long long val) : buf(val), ptr(&buf), len(8), type(Type::Int64) {} 36 | #endif 37 | 38 | AnyType(bool val) : AnyType((unsigned long)val) {} 39 | AnyType(char val) : AnyType((unsigned long)val) {} 40 | AnyType(unsigned char val) : AnyType((unsigned long)val) {} 41 | AnyType(unsigned short val) : AnyType((unsigned long)val) {} 42 | AnyType(unsigned int val) : AnyType((unsigned long)val) {} 43 | AnyType(unsigned long val) : buf(val), ptr(&buf), len(4), type(Type::Uint) {} 44 | #ifndef DB_NO_INT64 45 | AnyType(unsigned long long val) : buf(val), ptr(&buf), len(8), type(Type::Uint64) {} 46 | #endif 47 | 48 | AnyType(float val) : buf(*((uint32_t*)&val)), ptr(&buf), len(4), type(Type::Float) {} 49 | AnyType(double val) : AnyType((float)val) {} 50 | 51 | const void* ptr = nullptr; 52 | size_t len = 0; 53 | Type type = Type::None; 54 | }; 55 | 56 | } // namespace gdb 57 | -------------------------------------------------------------------------------- /src/utils/block.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "types.h" 5 | 6 | namespace gdb { 7 | 8 | class block_t { 9 | public: 10 | block_t() {} 11 | block_t(Type type, size_t hash) : typehash(DB_MAKE_TYPEHASH(type, hash)) {} 12 | 13 | uint32_t typehash = 0; 14 | uint32_t data = 0; 15 | 16 | // указатель на динамические данные 17 | inline void* ptr() const { 18 | return (void*)data; 19 | } 20 | 21 | // указатель непосредственно на данные размера size() 22 | void* buffer() const { 23 | switch (type()) { 24 | case Type::Int: 25 | case Type::Uint: 26 | case Type::Float: 27 | return (void*)&data; 28 | 29 | #ifndef DB_NO_INT64 30 | case Type::Int64: 31 | case Type::Uint64: 32 | return ptr(); 33 | #endif 34 | case Type::String: 35 | case Type::Bin: 36 | return ptr() ? ((uint8_t*)ptr() + 2) : nullptr; 37 | 38 | default: 39 | break; 40 | } 41 | return nullptr; 42 | } 43 | 44 | // хэш ключа записи 45 | inline size_t keyHash() const { 46 | return DB_GET_HASH(typehash); 47 | } 48 | 49 | // тип записи 50 | inline Type type() const { 51 | return DB_GET_TYPE(typehash); 52 | } 53 | 54 | // тип записи как pgm строка 55 | const __FlashStringHelper* typeRead() const { 56 | switch (type()) { 57 | case Type::Bin: return F("Bin"); 58 | case Type::Int: return F("Int"); 59 | case Type::Uint: return F("Uint"); 60 | case Type::Int64: return F("Int64"); 61 | case Type::Uint64: return F("Uint64"); 62 | case Type::Float: return F("Float"); 63 | case Type::String: return F("String"); 64 | default: break; 65 | } 66 | return F("None"); 67 | } 68 | 69 | // запись валидна 70 | inline bool valid() const { 71 | return typehash; 72 | } 73 | 74 | // запись валидна 75 | inline operator bool() const { 76 | return valid(); 77 | } 78 | 79 | // запись является валидной строкой 80 | bool isValidString() const { 81 | return (ptr() && type() == Type::String); 82 | } 83 | 84 | // освободить динамический буфер и сбросить тип 85 | void reset() { 86 | if (isDynamic() && ptr()) free(ptr()); 87 | typehash = DB_REPLACE_TYPE(typehash, Type::None); 88 | data = 0; 89 | } 90 | 91 | // зарезервировать место в количестве данных. true - buffer() всегда валидный 92 | bool reserve(size_t len) { 93 | if (valid()) { 94 | if (isDynamic()) { 95 | void* p = realloc(ptr(), realLen(len)); 96 | if (!p) return 0; 97 | data = (uint32_t)p; 98 | return 1; 99 | } else { 100 | if (len <= 4) return 1; 101 | } 102 | } 103 | return 0; 104 | } 105 | 106 | // обновить тип 107 | void updateType(Type newtype) { 108 | if (isDynamic() != Converter::isDynamic(newtype)) { 109 | if (isDynamic()) reset(); 110 | else data = 0; 111 | } 112 | typehash = DB_REPLACE_TYPE(typehash, newtype); 113 | } 114 | 115 | // записать данные текущего типа 116 | bool write(const void* value, size_t len) { 117 | if (!valid()) return 0; 118 | if (!reserve(len)) return 0; 119 | if (!isDynamic()) data = 0; 120 | memcpy(buffer(), value, len); 121 | setSize(len); 122 | return 1; 123 | } 124 | 125 | // сравнить данные и записать если отличаются 126 | bool compareAndUpdate(const void* value, size_t len) { 127 | if (len == size() && !memcmp(value, buffer(), len)) return 0; // same 128 | return write(value, len); 129 | } 130 | 131 | // обновить 132 | bool update(Type ntype, const void* value, size_t len, bool keepType) { 133 | if (!valid() || ntype == Type::None) return 0; 134 | 135 | if (type() == ntype) { 136 | return compareAndUpdate(value, len); //!!!!!!!!! 137 | } else { 138 | #ifndef DB_NO_CONVERT 139 | if (keepType) { 140 | Converter conv(ntype, value, len); 141 | switch (type()) { 142 | case Type::String: { 143 | Value v = conv.toText(); 144 | return compareAndUpdate(v.str(), v.length()); 145 | } 146 | case Type::Int: 147 | case Type::Uint: { 148 | uint32_t v = conv.toInt(); 149 | return compareAndUpdate(&v, 4); 150 | } 151 | #ifndef DB_NO_INT64 152 | case Type::Int64: 153 | case Type::Uint64: { 154 | uint64_t v = conv.toInt64(); 155 | return compareAndUpdate(&v, 8); 156 | } break; 157 | #endif 158 | #ifndef DB_NO_FLOAT 159 | case Type::Float: { 160 | float v = conv.toFloat(); 161 | return compareAndUpdate(&v, 4); 162 | } 163 | #endif 164 | default: 165 | return 0; 166 | } 167 | } else 168 | #endif 169 | { 170 | updateType(ntype); 171 | return write(value, len); 172 | } 173 | } 174 | return 1; 175 | } 176 | 177 | // записать размер для raw и string 178 | void setSize(size_t len) { 179 | switch (type()) { 180 | case Type::Bin: 181 | case Type::String: 182 | if (ptr()) { 183 | *((uint16_t*)ptr()) = len; 184 | if (type() == Type::String) ((char*)buffer())[len] = 0; 185 | } 186 | break; 187 | 188 | default: 189 | break; 190 | } 191 | } 192 | 193 | // создать пустую запись и зарезервировать место 194 | bool init(uint16_t len = 0) { 195 | switch (type()) { 196 | case Type::Bin: 197 | if (!reserve(len)) return 0; 198 | setSize(0); 199 | break; 200 | 201 | case Type::String: 202 | if (!reserve(len)) return 0; 203 | setSize(0); 204 | break; 205 | #ifndef DB_NO_INT64 206 | case Type::Int64: 207 | case Type::Uint64: 208 | if (!reserve(8)) return 0; 209 | memset(ptr(), 0, 8); 210 | #endif 211 | default: 212 | break; 213 | } 214 | return 1; 215 | } 216 | 217 | // скорректировать длину 218 | size_t realLen(size_t len) const { 219 | switch (type()) { 220 | case Type::Bin: return len + 2; 221 | case Type::String: return len + 2 + 1; 222 | default: break; 223 | } 224 | return len; 225 | } 226 | 227 | // вес данных / длина строки без 0-терминатора 228 | size_t size() const { 229 | switch (type()) { 230 | case Type::String: 231 | case Type::Bin: 232 | return ptr() ? *((uint16_t*)ptr()) : 0; 233 | 234 | default: 235 | return Converter::size(type()); 236 | } 237 | return 0; 238 | } 239 | 240 | // запись хранит данные динамически 241 | bool isDynamic() const { 242 | return Converter::isDynamic(type()); 243 | } 244 | }; 245 | 246 | } // namespace gdb 247 | -------------------------------------------------------------------------------- /src/utils/entry.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #include "block.h" 6 | 7 | namespace gdb { 8 | 9 | class Entry : protected block_t, public Text, public Converter { 10 | public: 11 | using block_t::buffer; 12 | using block_t::isValidString; 13 | using block_t::keyHash; 14 | using block_t::reserve; 15 | using block_t::size; 16 | using block_t::type; 17 | 18 | Entry() {} 19 | Entry(const block_t& b) : block_t(b), Text(b.isValidString() ? (const char*)b.buffer() : nullptr, b.size()), Converter(b.type(), b.buffer(), b.size()) {} 20 | 21 | // тип записи 22 | gdb::Type type() const { 23 | return block_t::type(); 24 | } 25 | 26 | bool valid() const { 27 | return block_t::valid(); 28 | } 29 | 30 | size_t printTo(Print& p) const { // override 31 | if (type() == gdb::Type::Bin) { 32 | size_t ret = 0; 33 | for (size_t b = 0; b < size(); b++) { 34 | uint8_t data = ((uint8_t*)buffer())[b]; 35 | if (data <= 0xF) ret += p.print('0'); 36 | ret += p.print(data, HEX); 37 | ret += p.print(' '); 38 | } 39 | return ret; 40 | } else { 41 | return toText().printTo(p); 42 | } 43 | } 44 | 45 | // ======================= EXPORT ======================= 46 | 47 | // вывести данные в буфер размера size(). Не добавляет 0-терминатор, если это строка 48 | void writeBytes(void* buf) const { 49 | if (block_t::valid() && buffer()) memcpy(buf, buffer(), size()); 50 | } 51 | 52 | // вывести в переменную 53 | template 54 | bool writeTo(T& dest) const { 55 | if (block_t::valid() && buffer() && sizeof(T) == size()) { 56 | writeBytes(&dest); 57 | return true; 58 | } 59 | return false; 60 | } 61 | 62 | bool addString(String& s) const { // override 63 | toText().addString(s); 64 | return true; 65 | } 66 | 67 | String toString() const { 68 | return Converter::toString(); 69 | } 70 | 71 | bool toBool() const { 72 | return Converter::toBool(); 73 | } 74 | 75 | int32_t toInt() const { 76 | return Converter::toInt(); 77 | } 78 | 79 | int16_t toInt16() const { 80 | return Converter::toInt16(); 81 | } 82 | 83 | int32_t toInt32() const { 84 | return Converter::toInt32(); 85 | } 86 | 87 | int64_t toInt64() const { 88 | return Converter::toInt64(); 89 | } 90 | 91 | float toFloat() const { 92 | return Converter::toFloat(); 93 | } 94 | 95 | Value toText() const { 96 | return Converter::toText(); 97 | } 98 | 99 | // ======================= CAST ======================= 100 | 101 | #define DB_MAKE_OPERATOR(T, func) \ 102 | operator T() const { \ 103 | return (T)func(); \ 104 | } \ 105 | bool operator==(const T v) const { \ 106 | return (T)func() == v; \ 107 | } \ 108 | bool operator!=(const T v) const { \ 109 | return (T)func() != v; \ 110 | } \ 111 | bool operator>(const T v) const { \ 112 | return (T)func() > v; \ 113 | } \ 114 | bool operator<(const T v) const { \ 115 | return (T)func() < v; \ 116 | } \ 117 | bool operator>=(const T v) const { \ 118 | return (T)func() >= v; \ 119 | } \ 120 | bool operator<=(const T v) const { \ 121 | return (T)func() <= v; \ 122 | } 123 | 124 | operator bool() const { 125 | return toBool(); 126 | } 127 | bool operator==(const bool v) const { 128 | return Converter::valid() ? Converter::toBool() == v : false; 129 | } 130 | bool operator!=(const bool v) const { 131 | return *this == !v; 132 | } 133 | 134 | // DB_MAKE_OPERATOR(bool, toBool) 135 | DB_MAKE_OPERATOR(char, toInt) 136 | DB_MAKE_OPERATOR(signed char, toInt) 137 | DB_MAKE_OPERATOR(unsigned char, toInt) 138 | DB_MAKE_OPERATOR(short, toInt) 139 | DB_MAKE_OPERATOR(unsigned short, toInt) 140 | DB_MAKE_OPERATOR(int, toInt) 141 | DB_MAKE_OPERATOR(unsigned int, toInt) 142 | DB_MAKE_OPERATOR(long, toInt) 143 | DB_MAKE_OPERATOR(unsigned long, toInt) 144 | DB_MAKE_OPERATOR(long long, toInt64) 145 | DB_MAKE_OPERATOR(unsigned long long, toInt64) 146 | DB_MAKE_OPERATOR(float, toFloat) 147 | DB_MAKE_OPERATOR(double, toFloat) 148 | 149 | private: 150 | using Text::toBool; 151 | using Text::toFloat; 152 | using Text::toInt; 153 | using Text::toInt16; 154 | using Text::toInt32; 155 | using Text::toInt64; 156 | using Text::toString; 157 | using Text::type; 158 | using Text::operator bool; 159 | using Text::operator char; 160 | using Text::operator signed char; 161 | using Text::operator short; 162 | using Text::operator unsigned short; 163 | using Text::operator int; 164 | using Text::operator unsigned int; 165 | using Text::operator long; 166 | using Text::operator unsigned long; 167 | using Text::operator long long; 168 | using Text::operator unsigned long long; 169 | using Text::operator float; 170 | using Text::operator double; 171 | }; 172 | 173 | } // namespace gdb 174 | -------------------------------------------------------------------------------- /src/utils/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | #define DB_TYPE_SIZE (3ul) 7 | #define DB_HASH_SIZE (32ul - DB_TYPE_SIZE) 8 | #define DB_TYPE_MASK (((1ul << DB_TYPE_SIZE) - 1) << DB_HASH_SIZE) 9 | #define DB_HASH_MASK ((1ul << DB_HASH_SIZE) - 1) 10 | #define DB_GET_TYPE(x) (Type)((uint32_t)x & DB_TYPE_MASK) 11 | #define DB_GET_HASH(x) ((uint32_t)x & DB_HASH_MASK) 12 | #define DB_MAKE_TYPEHASH(t, h) ((uint32_t)t | (h & DB_HASH_MASK)) 13 | #define DB_REPLACE_TYPE(x, t) ((x & ~(DB_TYPE_MASK)) | (uint32_t)t) 14 | 15 | // #define DB_KEY(name) name = SH(#name) & DB_HASH_MASK 16 | // #define DB_KEYS(name, ...) enum name : size_t { __VA_ARGS__ }; 17 | 18 | #define DB_KEY(name) name 19 | 20 | #define _DB_KEY(N, i, p, val) val = (SH(#val) & DB_HASH_MASK), 21 | #define DB_KEYS(name, ...) enum name : size_t { FOR_MACRO(_DB_KEY, 0, __VA_ARGS__) }; 22 | #define DB_KEYS_CLASS(name, ...) enum class name : size_t { FOR_MACRO(_DB_KEY, 0, __VA_ARGS__) }; 23 | 24 | #define _DB_INIT(N, i, p, val) p.init val; 25 | #define DB_INIT(name, ...) FOR_MACRO(_DB_INIT, db, __VA_ARGS__) 26 | 27 | namespace gdb { 28 | 29 | enum class Type : uint32_t { 30 | None = (0ul << DB_HASH_SIZE), 31 | Int = (1ul << DB_HASH_SIZE), 32 | Uint = (2ul << DB_HASH_SIZE), 33 | Int64 = (3ul << DB_HASH_SIZE), 34 | Uint64 = (4ul << DB_HASH_SIZE), 35 | Float = (5ul << DB_HASH_SIZE), 36 | String = (6ul << DB_HASH_SIZE), 37 | Bin = (7ul << DB_HASH_SIZE), 38 | }; 39 | 40 | class Converter { 41 | public: 42 | Converter() {} 43 | Converter(Type type, const void* p, size_t len) : type(type), p(p), len(len) {} 44 | 45 | static bool isDynamic(Type type) { 46 | switch (type) { 47 | case Type::Int64: 48 | case Type::Uint64: 49 | case Type::Bin: 50 | case Type::String: 51 | return 1; 52 | 53 | default: 54 | break; 55 | } 56 | return 0; 57 | } 58 | 59 | static size_t size(Type type) { 60 | switch (type) { 61 | case Type::Int: 62 | case Type::Uint: 63 | case Type::Float: 64 | return 4; 65 | 66 | case Type::Int64: 67 | case Type::Uint64: 68 | return 8; 69 | 70 | default: 71 | break; 72 | } 73 | return 0; 74 | } 75 | 76 | bool valid() const { 77 | return p; 78 | } 79 | 80 | String toString() const { 81 | if (!p) return String(); 82 | switch (type) { 83 | case Type::Int: 84 | case Type::Int64: 85 | return String(toInt()); 86 | 87 | case Type::Uint: 88 | case Type::Uint64: 89 | return String((uint32_t)toInt()); 90 | 91 | case Type::String: 92 | return Text((const char*)p, len).toString(); 93 | 94 | #ifndef DB_NO_FLOAT 95 | case Type::Float: 96 | return String(toFloat()); 97 | #endif 98 | default: 99 | break; 100 | } 101 | return String(); 102 | } 103 | 104 | bool toBool() const { 105 | if (!p) return false; 106 | switch (type) { 107 | case Type::String: return (*(char*)p == 't' || *(char*)p == '1'); 108 | default: break; 109 | } 110 | return toInt(); 111 | } 112 | 113 | int32_t toInt() const { 114 | if (!p) return 0; 115 | switch (type) { 116 | case Type::Int: 117 | case Type::Uint: 118 | return *((int32_t*)p); 119 | #ifndef DB_NO_INT64 120 | case Type::Int64: 121 | case Type::Uint64: 122 | return *((int64_t*)p); 123 | #endif 124 | case Type::Float: 125 | return *((float*)p); 126 | 127 | case Type::String: 128 | return Text((const char*)p, len).toInt32(); 129 | 130 | default: 131 | break; 132 | } 133 | return 0; 134 | } 135 | 136 | int16_t toInt16() const { 137 | return toInt(); 138 | } 139 | 140 | int32_t toInt32() const { 141 | return toInt(); 142 | } 143 | 144 | int64_t toInt64() const { 145 | if (!p) return 0; 146 | switch (type) { 147 | #ifndef DB_NO_INT64 148 | case Type::Int64: 149 | case Type::Uint64: 150 | return *((int64_t*)p); 151 | #endif 152 | case Type::String: 153 | return Text((const char*)p, len).toInt64(); 154 | 155 | default: 156 | break; 157 | } 158 | return toInt(); 159 | } 160 | 161 | float toFloat() const { 162 | if (!p) return 0; 163 | switch (type) { 164 | case Type::Float: return *((float*)p); 165 | #ifndef DB_NO_FLOAT 166 | case Type::String: return Text((const char*)p, len).toFloat(); 167 | #endif 168 | default: break; 169 | } 170 | return toInt(); 171 | } 172 | 173 | Value toText() const { 174 | if (!p) return Value(); 175 | switch (type) { 176 | case Type::Int: return (int32_t)toInt(); 177 | case Type::Uint: return (uint32_t)toInt(); 178 | #ifndef DB_NO_INT64 179 | case Type::Int64: return *((int64_t*)p); 180 | case Type::Uint64: return *((uint64_t*)p); 181 | #endif 182 | #ifndef DB_NO_FLOAT 183 | case Type::Float: return *((float*)p); 184 | #endif 185 | case Type::String: return Text((const char*)p, len); 186 | default: break; 187 | } 188 | return Value(); 189 | } 190 | 191 | private: 192 | Type type = Type::None; 193 | const void* p = nullptr; 194 | size_t len = 0; 195 | }; 196 | 197 | } // namespace gdb --------------------------------------------------------------------------------