Index: subversion/libsvn_wc/props.c =================================================================== --- subversion/libsvn_wc/props.c (revision 1068618) +++ subversion/libsvn_wc/props.c (working copy) @@ -1711,6 +1711,7 @@ SVN_ERR(svn_wc__db_read_props_of_immediates(b->db, local_abspath, b->receiver_func, b->receiver_baton, + NULL, NULL, scratch_pool)); return SVN_NO_ERROR; } @@ -1725,47 +1726,41 @@ void *cancel_baton, apr_pool_t *scratch_pool) { - struct read_dir_props_baton read_dir_baton; - - if (depth <= svn_depth_immediates) + switch (depth) { - apr_hash_t *props; + case svn_depth_empty: + { + apr_hash_t *props; - SVN_ERR(svn_wc__db_read_props(&props, wc_ctx->db, local_abspath, - scratch_pool, scratch_pool)); - if (receiver_func && props && apr_hash_count(props) > 0) - SVN_ERR((*receiver_func)(receiver_baton, local_abspath, props, - scratch_pool)); - if (depth == svn_depth_empty) - return SVN_NO_ERROR; - } - - if (depth == svn_depth_files) - { + SVN_ERR(svn_wc__db_read_props(&props, wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + if (receiver_func && props && apr_hash_count(props) > 0) + SVN_ERR((*receiver_func)(receiver_baton, local_abspath, props, + scratch_pool)); + } + break; + case svn_depth_files: SVN_ERR(svn_wc__db_read_props_of_files(wc_ctx->db, local_abspath, receiver_func, receiver_baton, + cancel_func, cancel_baton, scratch_pool)); - return SVN_NO_ERROR; - } - - if (depth == svn_depth_immediates) - { + break; + case svn_depth_immediates: SVN_ERR(svn_wc__db_read_props_of_immediates(wc_ctx->db, local_abspath, - receiver_func, - receiver_baton, + receiver_func, receiver_baton, + cancel_func, cancel_baton, scratch_pool)); - return SVN_NO_ERROR; + break; + case svn_depth_infinity: + SVN_ERR(svn_wc__db_read_props_recursive(wc_ctx->db, local_abspath, + receiver_func, receiver_baton, + cancel_func, cancel_baton, + scratch_pool)); + break; + default: + SVN_ERR_MALFUNCTION(); } - read_dir_baton.db = wc_ctx->db; - read_dir_baton.root_abspath = local_abspath; - read_dir_baton.receiver_func = receiver_func; - read_dir_baton.receiver_baton = receiver_baton; - - SVN_ERR(svn_wc__internal_walk_children(wc_ctx->db, local_abspath, FALSE, - read_dir_props, &read_dir_baton, - depth, cancel_func, cancel_baton, - scratch_pool)); return SVN_NO_ERROR; } Index: subversion/libsvn_wc/wc-queries.sql =================================================================== --- subversion/libsvn_wc/wc-queries.sql (revision 1068618) +++ subversion/libsvn_wc/wc-queries.sql (working copy) @@ -736,7 +736,62 @@ UPDATE nodes SET checksum = ?4 WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3; +/* ------------------------------------------------------------------------- */ +/* PROOF OF CONCEPT: Complex queries for callback walks, caching results + in a temporary table. */ +-- STMT_CLEAR_NODE_PROPS_CACHE +DROP TABLE IF EXISTS temp__node_props_cache; + +-- STMT_CACHE_NODE_PROPS_RECURSIVE +CREATE TEMPORARY TABLE temp__node_props_cache AS + SELECT local_relpath, kind, properties FROM nodes_current + WHERE wc_id = ?1 + AND (?2 = '' OR local_relpath = ?2 OR local_relpath LIKE ?2 || '/%') + AND local_relpath NOT IN ( + SELECT local_relpath FROM actual_node WHERE wc_id = ?1) + AND (presence = 'normal' OR presence = 'incomplete'); +CREATE UNIQUE INDEX temp__node_props_cache_unique + ON temp__node_props_cache (local_relpath); + +-- STMT_CACHE_ACTUAL_PROPS_RECURSIVE +INSERT INTO temp__node_props_cache (local_relpath, kind, properties) + SELECT A.local_relpath, N.kind, A.properties + FROM actual_node AS A JOIN nodes_current AS N + ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath + AND (N.presence = 'normal' OR N.presence = 'incomplete') + WHERE A.wc_id = ?1 + AND (?2 = '' OR A.local_relpath = ?2 OR A.local_relpath LIKE ?2 || '/%') + AND A.local_relpath NOT IN + (SELECT local_relpath FROM temp__node_props_cache); + +-- STMT_CACHE_NODE_PROPS_OF_CHILDREN +CREATE TEMPORARY TABLE temp__node_props_cache AS + SELECT local_relpath, kind, properties FROM nodes_current + WHERE wc_id = ?1 + AND (local_relpath = ?2 OR parent_relpath = ?2) + AND local_relpath NOT IN ( + SELECT local_relpath FROM actual_node WHERE wc_id = ?1) + AND (presence = 'normal' OR presence = 'incomplete'); +CREATE UNIQUE INDEX temp__node_props_cache_unique + ON temp__node_props_cache (local_relpath); + +-- STMT_CACHE_ACTUAL_PROPS_OF_CHILDREN +INSERT INTO temp__node_props_cache (local_relpath, kind, properties) + SELECT A.local_relpath, N.kind, A.properties + FROM actual_node AS A JOIN nodes_current AS N + ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath + AND (N.presence = 'normal' OR N.presence = 'incomplete') + WHERE A.wc_id = ?1 + AND (A.local_relpath = ?2 OR A.parent_relpath = ?2) + AND A.local_relpath NOT IN + (SELECT local_relpath FROM temp__node_props_cache); + +-- STMT_SELECT_RELEVANT_PROPS_FROM_CACHE +SELECT local_relpath, kind, properties FROM temp__node_props_cache +ORDER BY local_relpath; + + /* ------------------------------------------------------------------------- */ /* Grab all the statements related to the schema. */ Index: subversion/libsvn_wc/wc-metadata.sql =================================================================== --- subversion/libsvn_wc/wc-metadata.sql (revision 1068618) +++ subversion/libsvn_wc/wc-metadata.sql (working copy) @@ -483,6 +483,16 @@ CREATE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, op_depth); +/* Many queries have to filter the nodes table to pick only that version + of each node with the highest (most "current") op_depth. This view + does the heavy lifting for such queries. */ +CREATE VIEW NODES_CURRENT AS + SELECT * FROM nodes + JOIN (SELECT wc_id, local_relpath, MAX(op_depth) AS op_depth FROM nodes + GROUP BY wc_id, local_relpath) AS filter + ON nodes.wc_id = filter.wc_id + AND nodes.local_relpath = filter.local_relpath + AND nodes.op_depth = filter.op_depth; -- STMT_CREATE_NODES_TRIGGERS @@ -595,7 +605,22 @@ PRAGMA user_version = 24; +/* ------------------------------------------------------------------------- */ +/* Format 25 introduces the NODES_CURRENT view. */ + +-- STMT_UPGRADE_TO_25 +DROP VIEW IF EXISTS NODES_CURRENT; +CREATE VIEW NODES_CURRENT AS + SELECT * FROM nodes + JOIN (SELECT wc_id, local_relpath, MAX(op_depth) AS op_depth FROM nodes + GROUP BY wc_id, local_relpath) AS filter + ON nodes.wc_id = filter.wc_id + AND nodes.local_relpath = filter.local_relpath + AND nodes.op_depth = filter.op_depth; + +PRAGMA user_version = 25; + /* ------------------------------------------------------------------------- */ /* Format YYY introduces new handling for conflict information. */ Index: subversion/libsvn_wc/wc_db.c =================================================================== --- subversion/libsvn_wc/wc_db.c (revision 1068618) +++ subversion/libsvn_wc/wc_db.c (working copy) @@ -5169,188 +5169,152 @@ return SVN_NO_ERROR; } -/* Parse a node's PROP_DATA (which is PROP_DATA_LEN bytes long) - * into a hash table keyed by property names and containing property values. - * - * If parsing succeeds, and the set of properties is not empty, - * add the hash table to PROPS_PER_CHILD, keyed by the absolute path - * of the node CHILD_RELPATH within the working copy at WCROOT_ABSPATH. - * - * If the set of properties is empty, and PROPS_PER_CHILD already contains - * an entry for the node, clear the entry. This facilitates overriding - * properties retrieved from the NODES table with empty sets of properties - * stored in the ACTUAL_NODE table. */ +/* Call RECEIVER_FUNC, passing RECEIVER_BATON, an absolute path, and + * a hash table mapping char * names onto svn_string_t * + * values for any properties of immediate or recursive child nodes of + * LOCAL_ABSPATH, the actual query being determined by STMT_IDX. + * If FILES_ONLY is true, only report properties for file child nodes. + * Check for cancellation between calls of RECEIVER_FUNC. + */ +typedef struct cache_props_baton_t +{ + int stmt_node_props_ndx; + int stmt_actual_props_ndx; + apr_int64_t wc_id; + const char *local_relpath; + svn_cancel_func_t cancel_func; + void *cancel_baton; +} cache_props_baton_t; + static svn_error_t * -maybe_add_child_props(apr_hash_t *props_per_child, - const char *prop_data, - apr_size_t prop_data_len, - const char *child_relpath, - const char *wcroot_abspath, - apr_pool_t *result_pool, +cache_props_recursive(void *cb_baton, + svn_sqlite__db_t *db, apr_pool_t *scratch_pool) { - const char *child_abspath; - apr_hash_t *props; - svn_skel_t *prop_skel; + cache_props_baton_t *baton = cb_baton; + svn_sqlite__stmt_t *stmt; - prop_skel = svn_skel__parse(prop_data, prop_data_len, scratch_pool); - if (svn_skel__list_length(prop_skel) == 0) - return SVN_NO_ERROR; + SVN_ERR(svn_sqlite__get_statement(&stmt, db, baton->stmt_node_props_ndx)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", baton->wc_id, baton->local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); - child_abspath = svn_dirent_join(wcroot_abspath, child_relpath, result_pool); - SVN_ERR(svn_skel__parse_proplist(&props, prop_skel, result_pool)); - if (apr_hash_count(props)) - apr_hash_set(props_per_child, child_abspath, APR_HASH_KEY_STRING, props); - else - apr_hash_set(props_per_child, child_abspath, APR_HASH_KEY_STRING, NULL); + if (baton->cancel_func) + SVN_ERR(baton->cancel_func(baton->cancel_baton)); + SVN_ERR(svn_sqlite__get_statement(&stmt, db, baton->stmt_actual_props_ndx)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", baton->wc_id, baton->local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); return SVN_NO_ERROR; } -/* Call RECEIVER_FUNC, passing RECEIVER_BATON, an absolute path, and - * a hash table mapping char * names onto svn_string_t * - * values for any properties of immediate child nodes of LOCAL_ABSPATH. - * If FILES_ONLY is true, only report properties for file child nodes. - */ static svn_error_t * -read_props_of_children(svn_wc__db_t *db, - const char *local_abspath, - svn_boolean_t files_only, - svn_wc__proplist_receiver_t receiver_func, - void *receiver_baton, - apr_pool_t *scratch_pool) +read_props_recursive(svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t files_only, + svn_boolean_t immediates_only, + svn_wc__proplist_receiver_t receiver_func, + void *receiver_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) { svn_wc__db_pdh_t *pdh; - const char *local_relpath; - const char *prev_child_relpath; svn_sqlite__stmt_t *stmt; + cache_props_baton_t baton; svn_boolean_t have_row; - apr_hash_t *props_per_child; - apr_hash_t *files; - apr_hash_t *not_present; - apr_hash_index_t *hi; + int row_number; apr_pool_t *iterpool; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); SVN_ERR_ASSERT(receiver_func); - SVN_ERR(svn_wc__db_pdh_parse_local_abspath(&pdh, &local_relpath, db, + SVN_ERR(svn_wc__db_pdh_parse_local_abspath(&pdh, &baton.local_relpath, db, local_abspath, svn_sqlite__mode_readwrite, scratch_pool, scratch_pool)); VERIFY_USABLE_PDH(pdh); - props_per_child = apr_hash_make(scratch_pool); - not_present = apr_hash_make(scratch_pool); - if (files_only) - files = apr_hash_make(scratch_pool); + SVN_ERR(svn_sqlite__exec_statements(pdh->wcroot->sdb, + STMT_CLEAR_NODE_PROPS_CACHE)); + + if (immediates_only) + { + baton.stmt_node_props_ndx = STMT_CACHE_NODE_PROPS_OF_CHILDREN; + baton.stmt_actual_props_ndx = STMT_CACHE_ACTUAL_PROPS_OF_CHILDREN; + } else - files = NULL; + { + baton.stmt_node_props_ndx = STMT_CACHE_NODE_PROPS_RECURSIVE; + baton.stmt_actual_props_ndx = STMT_CACHE_ACTUAL_PROPS_RECURSIVE; + } + baton.wc_id = pdh->wcroot->wc_id; + baton.cancel_func = cancel_func; + baton.cancel_baton = cancel_baton; + SVN_ERR(svn_sqlite__with_transaction(pdh->wcroot->sdb, + cache_props_recursive, + &baton, scratch_pool)); + iterpool = svn_pool_create(scratch_pool); + SVN_ERR(svn_sqlite__get_statement(&stmt, pdh->wcroot->sdb, - STMT_SELECT_NODE_PROPS_OF_CHILDREN)); - SVN_ERR(svn_sqlite__bindf(stmt, "is", pdh->wcroot->wc_id, local_relpath)); + STMT_SELECT_RELEVANT_PROPS_FROM_CACHE)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); - prev_child_relpath = NULL; - while (have_row) + for (row_number = 0; have_row; ++row_number) { - svn_wc__db_status_t child_presence; - const char *child_relpath; const char *prop_data; apr_size_t len; - child_relpath = svn_sqlite__column_text(stmt, 2, scratch_pool); - - if (prev_child_relpath && strcmp(child_relpath, prev_child_relpath) == 0) + if (files_only && row_number > 0) { - /* Same child, but lower op_depth -- skip this row. */ - SVN_ERR(svn_sqlite__step(&have_row, stmt)); - continue; - } - prev_child_relpath = child_relpath; + svn_wc__db_kind_t child_kind; - child_presence = svn_sqlite__column_token(stmt, 1, presence_map); - if (child_presence != svn_wc__db_status_normal) - { - apr_hash_set(not_present, child_relpath, APR_HASH_KEY_STRING, ""); - SVN_ERR(svn_sqlite__step(&have_row, stmt)); - continue; - } - - prop_data = svn_sqlite__column_blob(stmt, 0, &len, NULL); - if (prop_data) - { - if (files_only) + child_kind = svn_sqlite__column_token(stmt, 1, kind_map); + if (child_kind != svn_wc__db_kind_file && + child_kind != svn_wc__db_kind_symlink) { - svn_wc__db_kind_t child_kind; - - child_kind = svn_sqlite__column_token(stmt, 3, kind_map); - if (child_kind != svn_wc__db_kind_file && - child_kind != svn_wc__db_kind_symlink) - { - SVN_ERR(svn_sqlite__step(&have_row, stmt)); - continue; - } - apr_hash_set(files, child_relpath, APR_HASH_KEY_STRING, NULL); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + continue; } - - SVN_ERR(maybe_add_child_props(props_per_child, prop_data, len, - child_relpath, pdh->wcroot->abspath, - scratch_pool, scratch_pool)); } - SVN_ERR(svn_sqlite__step(&have_row, stmt)); - } + svn_pool_clear(iterpool); - SVN_ERR(svn_sqlite__reset(stmt)); + /* See if someone wants to cancel this operation. */ + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); - SVN_ERR(svn_sqlite__get_statement(&stmt, pdh->wcroot->sdb, - STMT_SELECT_ACTUAL_PROPS_OF_CHILDREN)); - SVN_ERR(svn_sqlite__bindf(stmt, "is", pdh->wcroot->wc_id, local_relpath)); - SVN_ERR(svn_sqlite__step(&have_row, stmt)); - while (have_row) - { - const char *child_relpath; - const char *prop_data; - apr_size_t len; - - prop_data = svn_sqlite__column_blob(stmt, 0, &len, NULL); + prop_data = svn_sqlite__column_blob(stmt, 2, &len, NULL); if (prop_data) { - child_relpath = svn_sqlite__column_text(stmt, 1, scratch_pool); + svn_skel_t *prop_skel; - if (apr_hash_get(not_present, child_relpath, APR_HASH_KEY_STRING) || - (files_only && - apr_hash_get(files, child_relpath, APR_HASH_KEY_STRING) == NULL)) + prop_skel = svn_skel__parse(prop_data, len, iterpool); + if (svn_skel__list_length(prop_skel) != 0) { - SVN_ERR(svn_sqlite__step(&have_row, stmt)); - continue; + const char *child_relpath; + const char *child_abspath; + apr_hash_t *props; + + child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + child_abspath = svn_dirent_join(pdh->wcroot->abspath, + child_relpath, iterpool); + SVN_ERR(svn_skel__parse_proplist(&props, prop_skel, iterpool)); + if (receiver_func && apr_hash_count(props) != 0) + { + SVN_ERR((*receiver_func)(receiver_baton, + child_abspath, props, + iterpool)); + } } - SVN_ERR(maybe_add_child_props(props_per_child, prop_data, len, - child_relpath, pdh->wcroot->abspath, - scratch_pool, scratch_pool)); } SVN_ERR(svn_sqlite__step(&have_row, stmt)); } SVN_ERR(svn_sqlite__reset(stmt)); - - iterpool = svn_pool_create(scratch_pool); - for (hi = apr_hash_first(scratch_pool, props_per_child); - hi; - hi = apr_hash_next(hi)) - { - const char *child_abspath = svn__apr_hash_index_key(hi); - apr_hash_t *child_props = svn__apr_hash_index_val(hi); - - svn_pool_clear(iterpool); - - if (child_props) - SVN_ERR((*receiver_func)(receiver_baton, child_abspath, child_props, - iterpool)); - } svn_pool_destroy(iterpool); + SVN_ERR(svn_sqlite__exec_statements(pdh->wcroot->sdb, + STMT_CLEAR_NODE_PROPS_CACHE)); return SVN_NO_ERROR; } @@ -5360,11 +5324,15 @@ const char *local_abspath, svn_wc__proplist_receiver_t receiver_func, void *receiver_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, apr_pool_t *scratch_pool) { - return svn_error_return(read_props_of_children(db, local_abspath, TRUE, - receiver_func, receiver_baton, - scratch_pool)); + SVN_ERR(read_props_recursive(db, local_abspath, TRUE, TRUE, + receiver_func, receiver_baton, + cancel_func, cancel_baton, + scratch_pool)); + return SVN_NO_ERROR; } svn_error_t * @@ -5372,13 +5340,32 @@ const char *local_abspath, svn_wc__proplist_receiver_t receiver_func, void *receiver_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, apr_pool_t *scratch_pool) { - return svn_error_return(read_props_of_children(db, local_abspath, FALSE, - receiver_func, receiver_baton, - scratch_pool)); + SVN_ERR(read_props_recursive(db, local_abspath, FALSE, TRUE, + receiver_func, receiver_baton, + cancel_func, cancel_baton, + scratch_pool)); + return SVN_NO_ERROR; } +svn_error_t * +svn_wc__db_read_props_recursive(svn_wc__db_t *db, + const char *local_abspath, + svn_wc__proplist_receiver_t receiver_func, + void *receiver_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + SVN_ERR(read_props_recursive(db, local_abspath, FALSE, FALSE, + receiver_func, receiver_baton, + cancel_func, cancel_baton, + scratch_pool)); + return SVN_NO_ERROR; +} static svn_error_t * db_read_pristine_props(apr_hash_t **props, Index: subversion/libsvn_wc/wc_db.h =================================================================== --- subversion/libsvn_wc/wc_db.h (revision 1068618) +++ subversion/libsvn_wc/wc_db.h (working copy) @@ -1580,9 +1580,10 @@ svn_error_t * svn_wc__db_read_props_of_files(svn_wc__db_t *db, const char *local_abspath, - svn_wc__proplist_receiver_t - receiver_func, + svn_wc__proplist_receiver_t receiver_func, void *receiver_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, apr_pool_t *scratch_pool); /* Call RECEIVER_FUNC, passing RECEIVER_BATON, an absolute path, and @@ -1592,11 +1593,25 @@ svn_error_t * svn_wc__db_read_props_of_immediates(svn_wc__db_t *db, const char *local_abspath, - svn_wc__proplist_receiver_t - receiver_func, + svn_wc__proplist_receiver_t receiver_func, void *receiver_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, apr_pool_t *scratch_pool); +/* Call RECEIVER_FUNC, passing RECEIVER_BATON, an absolute path, and + * a hash table mapping char * names onto svn_string_t * + * values for any properties of all (recursive) child nodes of LOCAL_ABSPATH. + */ +svn_error_t * +svn_wc__db_read_props_recursive(svn_wc__db_t *db, + const char *local_abspath, + svn_wc__proplist_receiver_t receiver_func, + void *receiver_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + /* Set *PROPS to the properties of the node LOCAL_ABSPATH in the WORKING tree (looking through to the BASE tree as required). Index: subversion/libsvn_wc/upgrade.c =================================================================== --- subversion/libsvn_wc/upgrade.c (revision 1068618) +++ subversion/libsvn_wc/upgrade.c (working copy) @@ -1137,7 +1137,14 @@ return SVN_NO_ERROR; } +static svn_error_t * +bump_to_25(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_25)); + return SVN_NO_ERROR; +} + struct upgrade_data_t { svn_sqlite__db_t *sdb; const char *root_abspath; @@ -1404,6 +1411,12 @@ *result_format = 24; /* FALLTHROUGH */ + case 24: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_25, &bb, + scratch_pool)); + *result_format = 25; + /* FALLTHROUGH */ + /* ### future bumps go here. */ #if 0 case XXX-1: