ESP32 и SQLite - проблема с ALTER TABLE
Я использую ESP32 и SQLite3 от https://github.com/siara-cc/esp32_arduino_sqlite3_lib Все в порядке, кроме команды ALTER TABLE. Когда я пытаюсь
ALTER TABLE TAB_JOUEUR RENAME TO TAB_NEW_JOUEUR
Я получаю "логическую ошибку SQL". Я просто вижу, что исходный код по умолчанию не будет компилироваться с опцией ALTER TABLE, если определена SQLITE_OMIT_ALTERTABLE. Поэтому в начале источника .c я добавил:
#if defined(SQLITE_OMIT_ALTERTABLE)
#undef SQLITE_OMIT_ALTERTABLE
#endif
Но результат тот же самый.... Есть идеи?
@Peter, 👍1
Обсуждение1 ответ
Что ж. После долгих поисков вот способ "ПЕРЕИМЕНОВАТЬ" таблицу. Это немного сложно... Переименование таблицы в ESP SQLite очень интересно, потому что SQLite имеет очень ограниченные возможности для редактирования таблицы. Таким образом, вы не можете добавлять поля, удалять и так далее. Единственный способ-создать новую таблицу tmp, скопировать содержимое исходной таблицы в эту таблицу tmp, удалить исходную и переименовать tmp с именем новой. ВЫПАДАЮЩАЯ ТАБЛИЦА подходит для ESP-версии SQLite, но, как сказано в вопросе, ИЗМЕНЕНИЕ ПЕРЕИМЕНОВАНИЯ не работает.
Итак, вот как это сделать:
1-Какие у нас есть таблицы?
Когда мы инициализируем ESP, мы вызываем
sqlite3_initialize();
а затем мы передаем SQLite файл, который он будет использовать для сохранения данных, с вызовом функции sqlite3_open (), указывающей имя ранее созданного файла.
В этот файл будут сохранены все наши собственные таблицы, а также определения наших таблиц. Для этого SQLite создаст системную таблицу. Таким образом, в базе данных у нас есть наши собственные таблицы, а также системные таблицы SQLite
2 - Таблица схемы базы данных
Одной из наиболее важных таблиц является таблица схемы базы данных. Его название зависит от версии SQLite. В версии, которую мы используем на ESP8266 или ESP32, ее имя-sqlite_master . Эта таблица содержит 4 поля:
- типа. Значением может быть "таблица", "индекс", "представление" или "триггер".
- имя. Значение - это имя данных. Если тип= "таблица", имя-это имя
таблицы, определенной в этой записи - имя tbl_. Название таблицы. Поэтому, если тип= "имя", tbl_name = имя
- корневая страница. Int, который является индексом в дереве Btree, используемом SQLite для доступа к данным
- sql. Код для создания таблицы
3-Проверьте схему
Чтобы проверить содержимое этой конкретной таблицы, просто создайте ТАБЛИЦУ, а затем ВЫБЕРИТЕ * ИЗ sqlite_master
Вот что я сделал и каков результат:
ВЫЗОВ СОЗДАТЬ ТАБЛИЦУ:
создайте ТАБЛИЦУ, ЕСЛИ НЕ СУЩЕСТВУЕТ TAB_JOUEUR (ЦЕЛОЕ ЧИСЛО"tjoueur_id", ТЕКСТ "tjoueur_prenom", ТЕКСТ "tjoueur_nom", ТЕКСТ"tjoueur_ville", ПЕРВИЧНЫЙ КЛЮЧ (АВТОИНКРЕМЕНТ "tjoueur_id"))
Результат ВЫБОРА * ИЗ sqlite_master
- тип = таблица
- имя = TAB_JOUEUR
- tbl_name = TAB_JOUEUR
- корневая страница = 2
- sql = СОЗДАТЬ ТАБЛИЦУ TAB_JOUEUR ('tjoueur_id' ЦЕЛОЕ ЧИСЛО, ТЕКСТ "tjoueur_prenom", ТЕКСТ "tjoueur_nom", ТЕКСТ"tjoueur_ville", ПЕРВИЧНЫЙ КЛЮЧ (АВТОИНКРЕМЕНТ "tjoueur_id"))
а потом
- тип = таблица
- имя = sqlite_sequence
- tbl_name = последовательность sqlite_sequence
- корневая страница = 3
- sql = СОЗДАТЬ ТАБЛИЦУ sqlite_sequence(имя,раздел)
Как вы можете видеть, поле sql относительно таблицы "TAB_JOUEUR" является вызовом CREATE, но это не чистая копия исходного вызова CREATE. В исходном варианте как "создать" в нижнем регистре были дополнительные пробелы и есть "ЕСЛИ НЕ СУЩЕСТВУЕТ", которого у нас нет в поле sql sqlite_master. Но часть между () точно такая же (одинарная кавычка для tjoueur_id, двойная для tjoueur_nom, дополнительные пробелы...)
4 - Хитрость
Чтобы переименовать таблицу, вам просто нужно сделать ВЫБОР в sqlite_master, чтобы получить запись таблицы, которую вы хотите переименовать. Затем измените содержимое поля sql, чтобы заменить "старое имя" на "новое", и выполните обновление, используя корневую страницу в качестве идентификатора. Вот так:
void alter_table(String old_name, String new_name){
int rc ;
String data_rootpage;
String data_sql;
int pos_start;
char *err_msg = 0;
String query_sql = "SELECT rootpage,sql FROM sqlite_master WHERE type='table' AND name = '"+old_name+"'";
const char* sql = query_sql.c_str();
rc = sqlite3_prepare_v2(db_handle, sql, -1, &stmt, NULL);
rc = sqlite3_step(stmt);
data_rootpage = (const char *)sqlite3_column_text(stmt, 0);
data_sql = (const char *)sqlite3_column_text(stmt, 1);
// Find first ( in the string
pos_start = data_sql.indexOf ('(');
String tmp = "CREATE TABLE "+new_name+" "+data_sql.substring(pos_start);
query_sql = "UPDATE sqlite_master SET sql='"+tmp+"', name='"+new_name+"', tbl_name='"+new_name+"' WHERE rootpage = "+data_rootpage;
sql = query_sql.c_str();
rc = sqlite3_exec(db_handle, sql, 0, 0, &err_msg);
sqlite3_close(db_handle);
sqlite3_initialize();
sql_connect("/littlefs/transcom.db", &db_handle);}
Это базовый и не требующий тестирования (вы должны добавить его!). Я начинаю с поиска записи, касающейся таблицы "old_name". В поле sql я ищу " ( " , а затем заменяю первую часть данных поля sql новым именем таблицы. Затем я обновляю. Обратите внимание, что я не избегаю цитаты, потому что ранее я делал вызов CREATE без каких-либо " или " (проще!)
После этого я выполняю sqlite3_close (), а затем повторно открываю с помощью sqlite3_initialize(); Если вы этого не сделаете, SQLite не учитывает изменения (возможно, есть кэш для данных).
5 - Отличное, но... недостаточно!
Если вы попытаетесь это сделать, вы получите сообщение об ошибке при вызове обновления. Это потому, что для вас, "пользователь", sqlite_master-это таблица только для чтения. Поэтому вы не можете обновить его. Чтобы предотвратить запись в какую-либо таблицу (например, в таблицу sqlite_master), при ее создании SQLite устанавливает флаг "только для чтения". Хитрость заключается в том, чтобы отредактировать исходный код, чтобы придать этому флагу значение "чтение и запись". В файле sqlite3.c библиотеки это строка 17420. Вы увидите:
#define TF_Readonly 0x0001 /* Read-only system table */
Просто измените его на
#define TF_Readonly 0x0000 /* Read-only system table */
Поэтому, когда SQLite создаст системную таблицу, он установит флаг "чтение+запись", и, таким образом, вы сможете обновить таблицу sqlite_master. Вам нужно снова скомпилировать de lib (в IDE Arduino просто выберите неправильную плату, попробуйте скомпилировать, получите ошибку, выберите ESP32 и скомпилируйте снова, и IDE выполнит полную компиляцию lib)
Если у вас есть триггер и какая-то другая конкретная функция, возможно, было бы неплохо проверить результат select * from sqlite_master, чтобы узнать, нужно ли также обновлять некоторые записи. Но для базовой таблицы это сделает свою работу.
Надеюсь, это поможет.
Править
Если вы добавляете индекс (или более одного) в свою таблицу, вам также придется обновить записи "индекса" из sqlite_master. Они такие:
- тип = индекс
- имя = index_ttest_nom
- tbl_name = TAB_TEST
- корневая страница = 6
- sql = СОЗДАЙТЕ УНИКАЛЬНЫЙ ИНДЕКС index_ttest_nom НА ВКЛАДКЕ TAB_TEST (ttest_nom)
Поэтому просто обновите значение tbl_name, указав новое имя таблицы, и обновите поле sql таким же образом.
- esp32, platformio A fatal error occurred: Packet content transfer stopped (received 8 bytes) *** [upload] Error 2
- Как выбрать альтернативные контакты I2C на ESP32?
- Драйверы для чипа последовательного порта CH9102X
- Как преобразовать форматированный оператор print в строковую переменную?
- ESP32 - "Детектор Браунаута был активирован" при запуске Wi-Fi
- Питание esp32cam от аккумулятора
- Контакты RX и TX на esp32
- ESP32: отключить детектор отключения питания
Похоже на известную проблему без известного решения: https://github.com/siara-cc/esp32_arduino_sqlite3_lib/issues/10, @Majenko
Благодарю. я посмотрю, так ли это и в случае с полем добавления., @Peter