diff --git a/docs/examples/sqlite3/sqlite3_example.c b/docs/examples/sqlite3/sqlite3_example.c index fc9020431..52897083b 100644 --- a/docs/examples/sqlite3/sqlite3_example.c +++ b/docs/examples/sqlite3/sqlite3_example.c @@ -29,6 +29,9 @@ main( int argc, char **argv ) { return EXIT_FAILURE; } + // TODO we're just ignore errors if this fails, assuming it already existed + stumpless_create_default_sqlite3_table( db_target ); + stumpless_add_message( db_target, "goes to the sqlite3 database" ); stumpless_add_message( db_target, "this one too" ); diff --git a/include/private/error.h b/include/private/error.h index 90f1cd2a1..b4dba4bfe 100644 --- a/include/private/error.h +++ b/include/private/error.h @@ -17,11 +17,11 @@ */ #ifndef __STUMPLESS_PRIVATE_ERROR_H -# define __STUMPLESS_PRIVATE_ERROR_H +#define __STUMPLESS_PRIVATE_ERROR_H -# include -# include -# include "private/config.h" +#include +#include +#include "private/config.h" void clear_error( void ); @@ -235,6 +235,13 @@ raise_socket_send_failure( const char *message, int code, const char *code_type ); +/** + * TODO fill in + */ +COLD_FUNCTION +void +raise_sqlite3_error( const char *message, int code ); + COLD_FUNCTION void raise_stream_write_failure( void ); diff --git a/include/private/target/sqlite3.h b/include/private/target/sqlite3.h index 4821768bf..58cc23e72 100644 --- a/include/private/target/sqlite3.h +++ b/include/private/target/sqlite3.h @@ -35,7 +35,7 @@ struct sqlite3_target { /** A connection to the database this target writes to. */ sqlite3 *db; /** The SQL command used to insert entries into the database. */ - const char *insert_statement; + const char *insert_sql; #ifdef STUMPLESS_THREAD_SAFETY_SUPPORTED /** * Protects db. This mutex must be locked by a thread before it uses the diff --git a/include/stumpless/error.h b/include/stumpless/error.h index 1863cc8d0..ce2ca87c9 100644 --- a/include/stumpless/error.h +++ b/include/stumpless/error.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: Apache-2.0 */ /* - * Copyright 2018-2022 Joel E. Anderson + * Copyright 2018-2023 Joel E. Anderson * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ */ #ifndef __STUMPLESS_ERROR_H -# define __STUMPLESS_ERROR_H +#define __STUMPLESS_ERROR_H #include #include @@ -132,7 +132,13 @@ extern "C" { * * @since release v2.2.0 */\ - ERROR( STUMPLESS_INVALID_PARAM_STRING, 29 ) + ERROR( STUMPLESS_INVALID_PARAM_STRING, 29 ) \ +/** + * Sqlite3 encountered a failure. + * + * @since release v2.2.0 + */\ + ERROR( STUMPLESS_SQLITE3_FAILURE, 30 ) /** * An (enum) identifier of the types of errors that might be encountered. diff --git a/include/stumpless/target/sqlite3.h b/include/stumpless/target/sqlite3.h index c0a76cf24..15b0e4f97 100644 --- a/include/stumpless/target/sqlite3.h +++ b/include/stumpless/target/sqlite3.h @@ -34,14 +34,14 @@ */ #ifndef __STUMPLESS_TARGET_SQLITE3_H -# define __STUMPLESS_TARGET_SQLITE3_H +#define __STUMPLESS_TARGET_SQLITE3_H -# include -# include +#include +#include -# ifdef __cplusplus +#ifdef __cplusplus extern "C" { -# endif +#endif /** * TODO update @@ -70,6 +70,13 @@ STUMPLESS_PUBLIC_FUNCTION void stumpless_close_sqlite3_target( struct stumpless_target *target ); +/** + * TODO update + */ +STUMPLESS_PUBLIC_FUNCTION +struct stumpless_target * +stumpless_create_default_sqlite3_table( struct stumpless_target *target ); + /** * TODO update * Opens a file target. @@ -101,7 +108,8 @@ STUMPLESS_PUBLIC_FUNCTION struct stumpless_target * stumpless_open_sqlite3_target( const char *name ); -# ifdef __cplusplus +#ifdef __cplusplus } /* extern "C" */ -# endif +#endif + #endif /* __STUMPLESS_TARGET_SQLITE3_H */ diff --git a/src/error.c b/src/error.c index 71e7e68e4..5c310d410 100644 --- a/src/error.c +++ b/src/error.c @@ -326,6 +326,11 @@ raise_socket_send_failure( const char *message, raise_error( STUMPLESS_SOCKET_SEND_FAILURE, message, code, code_type ); } +void +raise_sqlite3_error( const char *message, int code ) { + raise_error( STUMPLESS_SQLITE3_FAILURE, message, code, "the sqlite3 result code" ); // TODO l10n +} + void raise_stream_write_failure( void ) { raise_error( STUMPLESS_STREAM_WRITE_FAILURE, diff --git a/src/target.c b/src/target.c index 92e6e83a4..492117fa9 100644 --- a/src/target.c +++ b/src/target.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 /* - * Copyright 2018-2022 Joel E. Anderson + * Copyright 2018-2023 Joel E. Anderson * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -408,6 +408,10 @@ stumpless_close_target( struct stumpless_target *target ) { config_close_socket_target( target ); break; + case STUMPLESS_SQLITE3_TARGET: + config_close_sqlite3_target( target ); + break; + case STUMPLESS_STREAM_TARGET: stumpless_close_stream_target( target ); break; diff --git a/src/target/sqlite3.c b/src/target/sqlite3.c index 86a661cb5..42c6e4bd3 100644 --- a/src/target/sqlite3.c +++ b/src/target/sqlite3.c @@ -17,44 +17,54 @@ */ #include +#include #include -#include #include +#include #include #include #include "private/config/locale/wrapper.h" #include "private/config/wrapper/thread_safety.h" #include "private/error.h" +#include "private/inthelper.h" #include "private/memory.h" #include "private/target.h" #include "private/target/sqlite3.h" #include "private/validate.h" -static sqlite3_stmt *default_statement = NULL; -static const char *default_insert = "INSERT INTO logs (message) VALUES (?)"; +static sqlite3_stmt *default_statement = NULL; // TODO needs to be per-target (for multiple target support) +static sqlite3_stmt *default_statements[1]; +static const char *default_insert_sql = "INSERT INTO logs (prival, version, timestamp, hostname, app_name, procid, msgid, structured_data, message) VALUES (3, 3, NULL, NULL, NULL, NULL, NULL, NULL, ?);"; // generic pointer here to prevent mandatory public API reliance on sqlite3.h +// this is called while the db mutex is held, so thread safety is not a concern static void * prepare_statments( const struct stumpless_entry *entry, void *data, size_t *count ) { struct sqlite3_target *target; - int bind_result; - - sqlite3_reset( default_statement ); - + int sql_result; + target = data; - // TODO not thread safe! - bind_result = sqlite3_bind_text( default_statement, 1, entry->message, entry->message_length, SQLITE_STATIC ); - printf( "bind result: %d\n", bind_result ); + if( !default_statement) { + + sql_result = sqlite3_prepare_v2( target->db, target->insert_sql, -1, &default_statement, NULL ); + if( sql_result != SQLITE_OK ) { + raise_sqlite3_error( "could not prepare the insert statement", sql_result ); // TODO l10n + return NULL; + } + } else { + sqlite3_reset( default_statement ); + } - *count = 1; - return &default_statement; -} + sql_result = sqlite3_bind_text( default_statement, 1, entry->message, entry->message_length, SQLITE_STATIC ); + if( sql_result != SQLITE_OK ) { + raise_sqlite3_error( "could not bind the message to the statement", sql_result ); // TODO l10n + return NULL; + } -static -void -after_steps( void *data ) { - + *count = 1; + default_statements[0] = default_statement; + return default_statements; } void @@ -71,6 +81,60 @@ stumpless_close_sqlite3_target( struct stumpless_target *target ) { clear_error( ); } +struct stumpless_target * +stumpless_create_default_sqlite3_table( struct stumpless_target *target ) { + struct sqlite3_target *db_target; + const char *create_sql = "CREATE TABLE logs (log_id INTEGER PRIMARY KEY, prival INTEGER NOT NULL, version INTEGER NOT NULL, timestamp TEXT, hostname TEXT, app_name TEXT, procid TEXT, msgid TEXT, structured_data TEXT, message TEXT);"; + struct sqlite3_stmt *create_statement; + int sql_result; + size_t try_count = 0; + struct stumpless_target *return_result; + + VALIDATE_ARG_NOT_NULL( target ); + + db_target = target->id; + return_result = target; + clear_error(); + + config_lock_mutex( &db_target->db_mutex ); + + // TODO assuming that the target is open already + sql_result = sqlite3_prepare_v2( db_target->db, create_sql, -1, &create_statement, NULL ); + if( sql_result != SQLITE_OK ) { + raise_sqlite3_error( "sqlite3_prepare_v2 failed on the table creation statement", sql_result ); // TODO l10n + return_result = NULL; + goto cleanup_and_finish; + } + + while( true ) { + try_count++; + sql_result = sqlite3_step( create_statement ); + + if( sql_result == SQLITE_BUSY ) { + if( try_count > 3 ) { // TODO arbitrary retry count ceiling + // TODO make code more specific, STUMPLESS_SQLITE3_BUSY + raise_error(STUMPLESS_SQLITE3_FAILURE, "the database was busy and could not complete the transaction", cap_size_t_to_int(try_count), "the number of attempts made" ); + return_result = NULL; + goto cleanup_and_finish; + } + } + + if( sql_result == SQLITE_DONE ) { + goto cleanup_and_finish; + } + + raise_sqlite3_error( "sqlite3_step failed on the table creation statement", sql_result ); // TODO l10n + return_result = NULL; + goto cleanup_and_finish; + } + + sqlite3_finalize( create_statement ); // todo capture errors + +cleanup_and_finish: + config_unlock_mutex( &db_target->db_mutex ); + return return_result; +} + struct stumpless_target * stumpless_open_sqlite3_target( const char *db_filename ) { struct stumpless_target *target; @@ -102,6 +166,7 @@ stumpless_open_sqlite3_target( const char *db_filename ) { void destroy_sqlite3_target( struct sqlite3_target *target ) { sqlite3_finalize( default_statement ); + default_statement = NULL; // TODO can remove later // we use v2 here to allow a close to not be blocked by pending transactions sqlite3_close_v2( target->db ); config_destroy_mutex( &target->db_mutex ); @@ -111,21 +176,20 @@ destroy_sqlite3_target( struct sqlite3_target *target ) { struct sqlite3_target * new_sqlite3_target( const char *db_filename ) { struct sqlite3_target *target; - int open_result; + int sql_result; target = alloc_mem( sizeof( *target ) ); if( !target ) { goto fail; } - open_result = sqlite3_open( db_filename, &target->db ); - if( open_result != SQLITE_OK ) { - // TODO raise appropriate error + sql_result = sqlite3_open( db_filename, &target->db ); + if( sql_result != SQLITE_OK ) { + raise_sqlite3_error( "could not open the provided database name", sql_result ); // TODO l10n goto fail_db; } - target->insert_statement = default_insert; - sqlite3_prepare_v2( target->db, target->insert_statement, -1, &default_statement, NULL ); + target->insert_sql = default_insert_sql; config_init_mutex( &target->db_mutex ); @@ -144,18 +208,32 @@ send_entry_to_sqlite3_target( const struct stumpless_target *target, size_t stmt_count; size_t i; sqlite3_stmt **statements; + int result = 1; + int sql_result; db_target = target->id; config_lock_mutex( &db_target->db_mutex ); statements = prepare_statments( entry, db_target, &stmt_count ); + if( !statements ) { + result = -1; + // TODO create an error for sqlite3 callback failure + // TODO only do this if we didn't use our built-in prepare + raise_sqlite3_error ( "the prepare statements callback failed", 0 ); // TODO make more specific + goto cleanup_and_finish; + } + for( i = 0; i < stmt_count; i++ ) { - sqlite3_step( statements[i] ); + sql_result = sqlite3_step( statements[i] ); + if( sql_result != SQLITE_DONE ) { + result = -1; + raise_sqlite3_error( "sqlite3_step failed", sql_result ); // TODO l10n + goto cleanup_and_finish; + } } - printf( "wrote to a sqlite3 target with %zd statements!\n", stmt_count ); +cleanup_and_finish: config_unlock_mutex( &db_target->db_mutex ); - - return -1; + return result; } diff --git a/test/function/config/sqlite3_unsupported.cpp b/test/function/config/sqlite3_unsupported.cpp index d7de0984b..65814c4eb 100644 --- a/test/function/config/sqlite3_unsupported.cpp +++ b/test/function/config/sqlite3_unsupported.cpp @@ -17,7 +17,6 @@ */ #include -#include #include #include "test/helper/assert.hpp" #include "test/helper/fixture.hpp" diff --git a/test/function/target/sqlite3.cpp b/test/function/target/sqlite3.cpp index 7a3a6e227..06aed7dd3 100644 --- a/test/function/target/sqlite3.cpp +++ b/test/function/target/sqlite3.cpp @@ -16,20 +16,17 @@ * limitations under the License. */ -#include #include #include -#include #include #include #include "test/helper/assert.hpp" #include "test/helper/fixture.hpp" -#include "test/helper/rfc5424.hpp" namespace { class Sqlite3TargetTest : public::testing::Test { protected: - const char *db_filename = "test_function.sqlite3"; + const char *db_filename = "test_function_fixture.sqlite3"; struct stumpless_target *target; struct stumpless_entry *basic_entry; @@ -38,8 +35,11 @@ namespace { struct stumpless_element *element; struct stumpless_param *param; + remove( db_filename ); target = stumpless_open_sqlite3_target( db_filename ); + stumpless_create_default_sqlite3_table( target ); + stumpless_set_target_default_app_name( target, "sqlite3-target-test" ); stumpless_set_target_default_msgid( target, "default-message" ); @@ -59,13 +59,13 @@ namespace { result = stumpless_add_entry( target, basic_entry ); EXPECT_GE( result, 0 ); - EXPECT_EQ( NULL, stumpless_get_error( ) ); + EXPECT_NO_ERROR; } /* non-fixture tests */ TEST( Sqlite3TargetCloseTest, Generic ) { - const char *db_filename = "generic_close_test.sqlite3"; + const char *db_filename = "test_function_close.sqlite3"; struct stumpless_target *target; remove( db_filename ); @@ -83,6 +83,7 @@ namespace { db_filename ); stumpless_free_all( ); + remove( db_filename ); } TEST( Sqlite3TargetCloseTest, NullTarget ) { @@ -104,17 +105,35 @@ namespace { stumpless_close_stream_target( target ); } + TEST( Sqlite3TargetCreateDefaultTableTest, Normal ) { + const char *db_filename = "test_function_default_table.sqlite3"; + struct stumpless_target *target; + struct stumpless_target *result; + + remove( db_filename ); + target = stumpless_open_sqlite3_target( db_filename ); + ASSERT_NOT_NULL( target ); + EXPECT_NO_ERROR; + + result = stumpless_create_default_sqlite3_table( target ); + EXPECT_EQ( result, target ); + EXPECT_NO_ERROR; + + stumpless_close_sqlite3_target( target ); + remove( db_filename ); + } + TEST( Sqlite3TargetOpenTest, Directory ) { struct stumpless_target *target; const struct stumpless_error *error; - target = stumpless_open_sqlite3_target( "/" ); + target = stumpless_open_sqlite3_target( "./" ); EXPECT_NULL( target ); - EXPECT_ERROR_ID_EQ( STUMPLESS_FILE_OPEN_FAILURE ); // TODO may need to change + EXPECT_ERROR_ID_EQ( STUMPLESS_SQLITE3_FAILURE ); } TEST( Sqlite3TargetOpenTest, MallocFailure ) { - const char *db_filename = "open-malloc-fail.sqlite3"; + const char *db_filename = "malloc_fail_on_open.sqlite3"; struct stumpless_target *target; const struct stumpless_error *error; void *(*set_malloc_result)(size_t); diff --git a/test/thread_safety/target/sqlite3.cpp b/test/thread_safety/target/sqlite3.cpp index 6d8bf88b4..8f0f00c0a 100644 --- a/test/thread_safety/target/sqlite3.cpp +++ b/test/thread_safety/target/sqlite3.cpp @@ -17,13 +17,10 @@ */ #include -#include #include -#include #include #include #include "test/helper/assert.hpp" -#include "test/helper/rfc5424.hpp" #include "test/helper/usage.hpp" namespace { diff --git a/tools/check_headers/sqlite.yml b/tools/check_headers/sqlite.yml index 09c216bd5..e58f08528 100644 --- a/tools/check_headers/sqlite.yml +++ b/tools/check_headers/sqlite.yml @@ -1,8 +1,11 @@ +"SQLITE_BUSY": "sqlite3.h" +"SQLITE_DONE": "sqlite3.h" "SQLITE_OK": "sqlite3.h" "sqlite3": "sqlite3.h" "sqlite3_bind_parameter_index": "sqlite3.h" "sqlite3_bind_text": "sqlite3.h" "sqlite3_close_v2": "sqlite3.h" +"sqlite3_finalize": "sqlite3.h" "sqlite3_open": "sqlite3.h" "sqlite3_reset": "sqlite3.h" "sqlite3_step": "sqlite3.h" diff --git a/tools/check_headers/stumpless.yml b/tools/check_headers/stumpless.yml index e3316c9cb..bafd4ff73 100644 --- a/tools/check_headers/stumpless.yml +++ b/tools/check_headers/stumpless.yml @@ -197,6 +197,7 @@ "stumpless_close_stream_target": "stumpless/target/stream.h" "stumpless_close_target": "stumpless/target.h" "stumpless_close_wel_target": "stumpless/target/wel.h" +"stumpless_create_default_sqlite3_table": "stumpless/target/sqlite3.h" "STUMPLESS_DEFAULT_FACILITY": "stumpless/config.h" "STUMPLESS_DEFAULT_FILE": "stumpless/target.h" "STUMPLESS_DEFAULT_SEVERITY": "stumpless/config.h" @@ -379,6 +380,7 @@ "STUMPLESS_SOCKET_SEND_FAILURE": "stumpless/error.h" "STUMPLESS_SOCKET_TARGETS_SUPPORTED": "stumpless/config.h" "STUMPLESS_SOCKET_TARGET": "stumpless/target.h" +"STUMPLESS_SQLITE3_FAILURE": "stumpless/error.h" "STUMPLESS_SQLITE3_TARGET": "stumpless/target.h" "STUMPLESS_SQLITE3_TARGET_VALUE": "stumpless/target.h" "STUMPLESS_SQLITE3_TARGETS_SUPPORTED": "stumpless/config.h" diff --git a/tools/check_headers/stumpless_private.yml b/tools/check_headers/stumpless_private.yml index b5996ead0..5d34d9554 100644 --- a/tools/check_headers/stumpless_private.yml +++ b/tools/check_headers/stumpless_private.yml @@ -121,6 +121,7 @@ "raise_journald_failure": "private/error.h" "raise_mb_conversion_failure": "private/error.h" "raise_resolve_hostname_failure": "private/error.h" +"raise_sqlite3_error": "private/error.h" "raise_wide_conversion_failure": "private/error.h" "raise_windows_failure": "private/error.h" "repeat_add_entry": "test/helper/usage.hpp"