diff options
Diffstat (limited to 'subversion/tests/libsvn_wc/wc-queries-test.c')
-rw-r--r-- | subversion/tests/libsvn_wc/wc-queries-test.c | 897 |
1 files changed, 897 insertions, 0 deletions
diff --git a/subversion/tests/libsvn_wc/wc-queries-test.c b/subversion/tests/libsvn_wc/wc-queries-test.c new file mode 100644 index 0000000..0621720 --- /dev/null +++ b/subversion/tests/libsvn_wc/wc-queries-test.c @@ -0,0 +1,897 @@ +/* + * wc-queries-test.c -- test the evaluation of the wc Sqlite queries + * + * ==================================================================== + * 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 "svn_pools.h" +#include "svn_ctype.h" +#include "private/svn_dep_compat.h" + +#include "svn_private_config.h" + +#include "../svn_test.h" + +#ifdef SVN_SQLITE_INLINE +/* Include sqlite3 inline, making all symbols private. */ + #define SQLITE_API static + #ifdef __APPLE__ + #include <Availability.h> + #if __MAC_OS_X_VERSION_MIN_REQUIRED < 1060 + /* <libkern/OSAtomic.h> is included on OS X by sqlite3.c, and + on old systems (Leopard or older), it cannot be compiled + with -std=c89 because it uses inline. This is a work-around. */ + #define inline __inline__ + #include <libkern/OSAtomic.h> + #undef inline + #endif + #endif + #include <sqlite3.c> +#else + #include <sqlite3.h> +#endif + +#include "../../libsvn_wc/wc-queries.h" + +WC_QUERIES_SQL_DECLARE_STATEMENTS(wc_queries); +WC_QUERIES_SQL_DECLARE_STATEMENT_INFO(wc_query_info); + +/* The first query after the normal wc queries */ +#define STMT_SCHEMA_FIRST STMT_CREATE_SCHEMA + +#define SQLITE_ERR(x) \ +{ \ + int sqlite_err__temp = (x); \ + if (sqlite_err__temp != SQLITE_OK) \ + return svn_error_createf(SVN_ERR_SQLITE_ERROR, \ + NULL, "sqlite: %s", \ + sqlite3_errmsg(sdb)); \ +} while (0) + +/* Schema creation statements fail during preparing when the table + already exists, and must be evaluated before testing the + queries. Statements above STMT_SCHEMA_FIRST only need to be + included here when they need to be evaluated before testing the + statements */ +static const int schema_statements[] = +{ + /* Usual tables */ + STMT_CREATE_SCHEMA, + STMT_CREATE_NODES, + STMT_CREATE_NODES_TRIGGERS, + STMT_CREATE_EXTERNALS, + STMT_INSTALL_SCHEMA_STATISTICS, + /* Memory tables */ + STMT_CREATE_TARGETS_LIST, + STMT_CREATE_CHANGELIST_LIST, + STMT_CREATE_CHANGELIST_TRIGGER, + STMT_CREATE_TARGET_PROP_CACHE, + STMT_CREATE_REVERT_LIST, + STMT_CREATE_DELETE_LIST, + STMT_CREATE_UPDATE_MOVE_LIST, + -1 /* final marker */ +}; + +/* These statements currently trigger warnings. It would be nice if + we could annotate these in wc-queries.sql */ +static const int slow_statements[] = +{ + /* Operate on the entire WC */ + STMT_SELECT_ALL_NODES, /* schema validation code */ + + /* Updates all records for a repository (designed slow) */ + STMT_UPDATE_LOCK_REPOS_ID, + + /* Full temporary table read */ + STMT_INSERT_ACTUAL_EMPTIES, + STMT_SELECT_REVERT_LIST_RECURSIVE, + STMT_SELECT_DELETE_LIST, + STMT_SELECT_UPDATE_MOVE_LIST, + + /* Designed as slow to avoid penalty on other queries */ + STMT_SELECT_UNREFERENCED_PRISTINES, + + /* Slow, but just if foreign keys are enabled: + * STMT_DELETE_PRISTINE_IF_UNREFERENCED, + */ + STMT_HAVE_STAT1_TABLE, /* Queries sqlite_master which has no index */ + + -1 /* final marker */ +}; + +/* Statements that just read the first record from a table, + using the primary key. Specialized as different sqlite + versions produce different results */ +static const int primary_key_statements[] = +{ + /* Is there a record? ### Can we somehow check for LIMIT 1, + and primary key instead of adding a list? */ + STMT_LOOK_FOR_WORK, + STMT_SELECT_WORK_ITEM, + + -1 /* final marker */ +}; + +/* Helper function to determine if a statement is in a list */ +static svn_boolean_t +in_list(const int list[], int stmt_idx) +{ + int i; + + for (i = 0; list[i] != -1; i++) + { + if (list[i] == stmt_idx) + return TRUE; + } + return FALSE; +} + +/* Helpers to determine if a statement is in a common list */ +#define is_slow_statement(stmt_idx) in_list(slow_statements, stmt_idx) +#define is_schema_statement(stmt_idx) \ + ((stmt_idx >= STMT_SCHEMA_FIRST) || in_list(schema_statements, stmt_idx)) + + +/* Create an in-memory db for evaluating queries */ +static svn_error_t * +create_memory_db(sqlite3 **db, + apr_pool_t *pool) +{ + sqlite3 *sdb; + int i; + + /* Create an in-memory raw database */ + SVN_TEST_ASSERT(sqlite3_initialize() == SQLITE_OK); + SQLITE_ERR(sqlite3_open_v2("", &sdb, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + NULL)); + + /* Create schema */ + for (i = 0; schema_statements[i] != -1; i++) + { + SQLITE_ERR(sqlite3_exec(sdb, wc_queries[schema_statements[i]], NULL, NULL, NULL)); + } + + *db = sdb; + return SVN_NO_ERROR; +} + +/* Verify sqlite3 runtime version */ +static svn_error_t * +test_sqlite_version(apr_pool_t *scratch_pool) +{ + printf("DBG: Using Sqlite %s\n", sqlite3_version); + + if (sqlite3_libversion_number() != SQLITE_VERSION_NUMBER) + printf("DBG: Compiled against Sqlite %s", SQLITE_VERSION); + + if (sqlite3_libversion_number() < SQLITE_VERSION_NUMBER) + return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, + "Compiled against Sqlite %s (at runtime we have Sqlite %s)", + SQLITE_VERSION, sqlite3_version); + +#if !SQLITE_VERSION_AT_LEAST(3, 7, 9) + return svn_error_create(SVN_ERR_TEST_FAILED, NULL, + "Sqlite upgrade recommended:\n" + "****************************************************************\n" + "* Subversion needs at least SQLite 3.7.9 to work optimally *\n" + "* *\n" + "* With older versions, at least some queries that are expected *\n" + "* to be using an index are not. This makes some operations use *\n" + "* every node in the working copy instead of just one. *\n" + "* *\n" + "* While Subversion works correctly in this case, you may see *\n" + "* slowdowns of WELL MORE THAN 1000* in some cases! *\n" + "* *\n" + "* *\n" + "* SQLITE UPGRADE RECOMMENDED *\n" + "****************************************************************\n"); +#else + return SVN_NO_ERROR; +#endif +} + +/* Parse all normal queries */ +static svn_error_t * +test_parsable(apr_pool_t *scratch_pool) +{ + sqlite3 *sdb; + int i; + + SVN_ERR(create_memory_db(&sdb, scratch_pool)); + + for (i=0; i < STMT_SCHEMA_FIRST; i++) + { + sqlite3_stmt *stmt; + const char *text = wc_queries[i]; + + if (is_schema_statement(i)) + continue; + + /* Some of our statement texts contain multiple queries. We prepare + them all. */ + while (*text != '\0') + { + const char *tail; + int r = sqlite3_prepare_v2(sdb, text, -1, &stmt, &tail); + + if (r != SQLITE_OK) + return svn_error_createf(SVN_ERR_SQLITE_ERROR, NULL, + "Preparing %s failed: %s\n%s", + wc_query_info[i][0], + sqlite3_errmsg(sdb), + text); + + SQLITE_ERR(sqlite3_finalize(stmt)); + + /* Continue after the current statement */ + text = tail; + } + } + + SQLITE_ERR(sqlite3_close(sdb)); /* Close the DB if ok; otherwise leaked */ + + return SVN_NO_ERROR; +} + +/* Contains a parsed record from EXPLAIN QUERY PLAN */ +struct explanation_item +{ + const char *operation; + const char *table; + const char *alias; + svn_boolean_t scan; + svn_boolean_t search; + svn_boolean_t covered_by_index; + svn_boolean_t primary_key; + svn_boolean_t automatic_index; + const char *index; + const char *expressions; + const char *expected; + + const char *compound_left; + const char *compound_right; + svn_boolean_t create_btree; + + int expression_vars; + int expected_rows; +}; + +#define MATCH_TOKEN(x, y) (x && (strcmp(x, y) == 0)) + +/* Simple parser for the Sqlite textual explanation into an explanation_item. + Writes "DBG:" lines when sqlite produces unexpected results. When no + valid explanation_item can be parsed sets *PARSED_ITEM to NULL, otherwise + to a valid result. */ +static svn_error_t * +parse_explanation_item(struct explanation_item **parsed_item, + const char *text, + apr_pool_t *result_pool) +{ + struct explanation_item *item = apr_pcalloc(result_pool, sizeof(*item)); + char *token; + char *last; + char *tmp = apr_pstrdup(result_pool, text); + const char *tmp_end = &tmp[strlen(tmp)]; + + *parsed_item = NULL; + + item->operation = apr_strtok(tmp, " ", &last); + + if (!item->operation) + { + return SVN_NO_ERROR; + } + + item->scan = MATCH_TOKEN(item->operation, "SCAN"); + + if (item->scan || MATCH_TOKEN(item->operation, "SEARCH")) + { + item->search = TRUE; /* Search or scan */ + token = apr_strtok(NULL, " ", &last); + + if (!MATCH_TOKEN(token, "TABLE")) + { + printf("DBG: Expected 'TABLE', got '%s' in '%s'\n", token, text); + return SVN_NO_ERROR; /* Nothing to parse */ + } + + item->table = apr_strtok(NULL, " ", &last); + + token = apr_strtok(NULL, " ", &last); + + /* Skip alias */ + if (MATCH_TOKEN(token, "AS")) + { + item->alias = apr_strtok(NULL, " ", &last); + token = apr_strtok(NULL, " ", &last); + } + + if (MATCH_TOKEN(token, "USING")) + { + token = apr_strtok(NULL, " ", &last); + + if (MATCH_TOKEN(token, "AUTOMATIC")) + { + /* Pain: A temporary index is created */ + item->automatic_index = TRUE; + token = apr_strtok(NULL, " ", &last); + } + + /* Handle COVERING */ + if (MATCH_TOKEN(token, "COVERING")) + { + /* Bonus: Query will be answered by just using the index */ + item->covered_by_index = TRUE; + token = apr_strtok(NULL, " ", &last); + } + + if (MATCH_TOKEN(token, "INDEX")) + { + item->index = apr_strtok(NULL, " ", &last); + } + else if (MATCH_TOKEN(token, "INTEGER")) + { + token = apr_strtok(NULL, " ", &last); + if (!MATCH_TOKEN(token, "PRIMARY")) + { + printf("DBG: Expected 'PRIMARY', got '%s' in '%s'\n", + token, text); + return SVN_NO_ERROR; + } + + token = apr_strtok(NULL, " ", &last); + if (!MATCH_TOKEN(token, "KEY")) + { + printf("DBG: Expected 'KEY', got '%s' in '%s'\n", + token, text); + return SVN_NO_ERROR; + } + + item->primary_key = TRUE; + } + else + { + printf("DBG: Expected 'INDEX' or 'PRIMARY', got '%s' in '%s'\n", + token, text); + return SVN_NO_ERROR; + } + + token = apr_strtok(NULL, " ", &last); + } + + if (token && token[0] == '(' && token[1] != '~') + { + /* Undo the tokenization to switch parser rules */ + size_t token_len = strlen(token); + + if (token + token_len < tmp_end) + token[token_len] = ' '; + + if (token[token_len] == '\0') + last[-1] = ' '; + + token++; /* Skip the '(' */ + + item->expressions = apr_strtok(token, ")", &last); + token = apr_strtok(NULL, " ", &last); + } + + if (token && *token == '(' && token[1] == '~') + { + /* Undo the tokenization to switch parser rules */ + size_t token_len = strlen(token); + + if (token + token_len < tmp_end) + token[token_len] = ' '; + + if (token[token_len] == '\0') + last[-1] = ' '; + + token += 2; /* Skip "(~" */ + + item->expected = apr_strtok(token, ")", &last); + token = apr_strtok(NULL, " ", &last); + } + + if (token) + { + printf("DBG: Unexpected token '%s' in '%s'\n", + token, text); + return SVN_NO_ERROR; + } + + /* Parsing successfull */ + } + else if (MATCH_TOKEN(item->operation, "EXECUTE")) + { + /* Subquery handling */ + return SVN_NO_ERROR; + } + else if (MATCH_TOKEN(item->operation, "COMPOUND")) + { + /* Handling temporary table (E.g. UNION) */ + + token = apr_strtok(NULL, " ", &last); + if (!MATCH_TOKEN(token, "SUBQUERIES")) + { + printf("DBG: Expected 'SUBQUERIES', got '%s' in '%s'\n", token, + text); + return SVN_NO_ERROR; + } + + item->compound_left = apr_strtok(NULL, " ", &last); + token = apr_strtok(NULL, " ", &last); + + if (!MATCH_TOKEN(token, "AND")) + { + printf("DBG: Expected 'AND', got '%s' in '%s'\n", token, text); + return SVN_NO_ERROR; + } + + item->compound_right = apr_strtok(NULL, " ", &last); + + token = apr_strtok(NULL, " ", &last); + if (MATCH_TOKEN(token, "USING")) + { + token = apr_strtok(NULL, " ", &last); + if (!MATCH_TOKEN(token, "TEMP")) + { + printf("DBG: Expected 'TEMP', got '%s' in '%s'\n", token, text); + } + token = apr_strtok(NULL, " ", &last); + if (!MATCH_TOKEN(token, "B-TREE")) + { + printf("DBG: Expected 'B-TREE', got '%s' in '%s'\n", token, + text); + } + item->create_btree = TRUE; + } + } + else if (MATCH_TOKEN(item->operation, "USE")) + { + /* Using a temporary table for ordering results */ + /* ### Need parsing */ + item->create_btree = TRUE; + } + else + { + printf("DBG: Unhandled sqlite operation '%s' in explanation\n", item->operation); + return SVN_NO_ERROR; + } + + if (item->expressions) + { + const char *p; + + for (p = item->expressions; *p; p++) + { + if (*p == '?') + item->expression_vars++; + } + } + if (item->expected) + { + item->expected_rows = atoi(item->expected); + } + + *parsed_item = item; + return SVN_NO_ERROR; +} + +/* Sqlite has an SQLITE_OMIT_EXPLAIN compilation flag, which may make + explain query just evaluate the query. Some older versions use a + different number of columns (and different texts) for + EXPLAIN query plan. + + If none of this is true set *SUPPORTED to TRUE, otherwise to FALSE */ +static svn_error_t * +supported_explain_query_plan(svn_boolean_t *supported, + sqlite3 *sdb, + apr_pool_t *scratch_pool) +{ + sqlite3_stmt *stmt; + int r; + + *supported = TRUE; + + r = sqlite3_prepare(sdb, "EXPLAIN QUERY PLAN SELECT 1", + -1, &stmt, NULL); + + if (r != SQLITE_OK) + { + *supported = FALSE; + return SVN_NO_ERROR; + } + + if (sqlite3_step(stmt) == SQLITE_ROW) + { + if (sqlite3_column_count(stmt) < 4) + { + *supported = FALSE; + /* Fall through */ + } + } + + SQLITE_ERR(sqlite3_reset(stmt)); + SQLITE_ERR(sqlite3_finalize(stmt)); + return SVN_NO_ERROR; +} + + +/* Returns TRUE if TABLE_NAME specifies a nodes table, which should be indexed + by wc_id and either local_relpath or parent_relpath */ +static svn_boolean_t +is_node_table(const char *table_name) +{ + return (apr_strnatcasecmp(table_name, "nodes") == 0 + || apr_strnatcasecmp(table_name, "actual_node") == 0 + || apr_strnatcasecmp(table_name, "externals") == 0 + || apr_strnatcasecmp(table_name, "lock") == 0 + || apr_strnatcasecmp(table_name, "wc_lock") == 0 + || FALSE); +} + +/* Returns TRUE if TABLE specifies an intermediate result table, which is + allowed to have table scans, etc. */ +static svn_boolean_t +is_result_table(const char *table_name) +{ + return (apr_strnatcasecmp(table_name, "target_prop_cache") == 0 + || apr_strnatcasecmp(table_name, "changelist_list") == 0 + || FALSE); +} + +static svn_error_t * +test_query_expectations(apr_pool_t *scratch_pool) +{ + sqlite3 *sdb; + int i; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + svn_error_t *warnings = NULL; + svn_boolean_t supports_query_info; + + SVN_ERR(create_memory_db(&sdb, scratch_pool)); + + SVN_ERR(supported_explain_query_plan(&supports_query_info, sdb, + scratch_pool)); + if (!supports_query_info) + { + SQLITE_ERR(sqlite3_close(sdb)); + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, + "Sqlite doesn't support EXPLAIN QUERY PLAN"); + } + + for (i=0; i < STMT_SCHEMA_FIRST; i++) + { + sqlite3_stmt *stmt; + const char *tail; + int r; + svn_boolean_t warned = FALSE; + apr_array_header_t *rows = NULL; + + if (is_schema_statement(i)) + continue; + + /* Prepare statement to find if it is a single statement. */ + r = sqlite3_prepare_v2(sdb, wc_queries[i], -1, &stmt, &tail); + + if (r != SQLITE_OK) + continue; /* Parse failure is already reported by 'test_parable' */ + + SQLITE_ERR(sqlite3_finalize(stmt)); + if (tail[0] != '\0') + continue; /* Multi-queries are currently not testable */ + + svn_pool_clear(iterpool); + + r = sqlite3_prepare_v2(sdb, + apr_pstrcat(iterpool, + "EXPLAIN QUERY PLAN ", + wc_queries[i], + NULL), + -1, &stmt, &tail); + + if (r != SQLITE_OK) + continue; /* EXPLAIN not enabled or doesn't support this query */ + + while (SQLITE_ROW == (r = sqlite3_step(stmt))) + { + /*int iSelectid; + int iOrder; + int iFrom;*/ + const unsigned char *zDetail; + char *detail; + struct explanation_item *item; + + /* ### The following code is correct for current Sqlite versions + ### (tested with 3.7.x), but the EXPLAIN QUERY PLAN output + ### is not guaranteed to be stable for future versions. */ + + /* Names as in Sqlite documentation */ + /*iSelectid = sqlite3_column_int(stmt, 0); + iOrder = sqlite3_column_int(stmt, 1); + iFrom = sqlite3_column_int(stmt, 2);*/ + zDetail = sqlite3_column_text(stmt, 3); + + if (! zDetail) + continue; + + if (!rows) + rows = apr_array_make(iterpool, 10, sizeof(const char*)); + + detail = apr_pstrdup(iterpool, (const char*)zDetail); + + APR_ARRAY_PUSH(rows, const char *) = detail; + + SVN_ERR(parse_explanation_item(&item, detail, iterpool)); + + if (!item) + continue; /* Not parsable or not interesting */ + + if (item->search + && item->automatic_index) + { + warned = TRUE; + if (!is_slow_statement(i)) + { + warnings = svn_error_createf(SVN_ERR_TEST_FAILED, warnings, + "%s: " + "Creates a temporary index: %s\n", + wc_query_info[i][0], wc_queries[i]); + } + } + else if (item->search && item->primary_key) + { + /* Nice */ + } + else if (item->search + && ((item->expression_vars < 2 && is_node_table(item->table)) + || (item->expression_vars < 1)) + && !is_result_table(item->table)) + { + if (in_list(primary_key_statements, i)) + { + /* Reported as primary key index usage in Sqlite 3.7, + as table scan in 3.8+, while the execution plan is + identical: read first record from table */ + } + else if (!is_slow_statement(i)) + { + warned = TRUE; + warnings = svn_error_createf(SVN_ERR_TEST_FAILED, warnings, + "%s: " + "Uses %s with only %d index component: (%s)\n%s", + wc_query_info[i][0], item->table, + item->expression_vars, item->expressions, + wc_queries[i]); + } + else + warned = TRUE; + } + else if (item->search && !item->index) + { + warned = TRUE; + if (!is_slow_statement(i)) + warnings = svn_error_createf(SVN_ERR_TEST_FAILED, warnings, + "%s: " + "Query on %s doesn't use an index:\n%s", + wc_query_info[i][0], item->table, wc_queries[i]); + } + else if (item->scan && !is_result_table(item->table)) + { + warned = TRUE; + if (!is_slow_statement(i)) + warnings = svn_error_createf(SVN_ERR_TEST_FAILED, warnings, + "Query %s: " + "Performs scan on %s:\n%s", + wc_query_info[i][0], item->table, wc_queries[i]); + } + else if (item->create_btree) + { + warned = TRUE; + if (!is_slow_statement(i)) + warnings = svn_error_createf(SVN_ERR_TEST_FAILED, warnings, + "Query %s: Creates a temporary B-TREE:\n%s", + wc_query_info[i][0], wc_queries[i]); + } + } + SQLITE_ERR(sqlite3_reset(stmt)); + SQLITE_ERR(sqlite3_finalize(stmt)); + + if (!warned && is_slow_statement(i)) + { + printf("DBG: Expected %s to be reported as slow, but it wasn't\n", + wc_query_info[i][0]); + } + + if (rows && warned != is_slow_statement(i)) + { + int w; + svn_error_t *info = NULL; + for (w = rows->nelts-1; w >= 0; w--) + { + if (warned) + info = svn_error_createf(SVN_ERR_SQLITE_CONSTRAINT, info, + "|%s", APR_ARRAY_IDX(rows, w, + const char*)); + else + printf("|%s\n", APR_ARRAY_IDX(rows, w, const char*)); + } + + warnings = svn_error_compose_create(warnings, info); + } + } + SQLITE_ERR(sqlite3_close(sdb)); /* Close the DB if ok; otherwise leaked */ + + return warnings; +} + +/* Helper to verify a bit of data in the sqlite3 statistics */ +static int +parse_stat_data(const char *stat) +{ + int n = 0; + apr_int64_t last = APR_INT64_MAX; + while (*stat) + { + apr_int64_t v; + char *next; + + if (*stat < '0' || *stat > '9') + return -2; + + errno = 0; + v = apr_strtoi64(stat, &next, 10); + + /* All numbers specify the average number of rows + with the same values in all columns left of it, + so the value must be >= 1 and lower than or equal + to all previous seen numbers */ + if (v <= 0 || (v > last) || (errno != 0)) + return -1; + + last = v; + + n++; + stat = next; + + if (*stat == ' ') + stat++; + } + + return n; +} + +static svn_error_t * +test_schema_statistics(apr_pool_t *scratch_pool) +{ + sqlite3 *sdb; + sqlite3_stmt *stmt; + + SVN_ERR(create_memory_db(&sdb, scratch_pool)); + + SQLITE_ERR( + sqlite3_exec(sdb, + "CREATE TABLE shadow_stat1(tbl TEXT, idx TEXT, stat TEXT)", + NULL, NULL, NULL)); + + SQLITE_ERR( + sqlite3_exec(sdb, + "INSERT INTO shadow_stat1 (tbl, idx, stat) " + "SELECT tbl, idx, stat FROM sqlite_stat1", + NULL, NULL, NULL)); + + SQLITE_ERR( + sqlite3_exec(sdb, + "DROP TABLE sqlite_stat1", + NULL, NULL, NULL)); + + /* Insert statement to give index at least 1 record */ + SQLITE_ERR( + sqlite3_exec(sdb, + "INSERT INTO nodes (wc_id, local_relpath, op_depth," + " presence, kind) " + "VALUES (1, '', 0, 'normal', 'dir')", + NULL, NULL, NULL)); + + SQLITE_ERR( + sqlite3_exec(sdb, + "INSERT INTO actual_node (wc_id, local_relpath) " + "VALUES (1, '')", + NULL, NULL, NULL)); + + SQLITE_ERR( + sqlite3_exec(sdb, + "INSERT INTO lock (repos_id, repos_relpath, lock_token) " + "VALUES (1, '', '')", + NULL, NULL, NULL)); + + /* These are currently not necessary for query optimization, but it's better + to tell Sqlite how we intend to use this table anyway */ + SQLITE_ERR( + sqlite3_exec(sdb, + "INSERT INTO wc_lock (wc_id, local_dir_relpath) " + "VALUES (1, '')", + NULL, NULL, NULL)); + + SQLITE_ERR( + sqlite3_exec(sdb, + "INSERT INTO WORK_QUEUE (work) " + "VALUES ('')", + NULL, NULL, NULL)); + + SQLITE_ERR( + sqlite3_exec(sdb, + "ANALYZE", + NULL, NULL, NULL)); + + SQLITE_ERR( + sqlite3_prepare(sdb, "SELECT s.tbl, s.idx, s.stat, r.stat " + "FROM shadow_stat1 s " + "LEFT JOIN sqlite_stat1 r ON " + "s.tbl=r.tbl and s.idx=r.idx", + -1, &stmt, NULL)); + + while (sqlite3_step(stmt) == SQLITE_ROW) + { + const char *wc_stat = (const char*)sqlite3_column_text(stmt, 2); + const char *sqlite_stat = (const char*)sqlite3_column_text(stmt, 3); + + if (! sqlite_stat) + { + return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, + "Schema statistic failure:" + " Refering to unknown index '%s' on '%s'", + sqlite3_column_text(stmt, 1), + sqlite3_column_text(stmt, 0)); + } + + if (parse_stat_data(wc_stat) != parse_stat_data(sqlite_stat)) + { + return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, + "Schema statistic failure:" + " Column mismatch for '%s' on '%s'", + sqlite3_column_text(stmt, 1), + sqlite3_column_text(stmt, 0)); + } + } + + SQLITE_ERR(sqlite3_reset(stmt)); + SQLITE_ERR(sqlite3_finalize(stmt)); + + SQLITE_ERR(sqlite3_close(sdb)); /* Close the DB if ok; otherwise leaked */ + + return SVN_NO_ERROR; +} + +struct svn_test_descriptor_t test_funcs[] = + { + SVN_TEST_NULL, + SVN_TEST_PASS2(test_sqlite_version, + "sqlite up-to-date"), + SVN_TEST_PASS2(test_parsable, + "queries are parsable"), + SVN_TEST_PASS2(test_query_expectations, + "test query expectations"), + SVN_TEST_PASS2(test_schema_statistics, + "test schema statistics"), + SVN_TEST_NULL + }; |