diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6f72a7f3..e4cc8978 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,5 @@
+* Optimized text_get
+
# Clixon Changelog
## 3.10.0 (Upcoming)
diff --git a/datastore/clixon_xmldb_text.c b/datastore/clixon_xmldb_text.c
index 5daa6ded..cb8af752 100644
--- a/datastore/clixon_xmldb_text.c
+++ b/datastore/clixon_xmldb_text.c
@@ -95,7 +95,7 @@ struct text_handle {
/* Struct per database in hash */
struct db_element{
int de_pid;
- cxobj *de_xml;
+ cxobj *de_xml; /* cache */
};
/*! Check struct magic number for sanity checks
@@ -352,85 +352,316 @@ singleconfigroot(cxobj *xt,
return retval;
}
-/*! Given XML tree x0 with marked nodes, copy marked nodes to new tree x1
- * Two marks are used: XML_FLAG_MARK and XML_FLAG_CHANGE
- *
- * The algorithm works as following:
- * (1) Copy individual nodes marked with XML_FLAG_CHANGE
- * until nodes marked with XML_FLAG_MARK are reached, where
- * (2) the complete subtree of that node is copied.
- * (3) Special case: key nodes in lists are copied if any node in list is marked
- */
-static int
-xml_copy_marked(cxobj *x0,
- cxobj *x1)
+
+int
+text_get_nocache(struct text_handle *th,
+ const char *db,
+ char *xpath,
+ int config,
+ cxobj **xtop)
{
- int retval = -1;
- int mark;
- cxobj *x;
- cxobj *xcopy;
- int iskey;
- yang_stmt *yt;
- char *name;
+ int retval = -1;
+ char *dbfile = NULL;
+ yang_spec *yspec;
+ cxobj *xt = NULL;
+ cxobj *x;
+ int fd = -1;
+ cxobj **xvec = NULL;
+ size_t xlen;
+ int i;
- assert(x0 && x1);
- yt = xml_spec(x0); /* can be null */
- /* Copy all attributes */
- x = NULL;
- while ((x = xml_child_each(x0, x, CX_ATTR)) != NULL) {
- name = xml_name(x);
- if ((xcopy = xml_new(name, x1, xml_spec(x))) == NULL)
- goto done;
- if (xml_copy(x, xcopy) < 0)
- goto done;
+ if ((yspec = th->th_yangspec) == NULL){
+ clicon_err(OE_YANG, ENOENT, "No yang spec");
+ goto done;
}
- /* Go through children to detect any marked nodes:
- * (3) Special case: key nodes in lists are copied if any
- * node in list is marked
+ if (text_db2file(th, db, &dbfile) < 0)
+ goto done;
+ if (dbfile==NULL){
+ clicon_err(OE_XML, 0, "dbfile NULL");
+ goto done;
+ }
+ if ((fd = open(dbfile, O_RDONLY)) < 0){
+ clicon_err(OE_UNIX, errno, "open(%s)", dbfile);
+ goto done;
+ }
+ /* Parse file into XML tree */
+ if (strcmp(th->th_format,"json")==0){
+ if ((json_parse_file(fd, yspec, &xt)) < 0)
+ goto done;
+ }
+ else if ((xml_parse_file(fd, "", yspec, &xt)) < 0)
+ goto done;
+ /* Always assert a top-level called "config".
+ To ensure that, deal with two cases:
+ 1. File is empty -> rename top-level to "config" */
+ if (xml_child_nr(xt) == 0){
+ if (xml_name_set(xt, "config") < 0)
+ goto done;
+ }
+ /* 2. File is not empty ... -> replace root */
+ else{
+ /* There should only be one element and called config */
+ if (singleconfigroot(xt, &xt) < 0)
+ goto done;
+ }
+ /* Here xt looks like: ... */
+ /* Given the xpath, return a vector of matches in xvec */
+ if (xpath_vec(xt, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
+ goto done;
+
+ /* If vectors are specified then mark the nodes found with all ancestors
+ * and filter out everything else,
+ * otherwise return complete tree.
*/
- mark = 0;
- x = NULL;
- while ((x = xml_child_each(x0, x, CX_ELMNT)) != NULL) {
- if (xml_flag(x, XML_FLAG_MARK|XML_FLAG_CHANGE)){
- mark++;
- break;
- }
- }
- x = NULL;
- while ((x = xml_child_each(x0, x, CX_ELMNT)) != NULL) {
- name = xml_name(x);
- if (xml_flag(x, XML_FLAG_MARK)){
- /* (2) the complete subtree of that node is copied. */
- if ((xcopy = xml_new(name, x1, xml_spec(x))) == NULL)
- goto done;
- if (xml_copy(x, xcopy) < 0)
- goto done;
- continue;
- }
- if (xml_flag(x, XML_FLAG_CHANGE)){
- /* Copy individual nodes marked with XML_FLAG_CHANGE */
- if ((xcopy = xml_new(name, x1, xml_spec(x))) == NULL)
- goto done;
- if (xml_copy_marked(x, xcopy) < 0) /* */
- goto done;
- }
- /* (3) Special case: key nodes in lists are copied if any
- * node in list is marked */
- if (mark && yt && yt->ys_keyword == Y_LIST){
- /* XXX: I think yang_key_match is suboptimal here */
- if ((iskey = yang_key_match((yang_node*)yt, name)) < 0)
- goto done;
- if (iskey){
- if ((xcopy = xml_new(name, x1, xml_spec(x))) == NULL)
- goto done;
- if (xml_copy(x, xcopy) < 0)
- goto done;
- }
+ if (xvec != NULL)
+ for (i=0; i1)
+ clicon_xml2file(stderr, xt, 0, 1);
+ *xtop = xt;
+ xt = NULL;
retval = 0;
done:
+ if (xt)
+ xml_free(xt);
+ if (dbfile)
+ free(dbfile);
+ if (xvec)
+ free(xvec);
+ if (fd != -1)
+ close(fd);
+ return retval;
+}
+
+
+/*! Given a tree rooted in x0t and a point x0, create a duplicate path in x1t
+ * Given a tree rooted in x0t and a point in the tree x0,
+ * create a duplicate rooted in x1t.
+ * x0t -- x -- x -- x0
+ * x1t
+ */
+int
+create_path2root(cxobj *x0,
+ cxobj *x0t,
+ cxobj *x1t,
+ cxobj **x1r)
+{
+ int retval = -1;
+ cxobj *x0p;
+ cxobj *x1p = NULL;
+ cxobj *x1 = NULL;
+ char *name;
+
+ name = xml_name(x0);
+ x0p = xml_parent(x0);
+ if (x0t != x0p){
+ if (create_path2root(x0p, x0t, x1t, &x1p) < 0)
+ goto done;
+ if ((x1 = xml_find_type(x1p, NULL, name, CX_ELMNT)) == NULL){
+ if ((x1 = xml_new(name, x1p, xml_spec(x0))) == NULL)
+ goto done;
+ if (xml_copy_one(x0, x1) < 0)
+ goto done;
+ }
+ }
+ else /* root of tree */
+ x1 = x1t;
+ assert(x1);
+ *x1r = x1;
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*!
+ * @param[in] config If set only configuration data, else also state
+ */
+int
+xml_copy_config(cxobj *x0,
+ cxobj *x1,
+ int config)
+{
+ int retval = -1;
+ cxobj *x;
+ cxobj *xcopy;
+ yang_stmt *ys;
+
+ if (xml_copy_one(x0, x1) <0)
+ goto done;
+ x = NULL;
+ while ((x = xml_child_each(x0, x, -1)) != NULL) {
+ if (config){
+ if ((ys = (yang_stmt*)xml_spec(x)) != NULL){
+ if (!yang_config(ys))
+ continue; /* skip */
+ }
+ }
+ if ((xcopy = xml_new(xml_name(x), x1, xml_spec(x))) == NULL)
+ goto done;
+ if (xml_copy(x, xcopy) < 0) /* recursion */
+ goto done;
+ }
+ retval = 0;
+ done:
+ return retval;
+}
+
+
+/*! Get content of database using xpath. return a set of matching sub-trees
+ * The function returns a minimal tree that includes all sub-trees that match
+ * xpath.
+ * This is a clixon datastore plugin of the the xmldb api
+ * @param[in] h Clicon handle
+ * @param[in] dbname Name of database to search in (filename including dir path
+ * @param[in] xpath String with XPATH syntax. or NULL for all
+ * @param[in] config If set only configuration data, else also state
+ * @param[out] xret Single return XML tree. Free with xml_free()
+ * @retval 0 OK
+ * @retval -1 Error
+ * @see xmldb_get the generic API function
+ */
+static int
+text_get_cache(struct text_handle *th,
+ const char *db,
+ char *xpath,
+ int config,
+ cxobj **xtop)
+{
+ int retval = -1;
+ char *dbfile = NULL;
+ yang_spec *yspec;
+ cxobj *x0t = NULL; /* (cached) top of tree */
+ cxobj *x;
+ int fd = -1;
+ cxobj **xvec = NULL;
+ size_t xlen;
+ int i;
+ struct db_element *de = NULL;
+ cxobj *x1t = NULL;
+ cxobj *x1;
+ struct db_element de0 = {0,};
+
+ if ((yspec = th->th_yangspec) == NULL){
+ clicon_err(OE_YANG, ENOENT, "No yang spec");
+ goto done;
+ }
+ de = hash_value(th->th_dbs, db, NULL);
+ if (de == NULL || de->de_xml == NULL){ /* Cache miss, read XML from file */
+ if (text_db2file(th, db, &dbfile) < 0)
+ goto done;
+ if (dbfile==NULL){
+ clicon_err(OE_XML, 0, "dbfile NULL");
+ goto done;
+ }
+ if ((fd = open(dbfile, O_RDONLY)) < 0){
+ clicon_err(OE_UNIX, errno, "open(%s)", dbfile);
+ goto done;
+ }
+ /* Parse file into XML tree */
+ if (strcmp(th->th_format,"json")==0){
+ if ((json_parse_file(fd, yspec, &x0t)) < 0)
+ goto done;
+ }
+ else if ((xml_parse_file(fd, "", yspec, &x0t)) < 0)
+ goto done;
+ /* Always assert a top-level called "config".
+ To ensure that, deal with two cases:
+ 1. File is empty -> rename top-level to "config" */
+ if (xml_child_nr(x0t) == 0){
+ if (xml_name_set(x0t, "config") < 0)
+ goto done;
+ }
+ /* 2. File is not empty ... -> replace root */
+ else{
+ /* There should only be one element and called config */
+ if (singleconfigroot(x0t, &x0t) < 0)
+ goto done;
+ }
+ /* default values? */
+ if (xml_apply(x0t, CX_ELMNT, xml_default, NULL) < 0)
+ goto done;
+ de0.de_xml = x0t;
+ hash_add(th->th_dbs, db, &de0, sizeof(de0));
+ } /* x0t == NULL */
+ else
+ x0t = de->de_xml;
+
+ /* Here x0t looks like: ... */
+ /* Given the xpath, return a vector of matches in xvec
+ * Can we do everything in one go?
+ * 0) Make a new tree
+ * 1) make the xpath check
+ * 2) iterate thru matches (maybe this can be folded into the xpath_vec?)
+ * a) for every node that is found, copy to new tree
+ * b) if config dont dont state data
+ */
+ /* Make new tree by copying top-of-tree from x0t to x1t */
+ if ((x1t = xml_new(xml_name(x0t), NULL, xml_spec(x0t))) == NULL)
+ goto done;
+ if (xml_copy_one(x0t, x1t) < 0)
+ goto done;
+ /* Get matches */
+ if (xpath_vec(x0t, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
+ goto done;
+
+ /* Iterate through matches */
+
+ /* If vectors are specified then mark the nodes found with all ancestors
+ * and filter out everything else,
+ * otherwise return complete tree.
+ */
+ for (i=0; i1)
+ clicon_xml2file(stderr, x1t, 0, 1);
+ *xtop = x1t;
+ retval = 0;
+ done:
+ if (dbfile)
+ free(dbfile);
+ if (xvec)
+ free(xvec);
+ if (fd != -1)
+ close(fd);
return retval;
}
@@ -454,146 +685,15 @@ text_get(xmldb_handle xh,
int config,
cxobj **xtop)
{
- int retval = -1;
- char *dbfile = NULL;
- yang_spec *yspec;
- cxobj *xt = NULL;
- cxobj *x;
- int fd = -1;
- cxobj **xvec = NULL;
- size_t xlen;
- int i;
struct text_handle *th = handle(xh);
- struct db_element *de = NULL;
- if ((yspec = th->th_yangspec) == NULL){
- clicon_err(OE_YANG, ENOENT, "No yang spec");
- goto done;
- }
- if (th->th_cache){
- if ((de = hash_value(th->th_dbs, db, NULL)) != NULL)
- xt = de->de_xml;
- }
- if (xt == NULL){
- if (text_db2file(th, db, &dbfile) < 0)
- goto done;
- if (dbfile==NULL){
- clicon_err(OE_XML, 0, "dbfile NULL");
- goto done;
- }
- if ((fd = open(dbfile, O_RDONLY)) < 0){
- clicon_err(OE_UNIX, errno, "open(%s)", dbfile);
- goto done;
- }
- /* Parse file into XML tree */
- if (strcmp(th->th_format,"json")==0){
- if ((json_parse_file(fd, yspec, &xt)) < 0)
- goto done;
- }
- else if ((xml_parse_file(fd, "", yspec, &xt)) < 0)
- goto done;
- /* Always assert a top-level called "config".
- To ensure that, deal with two cases:
- 1. File is empty -> rename top-level to "config" */
- if (xml_child_nr(xt) == 0){
- if (xml_name_set(xt, "config") < 0)
- goto done;
- }
- /* 2. File is not empty ... -> replace root */
- else{
- /* There should only be one element and called config */
- if (singleconfigroot(xt, &xt) < 0)
- goto done;
- }
- /* XXX: should we validate file if read from disk?
- * Argument against: we may want to have a semantically wrong file and wish
- * to edit?
- */
- } /* xt == NULL */
- /* Here xt looks like: ... */
- if (xpath_vec(xt, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
- goto done;
-
- /* If vectors are specified then mark the nodes found with all ancestors
- * and filter out everything else,
- * otherwise return complete tree.
- */
- if (xvec != NULL)
- for (i=0; ith_cache)
- xml_apply_ancestor(x, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE);
- }
-
-
- if (th->th_cache){
- /* Copy the matching parts of the (relevant) XML tree.
- * If cache was NULL, also write to datastore cache
- */
- cxobj *x1;
- struct db_element de0 = {0,};
-
- if (de != NULL)
- de0 = *de;
-
- x1 = xml_new(xml_name(xt), NULL, xml_spec(xt));
- /* Copy everything that is marked */
- if (xml_copy_marked(xt, x1) < 0)
- goto done;
- if (xml_apply(xt, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)) < 0)
- goto done;
- if (xml_apply(x1, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)) < 0)
- goto done;
- if (de0.de_xml == NULL){
- de0.de_xml = xt;
- hash_add(th->th_dbs, db, &de0, sizeof(de0));
- }
- xt = x1;
- }
- else{
- /* Remove everything that is not marked */
- if (!xml_flag(xt, XML_FLAG_MARK))
- if (xml_tree_prune_flagged_sub(xt, XML_FLAG_MARK, 1, NULL) < 0)
- goto done;
- }
- /* reset flag */
- if (xml_apply(xt, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
- goto done;
-
- /* filter out state (operations) data if config not set. Mark all nodes
- that are not config data */
- if (config){
- if (xml_apply(xt, CX_ELMNT, xml_non_config_data, NULL) < 0)
- goto done;
- /* Remove (prune) nodes that are marked (that does not pass test) */
- if (xml_tree_prune_flagged(xt, XML_FLAG_MARK, 1) < 0)
- goto done;
- }
- /* Add default values (if not set) */
- if (xml_apply(xt, CX_ELMNT, xml_default, NULL) < 0)
- goto done;
-#if 0 /* debug */
- if (xml_apply0(xt, -1, xml_sort_verify, NULL) < 0)
- clicon_log(LOG_NOTICE, "%s: sort verify failed #2", __FUNCTION__);
-#endif
- if (debug>1)
- clicon_xml2file(stderr, xt, 0, 1);
- *xtop = xt;
- xt = NULL;
- retval = 0;
- done:
- if (xt)
- xml_free(xt);
- if (dbfile)
- free(dbfile);
- if (xvec)
- free(xvec);
- if (fd != -1)
- close(fd);
- return retval;
+ if (th->th_cache)
+ return text_get_cache(th, db, xpath, config, xtop);
+ else
+ return text_get_nocache(th, db, xpath, config, xtop);
}
+
/*! Modify a base tree x0 with x1 with yang spec y according to operation op
* @param[in] th text handle
* @param[in] x0 Base xml tree (can be NULL in add scenarios)
diff --git a/doc/startup.md b/doc/startup.md
index 69b34ae2..99f5f191 100644
--- a/doc/startup.md
+++ b/doc/startup.md
@@ -8,7 +8,7 @@
* [Extra XML](#extra-xml)
* [Startup status](#startup-status)
* [Failsafe mode](#failsafe-mode)
- * [FLowcharts](#flowcharts)
+ * [Flowcharts](#flowcharts)
* [Thanks](#thanks)
* [References](#references)