├── .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 | [](https://github.com/GyverLibs/GyverDB/releases/latest/download/GyverDB.zip)
2 | [](https://registry.platformio.org/libraries/gyverlibs/GyverDB)
3 | [](https://alexgyver.ru/)
4 | [](https://alexgyver.ru/support_alex/)
5 | [](https://github-com.translate.goog/GyverLibs/GyverDB?_x_tr_sl=ru&_x_tr_tl=en)
6 |
7 | [](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
--------------------------------------------------------------------------------