diff options
Diffstat (limited to 'subversion/libsvn_subr/sqlite.c')
-rw-r--r-- | subversion/libsvn_subr/sqlite.c | 1294 |
1 files changed, 1294 insertions, 0 deletions
diff --git a/subversion/libsvn_subr/sqlite.c b/subversion/libsvn_subr/sqlite.c new file mode 100644 index 0000000..0afceff --- /dev/null +++ b/subversion/libsvn_subr/sqlite.c @@ -0,0 +1,1294 @@ +/* sqlite.c + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <apr_pools.h> + +#include "svn_types.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "svn_io.h" +#include "svn_dirent_uri.h" +#include "svn_checksum.h" + +#include "internal_statements.h" + +#include "private/svn_sqlite.h" +#include "svn_private_config.h" +#include "private/svn_dep_compat.h" +#include "private/svn_atomic.h" +#include "private/svn_skel.h" +#include "private/svn_token.h" + +#ifdef SQLITE3_DEBUG +#include "private/svn_debug.h" +#endif + +#ifdef SVN_SQLITE_INLINE +/* Import the sqlite3 API vtable from sqlite3wrapper.c */ +# define SQLITE_OMIT_DEPRECATED +# include <sqlite3ext.h> +extern const sqlite3_api_routines *const svn_sqlite3__api_funcs; +extern int (*const svn_sqlite3__api_initialize)(void); +extern int (*const svn_sqlite3__api_config)(int, ...); +# define sqlite3_api svn_sqlite3__api_funcs +# define sqlite3_initialize svn_sqlite3__api_initialize +# define sqlite3_config svn_sqlite3__api_config +#else +# include <sqlite3.h> +#endif + +#if !SQLITE_VERSION_AT_LEAST(3,7,12) +#error SQLite is too old -- version 3.7.12 is the minimum required version +#endif + +const char * +svn_sqlite__compiled_version(void) +{ + static const char sqlite_version[] = SQLITE_VERSION; + return sqlite_version; +} + +const char * +svn_sqlite__runtime_version(void) +{ + return sqlite3_libversion(); +} + + +INTERNAL_STATEMENTS_SQL_DECLARE_STATEMENTS(internal_statements); + + +#ifdef SQLITE3_DEBUG +/* An sqlite query execution callback. */ +static void +sqlite_tracer(void *data, const char *sql) +{ + /* sqlite3 *db3 = data; */ + SVN_DBG(("sql=\"%s\"\n", sql)); +} +#endif + +#ifdef SQLITE3_PROFILE +/* An sqlite execution timing callback. */ +static void +sqlite_profiler(void *data, const char *sql, sqlite3_uint64 duration) +{ + /* sqlite3 *db3 = data; */ + SVN_DBG(("[%.3f] sql=\"%s\"\n", 1e-9 * duration, sql)); +} +#endif + +struct svn_sqlite__db_t +{ + sqlite3 *db3; + const char * const *statement_strings; + int nbr_statements; + svn_sqlite__stmt_t **prepared_stmts; + apr_pool_t *state_pool; +}; + +struct svn_sqlite__stmt_t +{ + sqlite3_stmt *s3stmt; + svn_sqlite__db_t *db; + svn_boolean_t needs_reset; +}; + +struct svn_sqlite__context_t +{ + sqlite3_context *context; +}; + +struct svn_sqlite__value_t +{ + sqlite3_value *value; +}; + + +/* Convert SQLite error codes to SVN. Evaluates X multiple times */ +#define SQLITE_ERROR_CODE(x) ((x) == SQLITE_READONLY \ + ? SVN_ERR_SQLITE_READONLY \ + : ((x) == SQLITE_BUSY \ + ? SVN_ERR_SQLITE_BUSY \ + : ((x) == SQLITE_CONSTRAINT \ + ? SVN_ERR_SQLITE_CONSTRAINT \ + : SVN_ERR_SQLITE_ERROR))) + + +/* SQLITE->SVN quick error wrap, much like SVN_ERR. */ +#define SQLITE_ERR(x, db) do \ +{ \ + int sqlite_err__temp = (x); \ + if (sqlite_err__temp != SQLITE_OK) \ + return svn_error_createf(SQLITE_ERROR_CODE(sqlite_err__temp), \ + NULL, "sqlite: %s (S%d)", \ + sqlite3_errmsg((db)->db3), \ + sqlite_err__temp); \ +} while (0) + +#define SQLITE_ERR_MSG(x, msg) do \ +{ \ + int sqlite_err__temp = (x); \ + if (sqlite_err__temp != SQLITE_OK) \ + return svn_error_createf(SQLITE_ERROR_CODE(sqlite_err__temp), \ + NULL, "sqlite: %s (S%d)", (msg), \ + sqlite_err__temp); \ +} while (0) + + +/* Time (in milliseconds) to wait for sqlite locks before giving up. */ +#define BUSY_TIMEOUT 10000 + + +/* Convenience wrapper around exec_sql2(). */ +#define exec_sql(db, sql) exec_sql2((db), (sql), SQLITE_OK) + +/* Run the statement SQL on DB, ignoring SQLITE_OK and IGNORED_ERR. + (Note: the IGNORED_ERR parameter itself is not ignored.) */ +static svn_error_t * +exec_sql2(svn_sqlite__db_t *db, const char *sql, int ignored_err) +{ + char *err_msg; + int sqlite_err = sqlite3_exec(db->db3, sql, NULL, NULL, &err_msg); + + if (sqlite_err != SQLITE_OK && sqlite_err != ignored_err) + { + svn_error_t *err = svn_error_createf(SQLITE_ERROR_CODE(sqlite_err), NULL, + _("sqlite: %s (S%d)," + " executing statement '%s'"), + err_msg, sqlite_err, sql); + sqlite3_free(err_msg); + return err; + } + + return SVN_NO_ERROR; +} + + +static svn_error_t * +prepare_statement(svn_sqlite__stmt_t **stmt, svn_sqlite__db_t *db, + const char *text, apr_pool_t *result_pool) +{ + *stmt = apr_palloc(result_pool, sizeof(**stmt)); + (*stmt)->db = db; + (*stmt)->needs_reset = FALSE; + + SQLITE_ERR(sqlite3_prepare_v2(db->db3, text, -1, &(*stmt)->s3stmt, NULL), db); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_sqlite__exec_statements(svn_sqlite__db_t *db, int stmt_idx) +{ + SVN_ERR_ASSERT(stmt_idx < db->nbr_statements); + + return svn_error_trace(exec_sql(db, db->statement_strings[stmt_idx])); +} + + +svn_error_t * +svn_sqlite__get_statement(svn_sqlite__stmt_t **stmt, svn_sqlite__db_t *db, + int stmt_idx) +{ + SVN_ERR_ASSERT(stmt_idx < db->nbr_statements); + + if (db->prepared_stmts[stmt_idx] == NULL) + SVN_ERR(prepare_statement(&db->prepared_stmts[stmt_idx], db, + db->statement_strings[stmt_idx], + db->state_pool)); + + *stmt = db->prepared_stmts[stmt_idx]; + + if ((*stmt)->needs_reset) + return svn_error_trace(svn_sqlite__reset(*stmt)); + + return SVN_NO_ERROR; +} + +/* Like svn_sqlite__get_statement but gets an internal statement. + + All internal statements that use this api are executed with step_done(), + so we don't need the fallback reset handling here or in the pool cleanup */ +static svn_error_t * +get_internal_statement(svn_sqlite__stmt_t **stmt, svn_sqlite__db_t *db, + int stmt_idx) +{ + /* The internal statements are stored after the registered statements */ + int prep_idx = db->nbr_statements + stmt_idx; + SVN_ERR_ASSERT(stmt_idx < STMT_INTERNAL_LAST); + + if (db->prepared_stmts[prep_idx] == NULL) + SVN_ERR(prepare_statement(&db->prepared_stmts[prep_idx], db, + internal_statements[stmt_idx], + db->state_pool)); + + *stmt = db->prepared_stmts[prep_idx]; + + return SVN_NO_ERROR; +} + + +static svn_error_t * +step_with_expectation(svn_sqlite__stmt_t* stmt, + svn_boolean_t expecting_row) +{ + svn_boolean_t got_row; + + SVN_ERR(svn_sqlite__step(&got_row, stmt)); + if ((got_row && !expecting_row) + || + (!got_row && expecting_row)) + return svn_error_create(SVN_ERR_SQLITE_ERROR, + svn_sqlite__reset(stmt), + expecting_row + ? _("sqlite: Expected database row missing") + : _("sqlite: Extra database row found")); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__step_done(svn_sqlite__stmt_t *stmt) +{ + SVN_ERR(step_with_expectation(stmt, FALSE)); + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +svn_error_t * +svn_sqlite__step_row(svn_sqlite__stmt_t *stmt) +{ + return svn_error_trace(step_with_expectation(stmt, TRUE)); +} + + +svn_error_t * +svn_sqlite__step(svn_boolean_t *got_row, svn_sqlite__stmt_t *stmt) +{ + int sqlite_result = sqlite3_step(stmt->s3stmt); + + if (sqlite_result != SQLITE_DONE && sqlite_result != SQLITE_ROW) + { + svn_error_t *err1, *err2; + + err1 = svn_error_createf(SQLITE_ERROR_CODE(sqlite_result), NULL, + "sqlite: %s (S%d)", + sqlite3_errmsg(stmt->db->db3), sqlite_result); + err2 = svn_sqlite__reset(stmt); + return svn_error_compose_create(err1, err2); + } + + *got_row = (sqlite_result == SQLITE_ROW); + stmt->needs_reset = TRUE; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__insert(apr_int64_t *row_id, svn_sqlite__stmt_t *stmt) +{ + svn_boolean_t got_row; + + SVN_ERR(svn_sqlite__step(&got_row, stmt)); + if (row_id) + *row_id = sqlite3_last_insert_rowid(stmt->db->db3); + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +svn_error_t * +svn_sqlite__update(int *affected_rows, svn_sqlite__stmt_t *stmt) +{ + SVN_ERR(step_with_expectation(stmt, FALSE)); + + if (affected_rows) + *affected_rows = sqlite3_changes(stmt->db->db3); + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + + +static svn_error_t * +vbindf(svn_sqlite__stmt_t *stmt, const char *fmt, va_list ap) +{ + int count; + + for (count = 1; *fmt; fmt++, count++) + { + const void *blob; + apr_size_t blob_size; + const svn_token_map_t *map; + + switch (*fmt) + { + case 's': + SVN_ERR(svn_sqlite__bind_text(stmt, count, + va_arg(ap, const char *))); + break; + + case 'd': + SVN_ERR(svn_sqlite__bind_int(stmt, count, + va_arg(ap, int))); + break; + + case 'i': + case 'L': + SVN_ERR(svn_sqlite__bind_int64(stmt, count, + va_arg(ap, apr_int64_t))); + break; + + case 'b': + blob = va_arg(ap, const void *); + blob_size = va_arg(ap, apr_size_t); + SVN_ERR(svn_sqlite__bind_blob(stmt, count, blob, blob_size)); + break; + + case 'r': + SVN_ERR(svn_sqlite__bind_revnum(stmt, count, + va_arg(ap, svn_revnum_t))); + break; + + case 't': + map = va_arg(ap, const svn_token_map_t *); + SVN_ERR(svn_sqlite__bind_token(stmt, count, map, va_arg(ap, int))); + break; + + case 'n': + /* Skip this column: no binding */ + break; + + default: + SVN_ERR_MALFUNCTION(); + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__bindf(svn_sqlite__stmt_t *stmt, const char *fmt, ...) +{ + svn_error_t *err; + va_list ap; + + va_start(ap, fmt); + err = vbindf(stmt, fmt, ap); + va_end(ap); + return svn_error_trace(err); +} + +svn_error_t * +svn_sqlite__bind_int(svn_sqlite__stmt_t *stmt, + int slot, + int val) +{ + SQLITE_ERR(sqlite3_bind_int(stmt->s3stmt, slot, val), stmt->db); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__bind_int64(svn_sqlite__stmt_t *stmt, + int slot, + apr_int64_t val) +{ + SQLITE_ERR(sqlite3_bind_int64(stmt->s3stmt, slot, val), stmt->db); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__bind_text(svn_sqlite__stmt_t *stmt, + int slot, + const char *val) +{ + SQLITE_ERR(sqlite3_bind_text(stmt->s3stmt, slot, val, -1, SQLITE_TRANSIENT), + stmt->db); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__bind_blob(svn_sqlite__stmt_t *stmt, + int slot, + const void *val, + apr_size_t len) +{ + SQLITE_ERR(sqlite3_bind_blob(stmt->s3stmt, slot, val, (int) len, + SQLITE_TRANSIENT), + stmt->db); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__bind_token(svn_sqlite__stmt_t *stmt, + int slot, + const svn_token_map_t *map, + int value) +{ + const char *word = svn_token__to_word(map, value); + + SQLITE_ERR(sqlite3_bind_text(stmt->s3stmt, slot, word, -1, SQLITE_STATIC), + stmt->db); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__bind_revnum(svn_sqlite__stmt_t *stmt, + int slot, + svn_revnum_t value) +{ + if (SVN_IS_VALID_REVNUM(value)) + SQLITE_ERR(sqlite3_bind_int64(stmt->s3stmt, slot, + (sqlite_int64)value), stmt->db); + else + SQLITE_ERR(sqlite3_bind_null(stmt->s3stmt, slot), stmt->db); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__bind_properties(svn_sqlite__stmt_t *stmt, + int slot, + const apr_hash_t *props, + apr_pool_t *scratch_pool) +{ + svn_skel_t *skel; + svn_stringbuf_t *properties; + + if (props == NULL) + return svn_error_trace(svn_sqlite__bind_blob(stmt, slot, NULL, 0)); + + SVN_ERR(svn_skel__unparse_proplist(&skel, props, scratch_pool)); + properties = svn_skel__unparse(skel, scratch_pool); + return svn_error_trace(svn_sqlite__bind_blob(stmt, + slot, + properties->data, + properties->len)); +} + +svn_error_t * +svn_sqlite__bind_iprops(svn_sqlite__stmt_t *stmt, + int slot, + const apr_array_header_t *inherited_props, + apr_pool_t *scratch_pool) +{ + svn_skel_t *skel; + svn_stringbuf_t *properties; + + if (inherited_props == NULL) + return svn_error_trace(svn_sqlite__bind_blob(stmt, slot, NULL, 0)); + + SVN_ERR(svn_skel__unparse_iproplist(&skel, inherited_props, + scratch_pool, scratch_pool)); + properties = svn_skel__unparse(skel, scratch_pool); + return svn_error_trace(svn_sqlite__bind_blob(stmt, + slot, + properties->data, + properties->len)); +} + +svn_error_t * +svn_sqlite__bind_checksum(svn_sqlite__stmt_t *stmt, + int slot, + const svn_checksum_t *checksum, + apr_pool_t *scratch_pool) +{ + const char *csum_str; + + if (checksum == NULL) + csum_str = NULL; + else + csum_str = svn_checksum_serialize(checksum, scratch_pool, scratch_pool); + + return svn_error_trace(svn_sqlite__bind_text(stmt, slot, csum_str)); +} + + +const void * +svn_sqlite__column_blob(svn_sqlite__stmt_t *stmt, int column, + apr_size_t *len, apr_pool_t *result_pool) +{ + const void *val = sqlite3_column_blob(stmt->s3stmt, column); + *len = sqlite3_column_bytes(stmt->s3stmt, column); + + if (result_pool && val != NULL) + val = apr_pmemdup(result_pool, val, *len); + + return val; +} + +const char * +svn_sqlite__column_text(svn_sqlite__stmt_t *stmt, int column, + apr_pool_t *result_pool) +{ + /* cast from 'unsigned char' to regular 'char' */ + const char *result = (const char *)sqlite3_column_text(stmt->s3stmt, column); + + if (result_pool && result != NULL) + result = apr_pstrdup(result_pool, result); + + return result; +} + +svn_revnum_t +svn_sqlite__column_revnum(svn_sqlite__stmt_t *stmt, int column) +{ + if (svn_sqlite__column_is_null(stmt, column)) + return SVN_INVALID_REVNUM; + return (svn_revnum_t) sqlite3_column_int64(stmt->s3stmt, column); +} + +svn_boolean_t +svn_sqlite__column_boolean(svn_sqlite__stmt_t *stmt, int column) +{ + return sqlite3_column_int64(stmt->s3stmt, column) != 0; +} + +int +svn_sqlite__column_int(svn_sqlite__stmt_t *stmt, int column) +{ + return sqlite3_column_int(stmt->s3stmt, column); +} + +apr_int64_t +svn_sqlite__column_int64(svn_sqlite__stmt_t *stmt, int column) +{ + return sqlite3_column_int64(stmt->s3stmt, column); +} + +int +svn_sqlite__column_token(svn_sqlite__stmt_t *stmt, + int column, + const svn_token_map_t *map) +{ + /* cast from 'unsigned char' to regular 'char' */ + const char *word = (const char *)sqlite3_column_text(stmt->s3stmt, column); + + return svn_token__from_word_strict(map, word); +} + +int +svn_sqlite__column_token_null(svn_sqlite__stmt_t *stmt, + int column, + const svn_token_map_t *map, + int null_val) +{ + /* cast from 'unsigned char' to regular 'char' */ + const char *word = (const char *)sqlite3_column_text(stmt->s3stmt, column); + + if (!word) + return null_val; + + return svn_token__from_word_strict(map, word); +} + +svn_error_t * +svn_sqlite__column_properties(apr_hash_t **props, + svn_sqlite__stmt_t *stmt, + int column, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_size_t len; + const void *val; + + /* svn_skel__parse_proplist copies everything needed to result_pool */ + val = svn_sqlite__column_blob(stmt, column, &len, NULL); + if (val == NULL) + { + *props = NULL; + return SVN_NO_ERROR; + } + + SVN_ERR(svn_skel__parse_proplist(props, + svn_skel__parse(val, len, scratch_pool), + result_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__column_iprops(apr_array_header_t **iprops, + svn_sqlite__stmt_t *stmt, + int column, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_size_t len; + const void *val; + + /* svn_skel__parse_iprops copies everything needed to result_pool */ + val = svn_sqlite__column_blob(stmt, column, &len, NULL); + if (val == NULL) + { + *iprops = NULL; + return SVN_NO_ERROR; + } + + SVN_ERR(svn_skel__parse_iprops(iprops, + svn_skel__parse(val, len, scratch_pool), + result_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__column_checksum(const svn_checksum_t **checksum, + svn_sqlite__stmt_t *stmt, int column, + apr_pool_t *result_pool) +{ + const char *digest = svn_sqlite__column_text(stmt, column, NULL); + + if (digest == NULL) + *checksum = NULL; + else + SVN_ERR(svn_checksum_deserialize(checksum, digest, + result_pool, result_pool)); + + return SVN_NO_ERROR; +} + +svn_boolean_t +svn_sqlite__column_is_null(svn_sqlite__stmt_t *stmt, int column) +{ + return sqlite3_column_type(stmt->s3stmt, column) == SQLITE_NULL; +} + +int +svn_sqlite__column_bytes(svn_sqlite__stmt_t *stmt, int column) +{ + return sqlite3_column_bytes(stmt->s3stmt, column); +} + +svn_error_t * +svn_sqlite__finalize(svn_sqlite__stmt_t *stmt) +{ + SQLITE_ERR(sqlite3_finalize(stmt->s3stmt), stmt->db); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__reset(svn_sqlite__stmt_t *stmt) +{ + SQLITE_ERR(sqlite3_reset(stmt->s3stmt), stmt->db); + SQLITE_ERR(sqlite3_clear_bindings(stmt->s3stmt), stmt->db); + stmt->needs_reset = FALSE; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_sqlite__read_schema_version(int *version, + svn_sqlite__db_t *db, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR(prepare_statement(&stmt, db, "PRAGMA user_version;", scratch_pool)); + SVN_ERR(svn_sqlite__step_row(stmt)); + + *version = svn_sqlite__column_int(stmt, 0); + + return svn_error_trace(svn_sqlite__finalize(stmt)); +} + + +static volatile svn_atomic_t sqlite_init_state = 0; + +/* If possible, verify that SQLite was compiled in a thread-safe + manner. */ +/* Don't call this function directly! Use svn_atomic__init_once(). */ +static svn_error_t * +init_sqlite(void *baton, apr_pool_t *pool) +{ + if (sqlite3_libversion_number() < SVN_SQLITE_MIN_VERSION_NUMBER) + { + return svn_error_createf( + SVN_ERR_SQLITE_ERROR, NULL, + _("SQLite compiled for %s, but running with %s"), + SVN_SQLITE_MIN_VERSION, sqlite3_libversion()); + } + +#if APR_HAS_THREADS + + /* SQLite 3.5 allows verification of its thread-safety at runtime. + Older versions are simply expected to have been configured with + --enable-threadsafe, which compiles with -DSQLITE_THREADSAFE=1 + (or -DTHREADSAFE, for older versions). */ + if (! sqlite3_threadsafe()) + return svn_error_create(SVN_ERR_SQLITE_ERROR, NULL, + _("SQLite is required to be compiled and run in " + "thread-safe mode")); + + /* If SQLite has been already initialized, sqlite3_config() returns + SQLITE_MISUSE. */ + { + int err = sqlite3_config(SQLITE_CONFIG_MULTITHREAD); + if (err != SQLITE_OK && err != SQLITE_MISUSE) + return svn_error_createf(SQLITE_ERROR_CODE(err), NULL, + _("Could not configure SQLite (S%d)"), err); + } + SQLITE_ERR_MSG(sqlite3_initialize(), _("Could not initialize SQLite")); + +#endif /* APR_HAS_THRADS */ + + return SVN_NO_ERROR; +} + +static svn_error_t * +internal_open(sqlite3 **db3, const char *path, svn_sqlite__mode_t mode, + apr_pool_t *scratch_pool) +{ + { + int flags; + + if (mode == svn_sqlite__mode_readonly) + flags = SQLITE_OPEN_READONLY; + else if (mode == svn_sqlite__mode_readwrite) + flags = SQLITE_OPEN_READWRITE; + else if (mode == svn_sqlite__mode_rwcreate) + flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; + else + SVN_ERR_MALFUNCTION(); + + /* Turn off SQLite's mutexes. All svn objects are single-threaded, + so we can already guarantee that our use of the SQLite handle + will be serialized properly. + + Note: in 3.6.x, we've already config'd SQLite into MULTITHREAD mode, + so this is probably redundant, but if we are running in a process where + somebody initialized SQLite before us it is needed anyway. */ + flags |= SQLITE_OPEN_NOMUTEX; + + /* Open the database. Note that a handle is returned, even when an error + occurs (except for out-of-memory); thus, we can safely use it to + extract an error message and construct an svn_error_t. */ + { + /* We'd like to use SQLITE_ERR here, but we can't since it would + just return an error and leave the database open. So, we need to + do this manually. */ + /* ### SQLITE_CANTOPEN */ + int err_code = sqlite3_open_v2(path, db3, flags, NULL); + if (err_code != SQLITE_OK) + { + /* Save the error message before closing the SQLite handle. */ + char *msg = apr_pstrdup(scratch_pool, sqlite3_errmsg(*db3)); + + /* We don't catch the error here, since we care more about the open + error than the close error at this point. */ + sqlite3_close(*db3); + + SQLITE_ERR_MSG(err_code, msg); + } + } + } + + /* Retry until timeout when database is busy. */ + SQLITE_ERR_MSG(sqlite3_busy_timeout(*db3, BUSY_TIMEOUT), + sqlite3_errmsg(*db3)); + + return SVN_NO_ERROR; +} + + +/* APR cleanup function used to close the database when its pool is destroyed. + DATA should be the svn_sqlite__db_t handle for the database. */ +static apr_status_t +close_apr(void *data) +{ + svn_sqlite__db_t *db = data; + svn_error_t *err = SVN_NO_ERROR; + apr_status_t result; + int i; + + /* Check to see if we've already closed this database. */ + if (db->db3 == NULL) + return APR_SUCCESS; + + /* Finalize any existing prepared statements. */ + for (i = 0; i < db->nbr_statements; i++) + { + if (db->prepared_stmts[i]) + { + if (db->prepared_stmts[i]->needs_reset) + { +#ifdef SVN_DEBUG + const char *stmt_text = db->statement_strings[i]; + stmt_text = stmt_text; /* Provide value for debugger */ + + SVN_ERR_MALFUNCTION_NO_RETURN(); +#else + err = svn_error_compose_create( + err, + svn_sqlite__reset(db->prepared_stmts[i])); +#endif + } + err = svn_error_compose_create( + svn_sqlite__finalize(db->prepared_stmts[i]), err); + } + } + /* And finalize any used internal statements */ + for (; i < db->nbr_statements + STMT_INTERNAL_LAST; i++) + { + if (db->prepared_stmts[i]) + { + err = svn_error_compose_create( + svn_sqlite__finalize(db->prepared_stmts[i]), err); + } + } + + result = sqlite3_close(db->db3); + + /* If there's a pre-existing error, return it. */ + if (err) + { + result = err->apr_err; + svn_error_clear(err); + return result; + } + + if (result != SQLITE_OK) + return SQLITE_ERROR_CODE(result); /* ### lossy */ + + db->db3 = NULL; + + return APR_SUCCESS; +} + + +svn_error_t * +svn_sqlite__open(svn_sqlite__db_t **db, const char *path, + svn_sqlite__mode_t mode, const char * const statements[], + int unused1, const char * const *unused2, + apr_pool_t *result_pool, apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_atomic__init_once(&sqlite_init_state, + init_sqlite, NULL, scratch_pool)); + + *db = apr_pcalloc(result_pool, sizeof(**db)); + + SVN_ERR(internal_open(&(*db)->db3, path, mode, scratch_pool)); + +#ifdef SQLITE3_DEBUG + sqlite3_trace((*db)->db3, sqlite_tracer, (*db)->db3); +#endif +#ifdef SQLITE3_PROFILE + sqlite3_profile((*db)->db3, sqlite_profiler, (*db)->db3); +#endif + + /* ### simplify this. remnants of some old SQLite compat code. */ + { + int ignored_err = SQLITE_OK; + + SVN_ERR(exec_sql2(*db, "PRAGMA case_sensitive_like=1;", ignored_err)); + } + + SVN_ERR(exec_sql(*db, + /* Disable synchronization to disable the explicit disk flushes + that make Sqlite up to 50 times slower; especially on small + transactions. + + This removes some stability guarantees on specific hardware + and power failures, but still guarantees atomic commits on + application crashes. With our dependency on external data + like pristine files (Wc) and revision files (repository), + we can't keep up these additional guarantees anyway. + + ### Maybe switch to NORMAL(1) when we use larger transaction + scopes */ + "PRAGMA synchronous=OFF;" + /* Enable recursive triggers so that a user trigger will fire + in the deletion phase of an INSERT OR REPLACE statement. + Requires SQLite >= 3.6.18 */ + "PRAGMA recursive_triggers=ON;")); + +#if defined(SVN_DEBUG) + /* When running in debug mode, enable the checking of foreign key + constraints. This has possible performance implications, so we don't + bother to do it for production...for now. */ + SVN_ERR(exec_sql(*db, "PRAGMA foreign_keys=ON;")); +#endif + + /* Store temporary tables in RAM instead of in temporary files, but don't + fail on this if this option is disabled in the sqlite compilation by + setting SQLITE_TEMP_STORE to 0 (always to disk) */ + svn_error_clear(exec_sql(*db, "PRAGMA temp_store = MEMORY;")); + + /* Store the provided statements. */ + if (statements) + { + (*db)->statement_strings = statements; + (*db)->nbr_statements = 0; + while (*statements != NULL) + { + statements++; + (*db)->nbr_statements++; + } + + (*db)->prepared_stmts = apr_pcalloc( + result_pool, + ((*db)->nbr_statements + STMT_INTERNAL_LAST) + * sizeof(svn_sqlite__stmt_t *)); + } + else + { + (*db)->nbr_statements = 0; + (*db)->prepared_stmts = apr_pcalloc(result_pool, + (0 + STMT_INTERNAL_LAST) + * sizeof(svn_sqlite__stmt_t *)); + } + + (*db)->state_pool = result_pool; + apr_pool_cleanup_register(result_pool, *db, close_apr, apr_pool_cleanup_null); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__close(svn_sqlite__db_t *db) +{ + apr_status_t result = apr_pool_cleanup_run(db->state_pool, db, close_apr); + + if (result == APR_SUCCESS) + return SVN_NO_ERROR; + + return svn_error_wrap_apr(result, NULL); +} + +static svn_error_t * +reset_all_statements(svn_sqlite__db_t *db, + svn_error_t *error_to_wrap) +{ + int i; + svn_error_t *err; + + /* ### Should we reorder the errors in this specific case + ### to avoid returning the normal error as top level error? */ + + err = svn_error_compose_create(error_to_wrap, + svn_error_create(SVN_ERR_SQLITE_RESETTING_FOR_ROLLBACK, + NULL, NULL)); + + for (i = 0; i < db->nbr_statements; i++) + if (db->prepared_stmts[i] && db->prepared_stmts[i]->needs_reset) + err = svn_error_compose_create(err, + svn_sqlite__reset(db->prepared_stmts[i])); + + return err; +} + +svn_error_t * +svn_sqlite__begin_transaction(svn_sqlite__db_t *db) +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR(get_internal_statement(&stmt, db, + STMT_INTERNAL_BEGIN_TRANSACTION)); + SVN_ERR(svn_sqlite__step_done(stmt)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__begin_immediate_transaction(svn_sqlite__db_t *db) +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR(get_internal_statement(&stmt, db, + STMT_INTERNAL_BEGIN_IMMEDIATE_TRANSACTION)); + SVN_ERR(svn_sqlite__step_done(stmt)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__begin_savepoint(svn_sqlite__db_t *db) +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR(get_internal_statement(&stmt, db, + STMT_INTERNAL_SAVEPOINT_SVN)); + SVN_ERR(svn_sqlite__step_done(stmt)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__finish_transaction(svn_sqlite__db_t *db, + svn_error_t *err) +{ + svn_sqlite__stmt_t *stmt; + + /* Commit or rollback the sqlite transaction. */ + if (err) + { + svn_error_t *err2; + + err2 = get_internal_statement(&stmt, db, + STMT_INTERNAL_ROLLBACK_TRANSACTION); + if (!err2) + err2 = svn_sqlite__step_done(stmt); + + if (err2 && err2->apr_err == SVN_ERR_SQLITE_BUSY) + { + /* ### Houston, we have a problem! + + We are trying to rollback but we can't because some + statements are still busy. This leaves the database + unusable for future transactions as the current transaction + is still open. + + As we are returning the actual error as the most relevant + error in the chain, our caller might assume that it can + retry/compensate on this error (e.g. SVN_WC_LOCKED), while + in fact the SQLite database is unusable until the statements + started within this transaction are reset and the transaction + aborted. + + We try to compensate by resetting all prepared but unreset + statements; but we leave the busy error in the chain anyway to + help diagnosing the original error and help in finding where + a reset statement is missing. */ + + err2 = reset_all_statements(db, err2); + err2 = svn_error_compose_create( + svn_sqlite__step_done(stmt), + err2); + } + + return svn_error_compose_create(err, + err2); + } + + SVN_ERR(get_internal_statement(&stmt, db, STMT_INTERNAL_COMMIT_TRANSACTION)); + return svn_error_trace(svn_sqlite__step_done(stmt)); +} + +svn_error_t * +svn_sqlite__finish_savepoint(svn_sqlite__db_t *db, + svn_error_t *err) +{ + svn_sqlite__stmt_t *stmt; + + if (err) + { + svn_error_t *err2; + + err2 = get_internal_statement(&stmt, db, + STMT_INTERNAL_ROLLBACK_TO_SAVEPOINT_SVN); + + if (!err2) + err2 = svn_sqlite__step_done(stmt); + + if (err2 && err2->apr_err == SVN_ERR_SQLITE_BUSY) + { + /* Ok, we have a major problem. Some statement is still open, which + makes it impossible to release this savepoint. + + ### See huge comment in svn_sqlite__finish_transaction for + further details */ + + err2 = reset_all_statements(db, err2); + err2 = svn_error_compose_create(svn_sqlite__step_done(stmt), err2); + } + + err = svn_error_compose_create(err, err2); + err2 = get_internal_statement(&stmt, db, + STMT_INTERNAL_RELEASE_SAVEPOINT_SVN); + + if (!err2) + err2 = svn_sqlite__step_done(stmt); + + return svn_error_trace(svn_error_compose_create(err, err2)); + } + + SVN_ERR(get_internal_statement(&stmt, db, + STMT_INTERNAL_RELEASE_SAVEPOINT_SVN)); + + return svn_error_trace(svn_sqlite__step_done(stmt)); +} + +svn_error_t * +svn_sqlite__with_transaction(svn_sqlite__db_t *db, + svn_sqlite__transaction_callback_t cb_func, + void *cb_baton, + apr_pool_t *scratch_pool /* NULL allowed */) +{ + SVN_SQLITE__WITH_TXN(cb_func(cb_baton, db, scratch_pool), db); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__with_immediate_transaction( + svn_sqlite__db_t *db, + svn_sqlite__transaction_callback_t cb_func, + void *cb_baton, + apr_pool_t *scratch_pool /* NULL allowed */) +{ + SVN_SQLITE__WITH_IMMEDIATE_TXN(cb_func(cb_baton, db, scratch_pool), db); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__with_lock(svn_sqlite__db_t *db, + svn_sqlite__transaction_callback_t cb_func, + void *cb_baton, + apr_pool_t *scratch_pool /* NULL allowed */) +{ + SVN_SQLITE__WITH_LOCK(cb_func(cb_baton, db, scratch_pool), db); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__hotcopy(const char *src_path, + const char *dst_path, + apr_pool_t *scratch_pool) +{ + svn_sqlite__db_t *src_db; + + SVN_ERR(svn_sqlite__open(&src_db, src_path, svn_sqlite__mode_readonly, + NULL, 0, NULL, + scratch_pool, scratch_pool)); + + { + svn_sqlite__db_t *dst_db; + sqlite3_backup *backup; + int rc1, rc2; + + SVN_ERR(svn_sqlite__open(&dst_db, dst_path, svn_sqlite__mode_rwcreate, + NULL, 0, NULL, scratch_pool, scratch_pool)); + backup = sqlite3_backup_init(dst_db->db3, "main", src_db->db3, "main"); + if (!backup) + return svn_error_createf(SVN_ERR_SQLITE_ERROR, NULL, + _("SQLite hotcopy failed for %s"), src_path); + do + { + /* Pages are usually 1024 byte (SQLite docs). On my laptop + copying gets faster as the number of pages is increased up + to about 64, beyond that speed levels off. Lets put the + number of pages an order of magnitude higher, this is still + likely to be a fraction of large databases. */ + rc1 = sqlite3_backup_step(backup, 1024); + + /* Should we sleep on SQLITE_OK? That would make copying a + large database take much longer. When we do sleep how, + long should we sleep? Should the sleep get longer if we + keep getting BUSY/LOCKED? I have no real reason for + choosing 25. */ + if (rc1 == SQLITE_BUSY || rc1 == SQLITE_LOCKED) + sqlite3_sleep(25); + } + while (rc1 == SQLITE_OK || rc1 == SQLITE_BUSY || rc1 == SQLITE_LOCKED); + rc2 = sqlite3_backup_finish(backup); + if (rc1 != SQLITE_DONE) + SQLITE_ERR(rc1, dst_db); + SQLITE_ERR(rc2, dst_db); + SVN_ERR(svn_sqlite__close(dst_db)); + } + + SVN_ERR(svn_sqlite__close(src_db)); + + return SVN_NO_ERROR; +} + +struct function_wrapper_baton_t +{ + svn_sqlite__func_t func; + void *baton; + + apr_pool_t *scratch_pool; +}; + +static void +wrapped_func(sqlite3_context *context, + int argc, + sqlite3_value *values[]) +{ + struct function_wrapper_baton_t *fwb = sqlite3_user_data(context); + svn_sqlite__context_t sctx; + svn_sqlite__value_t **local_vals = + apr_palloc(fwb->scratch_pool, + sizeof(svn_sqlite__value_t *) * argc); + svn_error_t *err; + int i; + + sctx.context = context; + + for (i = 0; i < argc; i++) + { + local_vals[i] = apr_palloc(fwb->scratch_pool, sizeof(*local_vals[i])); + local_vals[i]->value = values[i]; + } + + err = fwb->func(&sctx, argc, local_vals, fwb->scratch_pool); + svn_pool_clear(fwb->scratch_pool); + + if (err) + { + char buf[256]; + sqlite3_result_error(context, + svn_err_best_message(err, buf, sizeof(buf)), + -1); + svn_error_clear(err); + } +} + +svn_error_t * +svn_sqlite__create_scalar_function(svn_sqlite__db_t *db, + const char *func_name, + int argc, + svn_sqlite__func_t func, + void *baton) +{ + struct function_wrapper_baton_t *fwb = apr_pcalloc(db->state_pool, + sizeof(*fwb)); + + fwb->scratch_pool = svn_pool_create(db->state_pool); + fwb->func = func; + fwb->baton = baton; + + SQLITE_ERR(sqlite3_create_function(db->db3, func_name, argc, SQLITE_ANY, + fwb, wrapped_func, NULL, NULL), + db); + + return SVN_NO_ERROR; +} + +int +svn_sqlite__value_type(svn_sqlite__value_t *val) +{ + return sqlite3_value_type(val->value); +} + +const char * +svn_sqlite__value_text(svn_sqlite__value_t *val) +{ + return (const char *) sqlite3_value_text(val->value); +} + +void +svn_sqlite__result_null(svn_sqlite__context_t *sctx) +{ + sqlite3_result_null(sctx->context); +} + +void +svn_sqlite__result_int64(svn_sqlite__context_t *sctx, apr_int64_t val) +{ + sqlite3_result_int64(sctx->context, val); +} |