diff --git a/README.md b/README.md index 416bb98..89ed5e5 100644 --- a/README.md +++ b/README.md @@ -1,225 +1,440 @@ + + # μSQLite library module for MicroPython + + **WARNING**: This project is in the beta development stage and may be subject to change. + + `usqlite` is a SQL database library module for [MicroPython](https://github.com/micropython/micropython) built on the popular [SQLite C library](https://sqlite.org/). + + The `usqlite` SQL interface is designed to be a subset of the DB-API 2.0 specification as specified by [**PEP 249**](https://www.python.org/dev/peps/pep-0249). The `usqlite` API interface is also highly compatible with the standard [**`sqlite3`**](https://docs.python.org/3/library/sqlite3.html) library for Python with a few extra features. + + Using the `usqlite` module in a MicroPython application is relatively simple. The application imports the `usqlite` library, connects to a database and then executes SQL commands. For example: + + ```python + import usqlite -if not usqlite.mem_status(): - usqlite.mem_status(True) # Enable memory usage monitoring + + +if not usqlite.mem_status(): + +usqlite.mem_status(True) # Enable memory usage monitoring + + con = usqlite.connect("data.db") + + con.executemany( - "BEGIN TRANSACTION;" - "CREATE TABLE IF NOT EXISTS data (name TEXT, year INT);"+ - "INSERT INTO data VALUES ('Larry', 1902);"+ - "INSERT INTO data VALUES ('Curly', 1903);"+ - "INSERT INTO data VALUES ('Moe', 1897);"+ - "INSERT INTO data VALUES ('Shemp', 1895);"+ - "COMMIT;") + +"BEGIN TRANSACTION;" + +"CREATE TABLE IF NOT EXISTS data (name TEXT, year INT);"+ + +"INSERT INTO data VALUES ('Larry', 1902);"+ + +"INSERT INTO data VALUES ('Curly', 1903);"+ + +"INSERT INTO data VALUES ('Moe', 1897);"+ + +"INSERT INTO data VALUES ('Shemp', 1895);"+ + +"COMMIT;") + + with con.execute("SELECT * from data") as cur: - for row in cur: - print("stooge:", row) - + +for row in cur: + +print("stooge:", row) + con.close() + + print("usqlite mem - current:", usqlite.mem_current(), "peak:", usqlite.mem_peak()) + + ``` + + The database files created or used by `usqlite` are compatible with SQLite database files created by applications on other platforms and operating systems such as [`DB Browser for SQLite`](https://sqlitebrowser.org/). + + --- + ## Getting Started + + The `usqlite` module is designed so that it can be easily compiled and included in MicroPython builds alongside other [external modules](https://docs.micropython.org/en/latest/develop/cmodules.html). + + Version [**3.47.0**](https://sqlite.org/releaselog/3_47_0.html) of the [**SQLite** amalgamated source](https://sqlite.org/amalgamation.html) files `sqlite3.h` and `sqlite3.c` are included with the `usqlite` source code. These may be replaced with alternate or custom amalgamated versions built from the [canonical SQLite source code](https://sqlite.org/download.html). + + ### Project directory structure -The directory structure used in the develoment of this module is as shown below. The `usqlite` project can easily be modified to suit your own alternate project structure requirements with minor changes to the code. + +The directory structure used in the develoment of this module is as shown below. The `usqlite` project can easily be modified to suit your own alternate project structure requirements with minor changes to the code. + + + ``` + ~/ - ├── micropython # MicroPython source code - └── modules - ├── micropython.cmake - ├── usqlite # μSQLite source code - ├── ... - └── ... + +├── micropython # MicroPython source code + +└── modules + +├── micropython.cmake + +├── usqlite # μSQLite source code + +├── ... + +└── ... + ``` + + ```sh + cd -mkdir modules -mkdir sqlite -git clone https://github.com/micropython/micropython.git -cd modules -git clone https://github.com/spatialdude/usqlite.git + +mkdir modules + +mkdir sqlite + +git clone https://github.com/micropython/micropython.git + +cd modules + +git clone https://github.com/spatialdude/usqlite.git + ``` + + Typical `micropython.cmake` + + ```cmake + # This top-level micropython.cmake is responsible for listing + # the individual modules we want to include. + # Paths are absolute, and ${CMAKE_CURRENT_LIST_DIR} can be + # used to prefix subdirectories. + + include(${CMAKE_CURRENT_LIST_DIR}/usqlite/micropython.cmake) + ``` + + ### Compiling + +Refer to the [Wiki] for build instructions.-(https://github.com/spatialdude/usqlite/wiki) Refer to the MicroPython's [Getting Started](https://github.com/micropython/micropython/wiki/Getting-Started) wiki and documentation for more details on setting up a build environment. + + #### Ports + + * [ESP32](https://github.com/spatialdude/usqlite/wiki/esp32) + * [Raspberry Pi Pico](https://github.com/spatialdude/usqlite/wiki/rp2) + * [Unix](https://github.com/spatialdude/usqlite/wiki/unix) + * [Windows](https://github.com/spatialdude/usqlite/wiki/windows) + * [Pybricks for smart LEGO® hubs](https://github.com/spatialdude/usqlite/wiki/pybricks) + + ### Custom Configurations + + The default configuration of `usqlite` is intended to suit typical project requirements. This includes which **SQLite** components are included, memory allocation configuration and debug options. -The `usqlite` configuration settings can be found in the C header file [`usqlite_config.h`](https://github.com/spatialdude/usqlite/blob/main/usqlite_config.h). + + +The `usqlite` configuration settings can be found in the C header file [`usqlite_config.h`](https://github.com/spatialdude/usqlite/blob/main/usqlite_config.h). + + ### Memory allocation configuration + + MicroPython builds often need to account for constrained memory enviroments. Fortunately the SQLite library is lightweight and has been designed so that it can be configured to accomodate many [different memory environment needs](https://sqlite.org/malloc.html). + + **SQLite** does an excellent job of keeping memory usage as low as possible, so `usqlite` can be made to work well even in very tightly constrained memory spaces. The `usqlite` module provides functions that allow your application to monitor memory usage. -The default configuration of `usqlite` implements a custom dymanic memory allocator that uses MicroPython's GC heap. Memory demands placed on the heap will vary greatly depending on the complexity of the SQL of your application. + + +The default configuration of `usqlite` implements a custom dymanic memory allocator that uses MicroPython's GC heap. Memory demands placed on the heap will vary greatly depending on the complexity of the SQL of your application. + + `usqlite` can be configured with an alternate memory configuration allocation that limits the memory to a fixed static heap size. + + --- + + ## `usqlite` library API + + As the `usqlite` API interface is highly compatible with the standard [**`sqlite3`**](https://docs.python.org/3/library/sqlite3.html) library for for Python, much of the `sqlite3` documentation is also applicable to `usqlite`. + + The details in this section will describe differences and API features unique to `usqlite`. Please also refer to the [**`sqlite3`**](https://docs.python.org/3/library/sqlite3.html) documentation as a general reference. + + ### **`usqlite`** global object + + #### Constants + + |Name|Type|Description| + |---|---|---| + |`version`|`str`|`usqlite` module version string e.g. `"0.1.8"`| + |`version_info`|`tuple`|`usqlite` module version tuple e.g `(0,1,8`)| + |`sqlite_version`|`str`|SQLite version string e.g. `"3.47.0"`| + |`sqlite_version_info`|`tuple`|SQLite version tuple e.g `(3,47,0`)| + |`sqlite_version_number`|`int`|SQLite version number e.g `3047000`| + + #### Functions + + |Signature|Return type|Description| + |---|---|---| + |`connect()`|`Connection`|| + |`statement_complete()`|`bool`|| + |`mem_current()`|`int`|Current `usqlite` module memory usage in bytes.| + |`mem_peak()`|`int`|Peak `usqlite` module memory usage in bytes. Include optional `bool` parameter `True` to reset peak memory usage statistics.| -|`mem_status()`|`bool`| Set or returns current status of memory usage monitoring. The memory usage status monitoring can be enabled or disabled via an optional `bool` parameter. The status can only be set on initialisation before the execution of any SQL.| + +|`mem_status()`|`bool`| Set or returns current status of memory usage monitoring. The memory usage status monitoring can be enabled or disabled via an optional `bool` parameter. The status can only be set on initialisation before the execution of any SQL.| + + ### **Connection** object + + A `Connection` object is returned by the `usqlite.connect()` function. + + #### Attributes + + |Name|Type|Access|Description| + |---|---|---|---| + |`row_type`|`str`|`R/W`|Get/set row data type: `tuple` (default), `dict`, or `row`| + |`total_changes`|`int`|`R`|| + + #### Functions + + |Name|Return type|Description| + |---|---|---| + |`close()`|`None`|Close the connection and all cursors associated with the connection.| + |`execute()`|`Cursor`|| + |`executemany()`|`Cursor`|| + |`set_trace_callback()`|`None`|| + + ### **Cursor** object + + #### Attributes + + |Name|Type|Access|Description| + |---|---|---|---| + |`arraysize`|`int`|`R/W`|| + |`connection`|`Connection`|`R`|| + |`description`|`list`|`R`|| + |`lastrowid`|`int`|`R`|| + |`rowcount`|`int`|`R`|| + + #### Functions + + |Name|Return type|Description| + |---|---|---| + |`close()`|`None`|| + |`execute()`|`self`|| + |`executemany()`|`self`|| + |`fetchone()`|`Any`|| + |`fetchmany()`|`list`|| + |`fetchall()`|`list`|| + + ### Data row objects + + The data type of rows returned by SQL statments is determined by the `Connection` object's `row_type` property. The default `row_type` is `tuple`. + + If the `row_type` is `dict` then each row of data is returnened in as a `dict` object with the column value key names set to each value's respective column name. + + The `row_type` row is a specialised type of `tuple` object with an the addional `keys` property that returns a `tuple` of column names. + + ### `execute` function parameter substitution + + `usqlite` has an extended range of [SQL expression](https://sqlite.org/lang_expr.html) parameter substitution methods available. + + #### `?` and `?NNN` indexed parameters + + Indexed parameter values are be supplied as a `tuple` or `list` + + For convenience, if the SQL statment contains a single `?` parameter, the parameter value can also be supplied as a single value. + + e.g. + + ``` + con.execute("SELECT * FROM data WHERE year > ?", 1900) + ``` + is equivalent to + ``` + con.execute("SELECT * FROM data WHERE year > ?", (1900,)) + ``` + + #### `:AAAA`, `@AAAA` and `$AAAA` named parameters + + Named parameters are passed as a `dict` object. The value keys must match the parameter names. + + e.g. + ``` + con.execute("SELECT * FROM data WHERE name=:name", {"name":"Larry"}) -``` +``` diff --git a/micropython.cmake b/micropython.cmake index dbb11f5..3fe9a99 100644 --- a/micropython.cmake +++ b/micropython.cmake @@ -1,7 +1,5 @@ -# Create an INTERFACE library for our C module. add_library(usermod_usqlite INTERFACE) -# Add our source files to the lib target_sources(usermod_usqlite INTERFACE ${CMAKE_CURRENT_LIST_DIR}/usqlite_module.c ${CMAKE_CURRENT_LIST_DIR}/usqlite_connection.c @@ -14,17 +12,8 @@ target_sources(usermod_usqlite INTERFACE ${CMAKE_CURRENT_LIST_DIR}/usqlite.c ) -if(IDF_TARGET MATCHES "^esp32") - target_compile_options(usermod_usqlite INTERFACE - -mtext-section-literals - ) -endif() - - -# Add the current directory as an include directory. target_include_directories(usermod_usqlite INTERFACE ${CMAKE_CURRENT_LIST_DIR} ) -# Link our INTERFACE library to the usermod target. target_link_libraries(usermod INTERFACE usermod_usqlite) diff --git a/usqlite.c b/usqlite.c index bdc69b6..f43d40d 100644 --- a/usqlite.c +++ b/usqlite.c @@ -56,30 +56,32 @@ SOFTWARE. #endif #ifdef SQLITE_OMIT_ANALYZE -SQLITE_PRIVATE void sqlite3Analyze(Parse *pParse, Token *pName1, Token *pName2) { +SQLITE_PRIVATE void sqlite3Analyze(Parse* pParse, Token* pName1, Token* pName2) { } #if __GNUC__ -SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3 *db, int iDB) { +SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3* db, int iDB) { + return SQLITE_OK; } -SQLITE_PRIVATE void sqlite3DeleteIndexSamples(sqlite3 *db, Index *pIdx) { +SQLITE_PRIVATE void sqlite3DeleteIndexSamples(sqlite3* db, Index* pIdx) { } #endif #endif #ifdef SQLITE_OMIT_ATTACH -SQLITE_PRIVATE void sqlite3Vacuum(Parse *p, Token *t, Expr *e) { +SQLITE_PRIVATE void sqlite3Vacuum(Parse* p, Token* t, Expr* e) { } -SQLITE_PRIVATE int sqlite3DbIsNamed(sqlite3 *db, int iDb, const char *zName) { +SQLITE_PRIVATE int sqlite3DbIsNamed(sqlite3* db, int iDb, const char* zName) { return 0; } -SQLITE_PRIVATE void sqlite3Attach(Parse *p, Expr *e1, Expr *e2, Expr *e3) { +SQLITE_PRIVATE void sqlite3Attach(Parse* p, Expr* e1, Expr* e2, Expr* e3) { } -SQLITE_PRIVATE void sqlite3Detach(Parse *p, Expr *e) { +SQLITE_PRIVATE void sqlite3Detach(Parse* p, Expr* e) { } #ifdef __GNUC__ -SQLITE_PRIVATE int sqlite3RunVacuum(char **c, sqlite3 *db, int i, sqlite3_value *v) { +SQLITE_PRIVATE int sqlite3RunVacuum(char** c, sqlite3* db, int i, sqlite3_value* v) { + return SQLITE_OK; } #endif #endif diff --git a/usqlite_connection.c b/usqlite_connection.c index a05bdf4..49259a2 100644 --- a/usqlite_connection.c +++ b/usqlite_connection.c @@ -32,11 +32,11 @@ static mp_obj_t usqlite_connection_close(mp_obj_t self_in); // ------------------------------------------------------------------------------ -static mp_obj_t usqlite_connection_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { - usqlite_connection_t *self = m_new_obj(usqlite_connection_t); +static mp_obj_t usqlite_connection_make_new(const mp_obj_type_t* type, size_t n_args, size_t n_kw, const mp_obj_t* args) { + usqlite_connection_t* self = m_new_obj(usqlite_connection_t); self->base.type = &usqlite_connection_type; - self->db = (sqlite3 *)MP_OBJ_TO_PTR(args[0]); + self->db = (sqlite3*)MP_OBJ_TO_PTR(args[0]); self->row_type = MP_QSTR_tuple; mp_obj_list_init(&self->cursors, 0); @@ -45,8 +45,8 @@ static mp_obj_t usqlite_connection_make_new(const mp_obj_type_t *type, size_t n_ // ------------------------------------------------------------------------------ -static void usqlite_connection_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - usqlite_connection_t *self = MP_OBJ_TO_PTR(self_in); +static void usqlite_connection_print(const mp_print_t* print, mp_obj_t self_in, mp_print_kind_t kind) { + usqlite_connection_t* self = MP_OBJ_TO_PTR(self_in); mp_printf(print, "<%s '%s'>", mp_obj_get_type_str(self_in), self->db ? sqlite3_db_filename(self->db, NULL) : ""); } @@ -61,17 +61,20 @@ static mp_obj_t usqlite_connection_close(mp_obj_t self_in) { return mp_const_none; } - for (size_t i = 0; i < self->cursors.len; i++) + // Safely close all cursors. + // Since calling usqlite_cursor_close() now REMOVES the item from the list, + // we just keep closing the first item until the list is empty. + while (self->cursors.len > 0) { - mp_obj_t cursor = self->cursors.items[i]; - self->cursors.items[0] = mp_const_none; + // Get the first cursor + mp_obj_t cursor = self->cursors.items[0]; + + // This call will close it AND remove it from self->cursors, reducing len by 1 usqlite_cursor_close(cursor); - #if MICROPY_MALLOC_USES_ALLOCATED_SIZE - m_free(MP_OBJ_TO_PTR(cursor), sizeof(usqlite_cursor_t)); - #else - m_free(MP_OBJ_TO_PTR(cursor)); - #endif } + + // List is now empty + self->cursors.len = 0; usqlite_logprintf(___FUNC___ " closing '%s'\n", sqlite3_db_filename(self->db, NULL)); sqlite3_close(self->db); @@ -97,7 +100,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(usqlite_connection_del_obj, usqlite_connection_del); // ------------------------------------------------------------------------------ static mp_obj_t usqlite_connection_cursor(mp_obj_t self_in) { - usqlite_connection_t *self = MP_OBJ_TO_PTR(self_in); + usqlite_connection_t* self = MP_OBJ_TO_PTR(self_in); mp_obj_t args[2] = { @@ -105,19 +108,19 @@ static mp_obj_t usqlite_connection_cursor(mp_obj_t self_in) { mp_const_none }; - #if defined(MP_OBJ_TYPE_GET_SLOT) +#if defined(MP_OBJ_TYPE_GET_SLOT) return MP_OBJ_TYPE_GET_SLOT(&usqlite_cursor_type, make_new)(NULL, MP_ARRAY_SIZE(args), 0, args); - #else +#else return usqlite_cursor_type.make_new(NULL, MP_ARRAY_SIZE(args), 0, args); - #endif +#endif } static MP_DEFINE_CONST_FUN_OBJ_1(usqlite_connection_cursor_obj, usqlite_connection_cursor); // ------------------------------------------------------------------------------ -static mp_obj_t usqlite_connection_execute(size_t n_args, const mp_obj_t *args) { - usqlite_connection_t *self = MP_OBJ_TO_PTR(args[0]); +static mp_obj_t usqlite_connection_execute(size_t n_args, const mp_obj_t* args) { + usqlite_connection_t* self = MP_OBJ_TO_PTR(args[0]); mp_obj_t xargs[4] = { @@ -133,11 +136,11 @@ static mp_obj_t usqlite_connection_execute(size_t n_args, const mp_obj_t *args) nxargs++; } - #if defined(MP_OBJ_TYPE_GET_SLOT) +#if defined(MP_OBJ_TYPE_GET_SLOT) return MP_OBJ_TYPE_GET_SLOT(&usqlite_cursor_type, make_new)(NULL, nxargs, 0, xargs); - #else +#else return usqlite_cursor_type.make_new(NULL, nxargs, 0, xargs); - #endif +#endif } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(usqlite_connection_execute_obj, 2, 3, usqlite_connection_execute); @@ -145,7 +148,7 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(usqlite_connection_execute_obj, 2, 3, // ------------------------------------------------------------------------------ static mp_obj_t usqlite_connection_executemany(mp_obj_t self_in, mp_obj_t sql) { - usqlite_connection_t *self = MP_OBJ_TO_PTR(self_in); + usqlite_connection_t* self = MP_OBJ_TO_PTR(self_in); mp_obj_t args[3] = { @@ -154,26 +157,27 @@ static mp_obj_t usqlite_connection_executemany(mp_obj_t self_in, mp_obj_t sql) { sql }; - #if defined(MP_OBJ_TYPE_GET_SLOT) +#if defined(MP_OBJ_TYPE_GET_SLOT) return MP_OBJ_TYPE_GET_SLOT(&usqlite_cursor_type, make_new)(NULL, MP_ARRAY_SIZE(args), 0, args); - #else +#else return usqlite_cursor_type.make_new(NULL, MP_ARRAY_SIZE(args), 0, args); - #endif +#endif } static MP_DEFINE_CONST_FUN_OBJ_2(usqlite_connection_executemany_obj, usqlite_connection_executemany); // ------------------------------------------------------------------------------ -static int traceCallback(unsigned uMask, void *context, void *p, void *x) { - usqlite_connection_t *self = (usqlite_connection_t *)context; - sqlite3_stmt *stmt = (sqlite3_stmt *)p; - char *xsql = sqlite3_expanded_sql(stmt); +static int traceCallback(unsigned uMask, void* context, void* p, void* x) { + usqlite_connection_t* self = (usqlite_connection_t*)context; + sqlite3_stmt* stmt = (sqlite3_stmt*)p; + char* xsql = sqlite3_expanded_sql(stmt); if (xsql) { mp_call_function_1(self->trace_callback, mp_obj_new_str(xsql, strlen(xsql))); sqlite3_free(xsql); - } else { - const char *sql = sqlite3_sql(stmt); + } + else { + const char* sql = sqlite3_sql(stmt); mp_call_function_1(self->trace_callback, mp_obj_new_str(sql, strlen(sql))); } @@ -181,15 +185,17 @@ static int traceCallback(unsigned uMask, void *context, void *p, void *x) { } static mp_obj_t usqlite_connection_set_trace_callback(mp_obj_t self_in, mp_obj_t callback) { - usqlite_connection_t *self = MP_OBJ_TO_PTR(self_in); + usqlite_connection_t* self = MP_OBJ_TO_PTR(self_in); if (callback == mp_const_none) { self->trace_callback = mp_const_none; sqlite3_trace_v2(self->db, 0, NULL, NULL); - } else if (mp_obj_is_callable(callback)) { + } + else if (mp_obj_is_callable(callback)) { self->trace_callback = callback; sqlite3_trace_v2(self->db, SQLITE_TRACE_STMT, traceCallback, self); - } else { + } + else { mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid callback")); } @@ -200,22 +206,22 @@ static MP_DEFINE_CONST_FUN_OBJ_2(usqlite_connection_set_trace_callback_obj, usql // ------------------------------------------------------------------------------ -void usqlite_connection_register(usqlite_connection_t *connection, mp_obj_t cursor) { +void usqlite_connection_register(usqlite_connection_t* connection, mp_obj_t cursor) { mp_obj_t cursors = MP_OBJ_FROM_PTR(&connection->cursors); mp_obj_list_append(cursors, cursor); } // ------------------------------------------------------------------------------ -void usqlite_connection_deregister(usqlite_connection_t *connection, mp_obj_t cursor) { +void usqlite_connection_deregister(usqlite_connection_t* connection, mp_obj_t cursor) { mp_obj_t cursors = MP_OBJ_FROM_PTR(&connection->cursors); mp_obj_list_remove(cursors, cursor); } // ------------------------------------------------------------------------------ -static void usqlite_connection_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { - usqlite_connection_t *self = (usqlite_connection_t *)self_in; +static void usqlite_connection_attr(mp_obj_t self_in, qstr attr, mp_obj_t* dest) { + usqlite_connection_t* self = (usqlite_connection_t*)self_in; if (dest[0] == MP_OBJ_NULL) { if ((usqlite_lookup(self_in, attr, dest))) { @@ -224,21 +230,22 @@ static void usqlite_connection_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) switch (attr) { - case MP_QSTR_row_type: - dest[0] = MP_OBJ_NEW_QSTR(self->row_type); - break; + case MP_QSTR_row_type: + dest[0] = MP_OBJ_NEW_QSTR(self->row_type); + break; - case MP_QSTR_total_changes: - dest[0] = mp_obj_new_int(sqlite3_total_changes(self->db)); - break; + case MP_QSTR_total_changes: + dest[0] = mp_obj_new_int(sqlite3_total_changes(self->db)); + break; } - } else if (dest[1] != MP_OBJ_NULL) { + } + else if (dest[1] != MP_OBJ_NULL) { switch (attr) { - case MP_QSTR_row_type: - self->row_type = mp_obj_str_get_qstr(dest[1]); - dest[0] = MP_OBJ_NULL; - break; + case MP_QSTR_row_type: + self->row_type = mp_obj_str_get_qstr(dest[1]); + dest[0] = MP_OBJ_NULL; + break; } // delete/store attribute @@ -247,7 +254,7 @@ static void usqlite_connection_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) // ------------------------------------------------------------------------------ -static mp_obj_t usqlite_connection_exit(size_t n_args, const mp_obj_t *args) { +static mp_obj_t usqlite_connection_exit(size_t n_args, const mp_obj_t* args) { usqlite_logprintf(___FUNC___ "\n"); usqlite_connection_close(args[0]); @@ -285,7 +292,7 @@ MP_DEFINE_CONST_OBJ_TYPE( print, usqlite_connection_print, attr, usqlite_connection_attr, locals_dict, &usqlite_connection_locals_dict - ); +); #else const mp_obj_type_t usqlite_connection_type = { @@ -293,7 +300,7 @@ const mp_obj_type_t usqlite_connection_type = .name = MP_QSTR_Connection, .print = usqlite_connection_print, .make_new = usqlite_connection_make_new, - .locals_dict = (mp_obj_dict_t *)&usqlite_connection_locals_dict, + .locals_dict = (mp_obj_dict_t*)&usqlite_connection_locals_dict, .attr = usqlite_connection_attr, }; #endif diff --git a/usqlite_cursor.c b/usqlite_cursor.c index f779802..5d08335 100644 --- a/usqlite_cursor.c +++ b/usqlite_cursor.c @@ -31,56 +31,60 @@ SOFTWARE. // ------------------------------------------------------------------------------ -static mp_obj_t usqlite_cursor_execute(size_t n_args, const mp_obj_t *args); +static mp_obj_t usqlite_cursor_execute(size_t n_args, const mp_obj_t* args); static mp_obj_t usqlite_cursor_executemany(mp_obj_t self_in, mp_obj_t sql_in); -static mp_obj_t row_tuple(usqlite_cursor_t *cursor); -static mp_obj_t row_dict(usqlite_cursor_t *cursor); -static mp_obj_t row_type(usqlite_cursor_t *cursor); +static mp_obj_t row_tuple(usqlite_cursor_t* cursor); +static mp_obj_t row_dict(usqlite_cursor_t* cursor); +static mp_obj_t row_type(usqlite_cursor_t* cursor); // ------------------------------------------------------------------------------ -static mp_obj_t usqlite_cursor_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { +static mp_obj_t usqlite_cursor_make_new(const mp_obj_type_t* type, size_t n_args, size_t n_kw, const mp_obj_t* args) +{ usqlite_row_type_initialize(); - usqlite_cursor_t *self = m_new_obj(usqlite_cursor_t); + usqlite_cursor_t* self = m_new_obj(usqlite_cursor_t); mp_obj_t self_obj = MP_OBJ_FROM_PTR(self); memset(self, 0, sizeof(usqlite_cursor_t)); self->base.type = &usqlite_cursor_type; - self->connection = (usqlite_connection_t *)MP_OBJ_TO_PTR(args[0]); + self->connection = (usqlite_connection_t*)MP_OBJ_TO_PTR(args[0]); self->arraysize = 1; usqlite_connection_register(self->connection, self_obj); switch (self->connection->row_type) { - case MP_QSTR_row: - self->rowfactory = row_type; - break; - - case MP_QSTR_dict: - self->rowfactory = row_dict; - break; - - case MP_QSTR_tuple: - default: - self->rowfactory = row_tuple; - break; + case MP_QSTR_row: + self->rowfactory = row_type; + break; + + case MP_QSTR_dict: + self->rowfactory = row_dict; + break; + + case MP_QSTR_tuple: + default: + self->rowfactory = row_tuple; + break; } - if (args[1] == mp_const_true) { + if (args[1] == mp_const_true) + { return usqlite_cursor_executemany(self_obj, args[2]); - } else if (args[1] == mp_const_false) { + } + else if (args[1] == mp_const_false) + { mp_obj_t xargs[3] = { self_obj, - args[2] - }; + args[2] }; size_t nxargs = 2; - if (n_args == 4) { + if (n_args == 4) + { xargs[2] = args[3]; nxargs++; } @@ -93,26 +97,35 @@ static mp_obj_t usqlite_cursor_make_new(const mp_obj_type_t *type, size_t n_args // ------------------------------------------------------------------------------ -static void usqlite_cursor_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - usqlite_cursor_t *self = MP_OBJ_TO_PTR(self_in); +static void usqlite_cursor_print(const mp_print_t* print, mp_obj_t self_in, mp_print_kind_t kind) +{ + usqlite_cursor_t* self = MP_OBJ_TO_PTR(self_in); mp_printf(print, "<%s '%s'>", mp_obj_get_type_str(self_in), self->stmt ? sqlite3_sql(self->stmt) : "NULL"); } // ------------------------------------------------------------------------------ -mp_obj_t usqlite_cursor_close(mp_obj_t self_in) { +mp_obj_t usqlite_cursor_close(mp_obj_t self_in) +{ LOGFUNC; - // usqlite_logprintf(___FUNC___ "\n"); - usqlite_cursor_t *self = (usqlite_cursor_t *)MP_OBJ_TO_PTR(self_in); - if (!self->stmt) { - return mp_const_none; + + // 1. Deregister FIRST to prevent memory leaks and zombies + if (self->connection) + { + usqlite_connection_deregister(self->connection, self_in); + self->connection = NULL; // Mark as detached so we don't use it again } - usqlite_logprintf(___FUNC___ " closing: '%s'\n", sqlite3_sql(self->stmt)); - sqlite3_finalize(self->stmt); - self->stmt = NULL; + // 2. Finalize the statement + if (self->stmt) + { + usqlite_logprintf(___FUNC___ " closing: '%s'\n", sqlite3_sql(self->stmt)); + sqlite3_finalize(self->stmt); + self->stmt = NULL; + } + self->rowcount = -1; self->rc = SQLITE_OK; @@ -123,29 +136,30 @@ MP_DEFINE_CONST_FUN_OBJ_1(usqlite_cursor_close_obj, usqlite_cursor_close); // ------------------------------------------------------------------------------ -static int stepExecute(usqlite_cursor_t *self) { +static int stepExecute(usqlite_cursor_t* self) +{ self->rc = sqlite3_step(self->stmt); switch (self->rc) { - case SQLITE_OK: - break; + case SQLITE_OK: + break; - case SQLITE_ROW: - self->rowcount++; - break; + case SQLITE_ROW: + self->rowcount++; + break; - case SQLITE_DONE: - break; + case SQLITE_DONE: + break; - case SQLITE_ERROR: - default: - mp_raise_msg_varg(&usqlite_Error, - MP_ERROR_TEXT("error (%d): %s sql: '%s'"), - self->rc, - sqlite3_errmsg(self->connection->db), - sqlite3_sql(self->stmt)); - break; + case SQLITE_ERROR: + default: + mp_raise_msg_varg(&usqlite_Error, + MP_ERROR_TEXT("error (%d): %s sql: '%s'"), + self->rc, + sqlite3_errmsg(self->connection->db), + sqlite3_sql(self->stmt)); + break; } return self->rc; @@ -153,28 +167,43 @@ static int stepExecute(usqlite_cursor_t *self) { // ------------------------------------------------------------------------------ -static int bindParameter(sqlite3_stmt *stmt, int index, mp_obj_t value) { - if (value == mp_const_none) { +static int bindParameter(sqlite3_stmt* stmt, int index, mp_obj_t value) +{ + if (value == mp_const_none) + { return sqlite3_bind_null(stmt, index); - } else if (mp_obj_is_integer(value)) { + } + else if (mp_obj_is_integer(value)) + { return sqlite3_bind_int(stmt, index, mp_obj_get_int(value)); - } else if (mp_obj_is_str(value)) { + } + else if (mp_obj_is_str(value)) + { GET_STR_DATA_LEN(value, str, nstr); - return sqlite3_bind_text(stmt, index, (const char *)str, nstr, NULL); - } else if (mp_obj_is_type(value, &mp_type_float)) { + // ERROR: NULL means static. changed to -1 (SQLITE_TRANSIENT) for safety. + return sqlite3_bind_text(stmt, index, (const char*)str, nstr, (void*)-1); + } + else if (mp_obj_is_type(value, &mp_type_float)) + { return sqlite3_bind_double(stmt, index, mp_obj_get_float(value)); - } else if (mp_obj_is_type(value, &mp_type_bytes)) { + } + else if (mp_obj_is_type(value, &mp_type_bytes)) + { GET_STR_DATA_LEN(value, bytes, nbytes); - return sqlite3_bind_blob(stmt, index, bytes, nbytes, NULL); + // ERROR: NULL means static. changed to -1 (SQLITE_TRANSIENT) for safety. + return sqlite3_bind_blob(stmt, index, bytes, nbytes, (void*)-1); } - #if MICROPY_PY_BUILTINS_BYTEARRAY - if (mp_obj_is_type(value, &mp_type_bytearray)) { +#if MICROPY_PY_BUILTINS_BYTEARRAY + if (mp_obj_is_type(value, &mp_type_bytearray)) + { mp_buffer_info_t buffer; - if (mp_get_buffer(value, &buffer, MP_BUFFER_READ)) { - return sqlite3_bind_blob(stmt, index, buffer.buf, buffer.len, NULL); + if (mp_get_buffer(value, &buffer, MP_BUFFER_READ)) + { + // ERROR: NULL means static. changed to -1 (SQLITE_TRANSIENT) for safety. + return sqlite3_bind_blob(stmt, index, buffer.buf, buffer.len, (void*)-1); } } - #endif +#endif mp_raise_msg_varg(&usqlite_Error, MP_ERROR_TEXT("Unsupported parameter value type '%s'"), @@ -185,63 +214,80 @@ static int bindParameter(sqlite3_stmt *stmt, int index, mp_obj_t value) { // ------------------------------------------------------------------------------ -static int bindParameters(sqlite3_stmt *stmt, mp_obj_t values) { +static int bindParameters(sqlite3_stmt* stmt, mp_obj_t values) +{ size_t nParams = sqlite3_bind_parameter_count(stmt); - if (!nParams) { + if (!nParams) + { return SQLITE_OK; } - const char *name = sqlite3_bind_parameter_name(stmt, 1); - if (name && *name != '?') { - if (!mp_obj_is_dict_or_ordereddict(values)) { + const char* name = sqlite3_bind_parameter_name(stmt, 1); + if (name && *name != '?') + { + if (!mp_obj_is_dict_or_ordereddict(values)) + { mp_raise_ValueError(MP_ERROR_TEXT("dict expected for named parameters")); return -1; } - mp_map_t *map = mp_obj_dict_get_map(values); + mp_map_t* map = mp_obj_dict_get_map(values); for (size_t i = 1; i <= nParams; i++) { name = sqlite3_bind_parameter_name(stmt, i); - if (!name) { + if (!name) + { mp_raise_ValueError(MP_ERROR_TEXT("Unexpected named parameter")); return -1; } name++; mp_obj_t namestr = mp_obj_new_str(name, strlen(name)); - mp_map_elem_t *elem = mp_map_lookup(map, namestr, MP_MAP_LOOKUP); + mp_map_elem_t* elem = mp_map_lookup(map, namestr, MP_MAP_LOOKUP); - if (!elem) { + if (!elem) + { mp_raise_msg_varg(&usqlite_Error, MP_ERROR_TEXT("Missing value for parameter '%s'"), --name); return -1; } int rc = bindParameter(stmt, i, elem->value); - if (rc) { + if (rc) + { return rc; } } - } else { + } + else + { bool namedIndex = name && *name == '?'; size_t len = 0; - mp_obj_t *items = NULL; + mp_obj_t* items = NULL; - if (mp_obj_is_type(values, &mp_type_tuple)) { + if (mp_obj_is_type(values, &mp_type_tuple)) + { mp_obj_tuple_get(values, &len, &items); - } else if (mp_obj_is_type(values, &mp_type_list)) { + } + else if (mp_obj_is_type(values, &mp_type_list)) + { mp_obj_list_get(values, &len, &items); - } else if (nParams == 1 && !namedIndex) { + } + else if (nParams == 1 && !namedIndex) + { return bindParameter(stmt, 1, values); - } else { + } + else + { mp_raise_msg_varg(&usqlite_Error, MP_ERROR_TEXT("tuple or list expected for > 1 nameless parameters, got a '%s'"), mp_obj_get_type_str(values)); return -1; } - if (!len) { + if (!len) + { mp_raise_ValueError(MP_ERROR_TEXT("Empty values set")); return -1; } @@ -249,22 +295,26 @@ static int bindParameters(sqlite3_stmt *stmt, mp_obj_t values) { for (size_t i = 0; i < nParams; i++) { size_t index = i; - if (namedIndex) { + if (namedIndex) + { name = sqlite3_bind_parameter_name(stmt, i + 1); - if (!name) { + if (!name) + { continue; } index = (size_t)atoi(name + 1) - 1; } - if (index >= len) { + if (index >= len) + { mp_raise_msg_varg(&usqlite_Error, MP_ERROR_TEXT("Parameter index %d > %d values"), ++index, len); return -1; } int rc = bindParameter(stmt, i + 1, items[index]); - if (rc) { + if (rc) + { return rc; } } @@ -275,20 +325,35 @@ static int bindParameters(sqlite3_stmt *stmt, mp_obj_t values) { // ------------------------------------------------------------------------------ -static mp_obj_t usqlite_cursor_execute(size_t n_args, const mp_obj_t *args) { +static mp_obj_t usqlite_cursor_execute(size_t n_args, const mp_obj_t *args) +{ mp_obj_t self_in = args[0]; usqlite_cursor_t *self = MP_OBJ_TO_PTR(self_in); + + // SAFETY CHECK: Prevent using a closed cursor + if (!self->connection) { + mp_raise_ValueError(MP_ERROR_TEXT("Cursor is closed")); + return mp_const_none; + } + const char *sql = mp_obj_str_get_str(args[1]); - usqlite_cursor_close(self_in); + // Note: We DO NOT call close(self_in) here anymore because it would detach the connection! + // Instead, we just finalize the *statement* to prepare for the new one. + if (self->stmt) { + sqlite3_finalize(self->stmt); + self->stmt = NULL; + } - if (!sql || !*sql) { + if (!sql || !*sql) + { mp_raise_msg(&usqlite_Error, MP_ERROR_TEXT("Empty sql")); return mp_const_none; } int rc = sqlite3_prepare_v2(self->connection->db, sql, strlen(sql), &self->stmt, NULL); - if (rc) { + if (rc) + { mp_raise_msg_varg(&usqlite_Error, MP_ERROR_TEXT("error (%d) %s preparing '%s'"), rc, @@ -300,20 +365,27 @@ static mp_obj_t usqlite_cursor_execute(size_t n_args, const mp_obj_t *args) { } int nParams = sqlite3_bind_parameter_count(self->stmt); - if (nParams > 0) { - if (n_args >= 3) { + if (nParams > 0) + { + if (n_args >= 3) + { rc = bindParameters(self->stmt, args[2]); - if (rc > 0) { + if (rc > 0) + { mp_raise_msg_varg(&usqlite_Error, MP_ERROR_TEXT("%s error binding '%s'"), sqlite3_errstr(rc), sql); return mp_const_none; - } else if (rc < 0) { + } + else if (rc < 0) + { return mp_const_none; } - } else { + } + else + { mp_raise_ValueError(MP_ERROR_TEXT("Values required")); return mp_const_none; } @@ -325,16 +397,16 @@ static mp_obj_t usqlite_cursor_execute(size_t n_args, const mp_obj_t *args) { switch (self->rc) { - case SQLITE_ROW: - break; + case SQLITE_ROW: + break; - case SQLITE_DONE: - self->rowcount = sqlite3_changes(self->connection->db); - break; + case SQLITE_DONE: + self->rowcount = sqlite3_changes(self->connection->db); + break; - default: - self->rowcount = -1; - break; + default: + self->rowcount = -1; + break; } return self_in; @@ -344,21 +416,24 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(usqlite_cursor_execute_obj, 2, 3, usq // ------------------------------------------------------------------------------ -static mp_obj_t usqlite_cursor_executemany(mp_obj_t self_in, mp_obj_t sql_in) { - usqlite_cursor_t *self = MP_OBJ_TO_PTR(self_in); - const char *sql = mp_obj_str_get_str(sql_in); +static mp_obj_t usqlite_cursor_executemany(mp_obj_t self_in, mp_obj_t sql_in) +{ + usqlite_cursor_t* self = MP_OBJ_TO_PTR(self_in); + const char* sql = mp_obj_str_get_str(sql_in); usqlite_cursor_close(self_in); - if (!sql || !*sql) { + if (!sql || !*sql) + { mp_raise_msg(&usqlite_Error, MP_ERROR_TEXT("Empty sql")); return mp_const_none; } - char *errmsg = NULL; + char* errmsg = NULL; int rc = sqlite3_exec(self->connection->db, sql, NULL, NULL, &errmsg); - if (rc) { + if (rc) + { mp_raise_msg_varg(&usqlite_Error, MP_ERROR_TEXT("%s"), errmsg ? errmsg : ""); } @@ -371,11 +446,13 @@ static MP_DEFINE_CONST_FUN_OBJ_2(usqlite_cursor_executemany_obj, usqlite_cursor_ // ------------------------------------------------------------------------------ -static mp_obj_t usqlite_cursor_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) { - usqlite_cursor_t *self = MP_OBJ_TO_PTR(self_in); +static mp_obj_t usqlite_cursor_getiter(mp_obj_t self_in, mp_obj_iter_buf_t* iter_buf) +{ + usqlite_cursor_t* self = MP_OBJ_TO_PTR(self_in); (void)iter_buf; - if (!self->stmt) { + if (!self->stmt) + { mp_raise_msg(&usqlite_Error, MP_ERROR_TEXT("No iter data")); return mp_const_none; } @@ -385,7 +462,8 @@ static mp_obj_t usqlite_cursor_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter // ------------------------------------------------------------------------------ -static mp_obj_t row_dict(usqlite_cursor_t *cursor) { +static mp_obj_t row_dict(usqlite_cursor_t* cursor) +{ int columns = sqlite3_data_count(cursor->stmt); mp_obj_t dict = mp_obj_new_dict(columns); @@ -400,10 +478,11 @@ static mp_obj_t row_dict(usqlite_cursor_t *cursor) { // ------------------------------------------------------------------------------ -static mp_obj_t row_tuple(usqlite_cursor_t *cursor) { +static mp_obj_t row_tuple(usqlite_cursor_t* cursor) +{ int columns = sqlite3_data_count(cursor->stmt); - mp_obj_tuple_t *o = MP_OBJ_TO_PTR(mp_obj_new_tuple(columns, NULL)); + mp_obj_tuple_t* o = MP_OBJ_TO_PTR(mp_obj_new_tuple(columns, NULL)); for (int i = 0; i < columns; i++) { @@ -415,10 +494,11 @@ static mp_obj_t row_tuple(usqlite_cursor_t *cursor) { // ------------------------------------------------------------------------------ -static mp_obj_t row_type(usqlite_cursor_t *cursor) { +static mp_obj_t row_type(usqlite_cursor_t* cursor) +{ int columns = sqlite3_data_count(cursor->stmt); - mp_obj_tuple_t *o = MP_OBJ_TO_PTR(mp_obj_new_tuple(columns + 1, NULL)); + mp_obj_tuple_t* o = MP_OBJ_TO_PTR(mp_obj_new_tuple(columns + 1, NULL)); o->items[columns] = MP_OBJ_FROM_PTR(cursor); @@ -432,30 +512,30 @@ static mp_obj_t row_type(usqlite_cursor_t *cursor) { // ------------------------------------------------------------------------------ -static mp_obj_t usqlite_cursor_iternext(mp_obj_t self_in) { - usqlite_cursor_t *self = MP_OBJ_TO_PTR(self_in); +static mp_obj_t usqlite_cursor_iternext(mp_obj_t self_in) +{ + usqlite_cursor_t* self = MP_OBJ_TO_PTR(self_in); mp_obj_t result = self->rc == SQLITE_ROW ? self->rowfactory(self) : MP_OBJ_STOP_ITERATION; - switch (self->rc) { - case SQLITE_OK: - break; + case SQLITE_OK: + break; - case SQLITE_ROW: - stepExecute(self); - break; + case SQLITE_ROW: + stepExecute(self); + break; - case SQLITE_DONE: - // self->rc = sqlite3_reset(self->stmt); - break; + case SQLITE_DONE: + // self->rc = sqlite3_reset(self->stmt); + break; - case SQLITE_ERROR: - default: - mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("sqlite3 error %d executing '%s'"), self->rc, sqlite3_sql(self->stmt)); - break; + case SQLITE_ERROR: + default: + mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("sqlite3 error %d executing '%s'"), self->rc, sqlite3_sql(self->stmt)); + break; } return result; @@ -463,15 +543,17 @@ static mp_obj_t usqlite_cursor_iternext(mp_obj_t self_in) { // ------------------------------------------------------------------------------ -static mp_obj_t usqlite_cursor_fetchone(mp_obj_t self_in) { +static mp_obj_t usqlite_cursor_fetchone(mp_obj_t self_in) +{ usqlite_cursor_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_t result = self->rc == SQLITE_ROW - ? self->rowfactory(self) - : mp_const_none; + ? self->rowfactory(self) + : mp_const_none; - if (self->rc == SQLITE_ROW) { - stepExecute(self_in); + if (self->rc == SQLITE_ROW) + { + stepExecute(self); } return result; @@ -481,16 +563,18 @@ static MP_DEFINE_CONST_FUN_OBJ_1(usqlite_cursor_fetchone_obj, usqlite_cursor_fet // ------------------------------------------------------------------------------ -static mp_obj_t usqlite_cursor_fetchmany(size_t n_args, const mp_obj_t *args) { - usqlite_cursor_t *self = MP_OBJ_TO_PTR(args[0]); +static mp_obj_t usqlite_cursor_fetchmany(size_t n_args, const mp_obj_t* args) +{ + usqlite_cursor_t* self = MP_OBJ_TO_PTR(args[0]); - if (self->rc != SQLITE_ROW) { + if (self->rc != SQLITE_ROW) + { return mp_obj_new_list(0, NULL); } mp_obj_t row = self->rowfactory(self); mp_obj_t list = mp_obj_new_list(1, &row); - mp_obj_list_t *listt = MP_OBJ_TO_PTR(list); + mp_obj_list_t* listt = MP_OBJ_TO_PTR(list); int size = n_args == 2 ? mp_obj_get_int(args[1]) @@ -498,18 +582,21 @@ static mp_obj_t usqlite_cursor_fetchmany(size_t n_args, const mp_obj_t *args) { stepExecute(args[0]); - if (!size) { + if (!size) + { size = 1; } - if (size == 1) { + if (size == 1) + { return list; } - while (self->rc == SQLITE_ROW && (size < 0 || (int)listt->len < size)) { + while (self->rc == SQLITE_ROW && (size < 0 || (int)listt->len < size)) + { row = self->rowfactory(self); mp_obj_list_append(list, row); - stepExecute(args[0]); + stepExecute(self); } return list; @@ -519,12 +606,12 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(usqlite_cursor_fetchmany_obj, 1, 2, u // ------------------------------------------------------------------------------ -static mp_obj_t usqlite_cursor_fetchall(mp_obj_t self_in) { +static mp_obj_t usqlite_cursor_fetchall(mp_obj_t self_in) +{ mp_obj_t args[] = { self_in, - mp_obj_new_int(-1) - }; + mp_obj_new_int(-1) }; return usqlite_cursor_fetchmany(MP_ARRAY_SIZE(args), args); } @@ -533,21 +620,22 @@ static MP_DEFINE_CONST_FUN_OBJ_1(usqlite_cursor_fetchall_obj, usqlite_cursor_fet // ------------------------------------------------------------------------------ -static mp_obj_t usqlite_cursor_description(sqlite3_stmt *stmt) { +static mp_obj_t usqlite_cursor_description(sqlite3_stmt* stmt) +{ int columns = sqlite3_data_count(stmt); - mp_obj_tuple_t *o = MP_OBJ_TO_PTR(mp_obj_new_tuple(columns, NULL)); + mp_obj_tuple_t* o = MP_OBJ_TO_PTR(mp_obj_new_tuple(columns, NULL)); for (int i = 0; i < columns; i++) { - mp_obj_tuple_t *c = MP_OBJ_TO_PTR(mp_obj_new_tuple(7, NULL)); + mp_obj_tuple_t* c = MP_OBJ_TO_PTR(mp_obj_new_tuple(7, NULL)); c->items[0] = usqlite_column_name(stmt, i); - #ifndef SQLITE_OMIT_DECLTYPE +#ifndef SQLITE_OMIT_DECLTYPE c->items[1] = usqlite_column_decltype(stmt, i); - #else +#else c->items[1] = mp_const_none; - #endif +#endif for (int j = 2; j < 7; j++) { c->items[j] = mp_const_none; @@ -561,77 +649,69 @@ static mp_obj_t usqlite_cursor_description(sqlite3_stmt *stmt) { // ------------------------------------------------------------------------------ -static void usqlite_cursor_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { - usqlite_cursor_t *self = MP_OBJ_TO_PTR(self_in); +static void usqlite_cursor_attr(mp_obj_t self_in, qstr attr, mp_obj_t* dest) +{ + usqlite_cursor_t* self = MP_OBJ_TO_PTR(self_in); - if (dest[0] == MP_OBJ_NULL) { - if ((usqlite_lookup(self_in, attr, dest))) { + if (dest[0] == MP_OBJ_NULL) + { + if ((usqlite_lookup(self_in, attr, dest))) + { return; } - const char* strConnection = "connection"; - mp_obj_t objConnection = mp_obj_new_str(strConnection, strlen(strConnection)); - qstr qstrConnection = mp_obj_str_get_qstr(objConnection); - - if (attr == qstrConnection) { + switch (attr) + { + case MP_QSTR_connection: dest[0] = MP_OBJ_FROM_PTR(self->connection); - } - else { - - switch (attr) - { - case MP_QSTR_connection: - dest[0] = MP_OBJ_FROM_PTR(self->connection); - break; + break; - case MP_QSTR_description: - dest[0] = usqlite_cursor_description(self->stmt); - break; + case MP_QSTR_description: + dest[0] = usqlite_cursor_description(self->stmt); + break; - case MP_QSTR_lastrowid: { - sqlite3_int64 rowid = sqlite3_last_insert_rowid(self->connection->db); - dest[0] = rowid ? mp_obj_new_int_from_ll(rowid) : mp_const_none; - } - break; + case MP_QSTR_lastrowid: + { + sqlite3_int64 rowid = sqlite3_last_insert_rowid(self->connection->db); + dest[0] = rowid ? mp_obj_new_int_from_ll(rowid) : mp_const_none; + } + break; - case MP_QSTR_rowcount: - dest[0] = mp_obj_new_int(self->rowcount); - break; + case MP_QSTR_rowcount: + dest[0] = mp_obj_new_int(self->rowcount); + break; - case MP_QSTR_arraysize: - dest[0] = mp_obj_new_int(self->arraysize); - break; - } + case MP_QSTR_arraysize: + dest[0] = mp_obj_new_int(self->arraysize); + break; } - } else if (dest[1] != MP_OBJ_NULL) { + } + else if (dest[1] != MP_OBJ_NULL) + { switch (attr) { - case MP_QSTR_arraysize: - self->arraysize = mp_obj_get_int(dest[1]); - dest[0] = MP_OBJ_NULL; - break; + case MP_QSTR_arraysize: + self->arraysize = mp_obj_get_int(dest[1]); + dest[0] = MP_OBJ_NULL; + break; } } } - // ------------------------------------------------------------------------------ -static mp_obj_t usqlite_cursor_del(mp_obj_t self_in) { - usqlite_cursor_t *self = MP_OBJ_TO_PTR(self_in); - +static mp_obj_t usqlite_cursor_del(mp_obj_t self_in) +{ usqlite_logprintf(___FUNC___ "\n"); - - usqlite_cursor_close(self_in); - usqlite_connection_deregister(self->connection, self_in); - - return mp_const_none; + // Just call close(), which now handles the deregistration logic safely + return usqlite_cursor_close(self_in); } MP_DEFINE_CONST_FUN_OBJ_1(usqlite_cursor_del_obj, usqlite_cursor_del); // ------------------------------------------------------------------------------ -static mp_obj_t usqlite_cursor_exit(size_t n_args, const mp_obj_t *args) { +static mp_obj_t usqlite_cursor_exit(size_t n_args, const mp_obj_t* args) +{ usqlite_logprintf(___FUNC___ "\n"); usqlite_cursor_close(args[0]); @@ -645,16 +725,16 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(usqlite_cursor_exit_obj, 4, 4, usqlit static const mp_rom_map_elem_t usqlite_cursor_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&usqlite_cursor_del_obj) }, - { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj) }, - { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&usqlite_cursor_exit_obj) }, - - { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&usqlite_cursor_close_obj) }, - { MP_ROM_QSTR(MP_QSTR_execute), MP_ROM_PTR(&usqlite_cursor_execute_obj) }, - { MP_ROM_QSTR(MP_QSTR_executemany), MP_ROM_PTR(&usqlite_cursor_executemany_obj) }, - { MP_ROM_QSTR(MP_QSTR_fetchone), MP_ROM_PTR(&usqlite_cursor_fetchone_obj) }, - { MP_ROM_QSTR(MP_QSTR_fetchmany), MP_ROM_PTR(&usqlite_cursor_fetchmany_obj) }, - { MP_ROM_QSTR(MP_QSTR_fetchall), MP_ROM_PTR(&usqlite_cursor_fetchall_obj) }, + {MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&usqlite_cursor_del_obj)}, + {MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj)}, + {MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&usqlite_cursor_exit_obj)}, + + {MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&usqlite_cursor_close_obj)}, + {MP_ROM_QSTR(MP_QSTR_execute), MP_ROM_PTR(&usqlite_cursor_execute_obj)}, + {MP_ROM_QSTR(MP_QSTR_executemany), MP_ROM_PTR(&usqlite_cursor_executemany_obj)}, + {MP_ROM_QSTR(MP_QSTR_fetchone), MP_ROM_PTR(&usqlite_cursor_fetchone_obj)}, + {MP_ROM_QSTR(MP_QSTR_fetchmany), MP_ROM_PTR(&usqlite_cursor_fetchmany_obj)}, + {MP_ROM_QSTR(MP_QSTR_fetchall), MP_ROM_PTR(&usqlite_cursor_fetchall_obj)}, }; MP_DEFINE_CONST_DICT(usqlite_cursor_locals_dict, usqlite_cursor_locals_dict_table); @@ -675,20 +755,18 @@ MP_DEFINE_CONST_OBJ_TYPE( print, usqlite_cursor_print, attr, usqlite_cursor_attr, iter, &usqlite_getiter_iternext, - locals_dict, &usqlite_cursor_locals_dict - ); + locals_dict, &usqlite_cursor_locals_dict); #else const mp_obj_type_t usqlite_cursor_type = { - { &mp_type_type }, + {&mp_type_type}, .name = MP_QSTR_Cursor, .print = usqlite_cursor_print, .make_new = usqlite_cursor_make_new, .getiter = usqlite_cursor_getiter, .iternext = usqlite_cursor_iternext, - .locals_dict = (mp_obj_dict_t *)&usqlite_cursor_locals_dict, - .attr = &usqlite_cursor_attr -}; + .locals_dict = (mp_obj_dict_t*)&usqlite_cursor_locals_dict, + .attr = &usqlite_cursor_attr }; #endif // ------------------------------------------------------------------------------ diff --git a/usqlite_file.c b/usqlite_file.c index 9912a1e..d8cc67a 100644 --- a/usqlite_file.c +++ b/usqlite_file.c @@ -34,24 +34,33 @@ extern const mp_obj_module_t mp_module_io; // ------------------------------------------------------------------------------ -bool usqlite_file_exists(const char *pathname) { +bool usqlite_file_exists(const char* pathname) { mp_obj_t os = mp_module_get_builtin(MP_QSTR_uos, 0); mp_obj_t ilistdir = usqlite_method(os, MP_QSTR_ilistdir); char path[MAXPATHNAME + 1]; + size_t pathname_len = strlen(pathname); + + // Check if pathname fits in buffer + if (pathname_len >= MAXPATHNAME + 1) { + return false; // Path too long + } + strcpy(path, pathname); - const char *filename = pathname; + const char* filename = pathname; - char *lastSep = strrchr(path, '/'); + char* lastSep = strrchr(path, '/'); if (lastSep) { *lastSep++ = 0; filename = lastSep; - } else { + } + else { lastSep = strrchr(path, '\\'); if (lastSep) { *lastSep++ = 0; filename = lastSep; - } else { + } + else { path[0] = '.'; path[1] = 0; } @@ -62,11 +71,11 @@ bool usqlite_file_exists(const char *pathname) { mp_obj_t entry = mp_iternext(listdir); while (entry != MP_OBJ_STOP_ITERATION) { - mp_obj_tuple_t *t = MP_OBJ_TO_PTR(entry); + mp_obj_tuple_t* t = MP_OBJ_TO_PTR(entry); int type = mp_obj_get_int(t->items[1]); if (type == 0x8000) { - const char *name = mp_obj_str_get_str(t->items[0]); + const char* name = mp_obj_str_get_str(t->items[0]); if ((exists = strcmp(filename, name) == 0)) { break; } @@ -81,27 +90,33 @@ bool usqlite_file_exists(const char *pathname) { // ------------------------------------------------------------------------------ -int usqlite_file_open(MPFILE *file, const char *pathname, int flags) { +int usqlite_file_open(MPFILE* file, const char* pathname, int flags) { LOGFUNC; mp_obj_t filename = mp_obj_new_str(pathname, strlen(pathname)); char mode[8]; memset(mode, 0, sizeof(mode)); - char *pMode = mode; + char* pMode = mode; if (flags & SQLITE_OPEN_CREATE) { if (!usqlite_file_exists(pathname)) { *pMode++ = 'w'; } - + else { + *pMode++ = 'r'; // Open existing file for read/write + } *pMode++ = '+'; - } else if (flags & SQLITE_OPEN_READWRITE) { + + } + else if (flags & SQLITE_OPEN_READWRITE) { *pMode++ = 'r'; *pMode++ = '+'; - } else if (flags & SQLITE_OPEN_READONLY) { + } + else if (flags & SQLITE_OPEN_READONLY) { *pMode++ = 'r'; - } else { + } + else { *pMode++ = 'r'; } @@ -113,7 +128,12 @@ int usqlite_file_open(MPFILE *file, const char *pathname, int flags) { mp_obj_t open = usqlite_method(&mp_module_io, MP_QSTR_open); file->stream = mp_call_function_2(open, filename, filemode); - strcpy(file->pathname, pathname); + + //Copy at most MAXPATHNAME - 1 characters + strncpy(file->pathname, pathname, MAXPATHNAME - 1); + // Ensure the very last byte is always null, preventing overrun reads + file->pathname[MAXPATHNAME - 1] = '\0'; + file->flags = flags; // const mp_stream_p_t* stream = mp_get_stream(file->stream); @@ -133,7 +153,7 @@ file->stream = mp_builtin_open(2, args, NULL); // ------------------------------------------------------------------------------ -int usqlite_file_close(MPFILE *file) { +int usqlite_file_close(MPFILE* file) { LOGFUNC; if (file->stream) { @@ -152,7 +172,7 @@ int usqlite_file_close(MPFILE *file) { // ------------------------------------------------------------------------------ -int usqlite_file_read(MPFILE *file, void *pBuf, size_t nBuf) { +int usqlite_file_read(MPFILE* file, void* pBuf, size_t nBuf) { LOGFUNC; int error = 0; @@ -166,12 +186,12 @@ int usqlite_file_read(MPFILE *file, void *pBuf, size_t nBuf) { // ------------------------------------------------------------------------------ -int usqlite_file_write(MPFILE *file, const void *pBuf, size_t nBuf) { +int usqlite_file_write(MPFILE* file, const void* pBuf, size_t nBuf) { LOGFUNC; int error = 0; - mp_uint_t size = mp_stream_rw(file->stream, (void *)pBuf, nBuf, &error, MP_STREAM_RW_WRITE); + mp_uint_t size = mp_stream_rw(file->stream, (void*)pBuf, nBuf, &error, MP_STREAM_RW_WRITE); if (size != nBuf) { usqlite_errprintf("write error: %d", error); } @@ -181,10 +201,10 @@ int usqlite_file_write(MPFILE *file, const void *pBuf, size_t nBuf) { // ------------------------------------------------------------------------------ -int usqlite_file_flush(MPFILE *file) { +int usqlite_file_flush(MPFILE* file) { LOGFUNC; - const mp_stream_p_t *stream = mp_get_stream(file->stream); + const mp_stream_p_t* stream = mp_get_stream(file->stream); int error = 0; mp_uint_t result = stream->ioctl(file->stream, MP_STREAM_FLUSH, 0, &error); @@ -199,7 +219,7 @@ int usqlite_file_flush(MPFILE *file) { // ------------------------------------------------------------------------------ -int usqlite_file_seek(MPFILE *file, int offset, int origin) { +int usqlite_file_seek(MPFILE* file, int offset, int origin) { LOGFUNC; struct mp_stream_seek_t seek; @@ -207,7 +227,7 @@ int usqlite_file_seek(MPFILE *file, int offset, int origin) { seek.offset = offset; seek.whence = origin; - const mp_stream_p_t *stream = mp_get_stream(file->stream); + const mp_stream_p_t* stream = mp_get_stream(file->stream); int error; mp_uint_t result = stream->ioctl(file->stream, MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek, &error); @@ -221,7 +241,7 @@ int usqlite_file_seek(MPFILE *file, int offset, int origin) { // ------------------------------------------------------------------------------ -int usqlite_file_tell(MPFILE *file) { +int usqlite_file_tell(MPFILE* file) { LOGFUNC; return usqlite_file_seek(file, 0, MP_SEEK_CUR); @@ -229,7 +249,7 @@ int usqlite_file_tell(MPFILE *file) { // ------------------------------------------------------------------------------ -int usqlite_file_delete(const char *pathname) { +int usqlite_file_delete(const char* pathname) { LOGFUNC; usqlite_logprintf("%s: %s\n", __func__, pathname); diff --git a/usqlite_mem.c b/usqlite_mem.c index 85dee94..ea66460 100644 --- a/usqlite_mem.c +++ b/usqlite_mem.c @@ -42,15 +42,15 @@ void usqlite_mem_init(void) { usqlite_logprintf("zero malloc heap: %d\n", MEMSYS5_HEAP_SIZE); - void *heap = m_malloc(MEMSYS5_HEAP_SIZE); + void* heap = m_malloc(MEMSYS5_HEAP_SIZE); if (!heap) { - mp_raise_msg_varg(&usqlite_Error, MP_ERROR_TEXT("Failed to alloc heap: %d"), HEAP_SIZE); + mp_raise_msg_varg(&usqlite_Error, MP_ERROR_TEXT("Failed to alloc heap: %d"), MEMSYS5_HEAP_SIZE); return; } LOGLINE; sqlite_heap = MP_OBJ_FROM_PTR(heap); - sqlite3_config(SQLITE_CONFIG_HEAP, heap, HEAP_SIZE, 0); + sqlite3_config(SQLITE_CONFIG_HEAP, heap, MEMSYS5_HEAP_SIZE, 0); LOGLINE; } #endif @@ -59,39 +59,39 @@ void usqlite_mem_init(void) { #if defined(SQLITE_ZERO_MALLOC) && !defined(SQLITE_ENABLE_MEMSYS5) // ------------------------------------------------------------------------------ -void *mpmemMalloc(int size) { - void *mem = gc_alloc(size, false); +void* mpmemMalloc(int size) { + void* mem = gc_alloc(size, false); - #if MICROPY_MEM_STATS +#if MICROPY_MEM_STATS MP_STATE_MEM(total_bytes_allocated) += size; MP_STATE_MEM(current_bytes_allocated) += size; - #endif +#endif return mem; } -void mpmemFree(void *mem) { - #if MICROPY_MEM_STATS +void mpmemFree(void* mem) { +#if MICROPY_MEM_STATS int size = gc_nbytes(mem); MP_STATE_MEM(total_bytes_allocated) -= size; MP_STATE_MEM(current_bytes_allocated) -= size; - #endif +#endif gc_free(mem); } -void *mpmemRealloc(void *mem, int size) { - #if MICROPY_MEM_STATS +void* mpmemRealloc(void* mem, int size) { +#if MICROPY_MEM_STATS int nsize = size - gc_nbytes(mem); MP_STATE_MEM(total_bytes_allocated) += nsize; MP_STATE_MEM(current_bytes_allocated) += nsize; - #endif +#endif return gc_realloc(mem, size, true); } -int mpmemSize(void *mem) { +int mpmemSize(void* mem) { return gc_nbytes(mem); } @@ -99,13 +99,13 @@ int mpmemRoundup(int size) { return size; } -int mpmemInit(void *appData) { +int mpmemInit(void* appData) { LOGFUNC; return SQLITE_OK; } -void mpmemShutdown(void *appData) { +void mpmemShutdown(void* appData) { LOGFUNC; } diff --git a/usqlite_row.c b/usqlite_row.c index 687e341..9dc3c78 100644 --- a/usqlite_row.c +++ b/usqlite_row.c @@ -27,7 +27,7 @@ SOFTWARE. #include "py/objstr.h" #include "py/objtuple.h" -static void usqlite_row_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest); +static void usqlite_row_attr(mp_obj_t self_in, qstr attr, mp_obj_t* dest); // ------------------------------------------------------------------------------ @@ -38,7 +38,7 @@ void usqlite_row_type_initialize() { return; } - #if defined(MP_DEFINE_CONST_OBJ_TYPE) +#if defined(MP_DEFINE_CONST_OBJ_TYPE) usqlite_row_type.base.type = &mp_type_type; usqlite_row_type.flags = MP_TYPE_FLAG_ITER_IS_GETITER; usqlite_row_type.name = MP_QSTR_Row; @@ -50,20 +50,24 @@ void usqlite_row_type_initialize() { MP_OBJ_TYPE_SET_SLOT(&usqlite_row_type, subscr, mp_obj_tuple_subscr, 5); MP_OBJ_TYPE_SET_SLOT(&usqlite_row_type, iter, mp_obj_tuple_getiter, 6); MP_OBJ_TYPE_SET_SLOT(&usqlite_row_type, locals_dict, MP_OBJ_TYPE_GET_SLOT(&mp_type_tuple, locals_dict), 7); - #else +#else usqlite_row_type.make_new = mp_type_tuple.make_new; usqlite_row_type.locals_dict = mp_type_tuple.locals_dict; - #endif +#endif initialized = 1; } // ------------------------------------------------------------------------------ -static mp_obj_t keys(usqlite_cursor_t *cursor) { +static mp_obj_t keys(usqlite_cursor_t* cursor) { + if (!cursor || !cursor->stmt) { + return mp_const_none; + } + int columns = sqlite3_data_count(cursor->stmt); - mp_obj_tuple_t *o = MP_OBJ_TO_PTR(mp_obj_new_tuple(columns, NULL)); + mp_obj_tuple_t* o = MP_OBJ_TO_PTR(mp_obj_new_tuple(columns, NULL)); for (int i = 0; i < columns; i++) { @@ -75,19 +79,25 @@ static mp_obj_t keys(usqlite_cursor_t *cursor) { // ------------------------------------------------------------------------------ -static void usqlite_row_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { +static void usqlite_row_attr(mp_obj_t self_in, qstr attr, mp_obj_t* dest) { if (dest[0] == MP_OBJ_NULL) { if ((usqlite_lookup(self_in, attr, dest))) { return; } - mp_obj_tuple_t *self = (mp_obj_tuple_t *)self_in; + mp_obj_tuple_t* self = (mp_obj_tuple_t*)self_in; switch (attr) { - case MP_QSTR_keys: - dest[0] = keys(self->items[self->len]); - break; + case MP_QSTR_keys: + // Validate that the tuple has the cursor pointer + if (self->len > 0 && self->items[self->len - 1] != MP_OBJ_NULL) { + usqlite_cursor_t* cursor = MP_OBJ_TO_PTR(self->items[self->len - 1]); + if (cursor && cursor->stmt) { + dest[0] = keys(cursor); + } + } + break; } } }