mirror of https://github.com/tstack/lnav
[sqlite] add an xml/xpath extension
parent
9ed3a80326
commit
4ca6fd7bfd
@ -0,0 +1,9 @@
|
||||
|
||||
noinst_LIBRARIES = libpugixml.a
|
||||
|
||||
noinst_HEADERS = \
|
||||
pugiconfig.hpp \
|
||||
pugixml.hpp
|
||||
|
||||
libpugixml_a_SOURCES = \
|
||||
pugixml.cpp
|
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* pugixml parser - version 1.11
|
||||
* --------------------------------------------------------
|
||||
* Copyright (C) 2006-2020, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
|
||||
* Report bugs and download new versions at https://pugixml.org/
|
||||
*
|
||||
* This library is distributed under the MIT License. See notice at the end
|
||||
* of this file.
|
||||
*
|
||||
* This work is based on the pugxml parser, which is:
|
||||
* Copyright (C) 2003, by Kristen Wegner (kristen@tima.net)
|
||||
*/
|
||||
|
||||
#ifndef HEADER_PUGICONFIG_HPP
|
||||
#define HEADER_PUGICONFIG_HPP
|
||||
|
||||
// Uncomment this to enable wchar_t mode
|
||||
// #define PUGIXML_WCHAR_MODE
|
||||
|
||||
// Uncomment this to enable compact mode
|
||||
// #define PUGIXML_COMPACT
|
||||
|
||||
// Uncomment this to disable XPath
|
||||
// #define PUGIXML_NO_XPATH
|
||||
|
||||
// Uncomment this to disable STL
|
||||
// #define PUGIXML_NO_STL
|
||||
|
||||
// Uncomment this to disable exceptions
|
||||
#define PUGIXML_NO_EXCEPTIONS
|
||||
|
||||
// Set this to control attributes for public classes/functions, i.e.:
|
||||
// #define PUGIXML_API __declspec(dllexport) // to export all public symbols from DLL
|
||||
// #define PUGIXML_CLASS __declspec(dllimport) // to import all classes from DLL
|
||||
// #define PUGIXML_FUNCTION __fastcall // to set calling conventions to all public functions to fastcall
|
||||
// In absence of PUGIXML_CLASS/PUGIXML_FUNCTION definitions PUGIXML_API is used instead
|
||||
|
||||
// Tune these constants to adjust memory-related behavior
|
||||
// #define PUGIXML_MEMORY_PAGE_SIZE 32768
|
||||
// #define PUGIXML_MEMORY_OUTPUT_STACK 10240
|
||||
// #define PUGIXML_MEMORY_XPATH_PAGE_SIZE 4096
|
||||
|
||||
// Tune this constant to adjust max nesting for XPath queries
|
||||
// #define PUGIXML_XPATH_DEPTH_LIMIT 1024
|
||||
|
||||
// Uncomment this to switch to header-only version
|
||||
// #define PUGIXML_HEADER_ONLY
|
||||
|
||||
// Uncomment this to enable long long support
|
||||
// #define PUGIXML_HAS_LONG_LONG
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Copyright (c) 2006-2020 Arseny Kapoulkine
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,411 @@
|
||||
/**
|
||||
* Copyright (c) 2020, Timothy Stack
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Timothy Stack nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "base/lnav_log.hh"
|
||||
#include "pugixml/pugixml.hpp"
|
||||
#include "sql_util.hh"
|
||||
#include "vtab_module.hh"
|
||||
#include "yajlpp/yajlpp.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
enum {
|
||||
XP_COL_RESULT,
|
||||
XP_COL_NODE_PATH,
|
||||
XP_COL_NODE_ATTR,
|
||||
XP_COL_NODE_TEXT,
|
||||
XP_COL_XPATH,
|
||||
XP_COL_VALUE,
|
||||
};
|
||||
|
||||
static
|
||||
thread_local std::unordered_map<std::string, pugi::xpath_query> QUERY_CACHE;
|
||||
|
||||
static
|
||||
pugi::xpath_query checkout_query(const std::string& query)
|
||||
{
|
||||
auto iter = QUERY_CACHE.find(query);
|
||||
if (iter == QUERY_CACHE.end()) {
|
||||
auto xquery = pugi::xpath_query(query.c_str());
|
||||
|
||||
if (!xquery) {
|
||||
return xquery;
|
||||
}
|
||||
|
||||
auto pair = QUERY_CACHE.emplace(query, std::move(xquery));
|
||||
|
||||
iter = pair.first;
|
||||
}
|
||||
|
||||
auto retval = std::move(iter->second);
|
||||
|
||||
QUERY_CACHE.erase(iter);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static
|
||||
void checkin_query(const std::string& query_str, pugi::xpath_query query)
|
||||
{
|
||||
if (!query) {
|
||||
return;
|
||||
}
|
||||
|
||||
QUERY_CACHE[query_str] = std::move(query);
|
||||
}
|
||||
|
||||
static
|
||||
std::string get_actual_path(const pugi::xml_node& node)
|
||||
{
|
||||
std::string retval;
|
||||
auto curr = node;
|
||||
|
||||
while (curr) {
|
||||
switch (curr.type()) {
|
||||
case pugi::node_null:
|
||||
break;
|
||||
case pugi::node_pcdata:
|
||||
retval += "text()";
|
||||
break;
|
||||
default: {
|
||||
auto name = std::string(curr.name());
|
||||
|
||||
if (curr.previous_sibling(curr.name()) ||
|
||||
curr.next_sibling(curr.name())) {
|
||||
auto sibling = curr;
|
||||
int index = 0;
|
||||
|
||||
while (sibling) {
|
||||
index += 1;
|
||||
sibling = sibling.previous_sibling(curr.name());
|
||||
}
|
||||
|
||||
name += "[" + std::to_string(index) + "]";
|
||||
}
|
||||
if (retval.empty()) {
|
||||
retval = name;
|
||||
} else {
|
||||
retval = name + std::string("/") + retval;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
curr = curr.parent();
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
struct xpath_vtab {
|
||||
static constexpr const char *CREATE_STMT = R"(
|
||||
-- The xpath() table-valued function allows you to execute an xpath expression
|
||||
CREATE TABLE xpath (
|
||||
result text, -- The result of the xpath expression
|
||||
node_path text, -- The absolute path to the node selected by the expression
|
||||
node_attr text, -- The node attributes stored in a JSON object
|
||||
node_text text, -- The text portion of the node selected by the expression
|
||||
|
||||
xpath text HIDDEN,
|
||||
value text HIDDEN
|
||||
);
|
||||
)";
|
||||
|
||||
struct cursor {
|
||||
sqlite3_vtab_cursor base;
|
||||
sqlite3_int64 c_rowid{0};
|
||||
string c_xpath;
|
||||
string c_value;
|
||||
pugi::xpath_query c_query;
|
||||
pugi::xml_document c_doc;
|
||||
pugi::xpath_node_set c_results;
|
||||
|
||||
cursor(sqlite3_vtab *vt)
|
||||
: base({vt}) {
|
||||
};
|
||||
|
||||
~cursor() {
|
||||
this->reset();
|
||||
}
|
||||
|
||||
int reset() {
|
||||
this->c_rowid = 0;
|
||||
checkin_query(this->c_xpath, std::move(this->c_query));
|
||||
|
||||
return SQLITE_OK;
|
||||
};
|
||||
|
||||
int next() {
|
||||
this->c_rowid += 1;
|
||||
|
||||
return SQLITE_OK;
|
||||
};
|
||||
|
||||
int eof() {
|
||||
return this->c_rowid >= this->c_results.size();
|
||||
};
|
||||
|
||||
int get_rowid(sqlite3_int64 &rowid_out) {
|
||||
rowid_out = this->c_rowid;
|
||||
|
||||
return SQLITE_OK;
|
||||
};
|
||||
};
|
||||
|
||||
int get_column(const cursor &vc, sqlite3_context *ctx, int col) {
|
||||
switch (col) {
|
||||
case XP_COL_RESULT: {
|
||||
auto& xpath_node = vc.c_results[vc.c_rowid];
|
||||
|
||||
if (xpath_node.node()) {
|
||||
ostringstream oss;
|
||||
|
||||
// XXX avoid the extra allocs
|
||||
xpath_node.node().print(oss);
|
||||
auto node_xml = oss.str();
|
||||
sqlite3_result_text(ctx,
|
||||
node_xml.c_str(),
|
||||
node_xml.length(),
|
||||
SQLITE_TRANSIENT);
|
||||
} else if (xpath_node.attribute()) {
|
||||
sqlite3_result_text(ctx,
|
||||
xpath_node.attribute().value(),
|
||||
-1,
|
||||
SQLITE_TRANSIENT);
|
||||
} else {
|
||||
sqlite3_result_null(ctx);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case XP_COL_NODE_PATH: {
|
||||
auto& xpath_node = vc.c_results[vc.c_rowid];
|
||||
auto x_node = xpath_node.node();
|
||||
auto x_attr = xpath_node.attribute();
|
||||
|
||||
if (x_node || x_attr) {
|
||||
if (!x_node) {
|
||||
x_node = xpath_node.parent();
|
||||
}
|
||||
|
||||
auto node_path = get_actual_path(x_node);
|
||||
if (x_attr) {
|
||||
node_path += "/@" + std::string(x_attr.name());
|
||||
}
|
||||
sqlite3_result_text(ctx,
|
||||
node_path.c_str(),
|
||||
node_path.length(),
|
||||
SQLITE_TRANSIENT);
|
||||
} else {
|
||||
sqlite3_result_null(ctx);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case XP_COL_NODE_ATTR: {
|
||||
auto& xpath_node = vc.c_results[vc.c_rowid];
|
||||
auto x_node = xpath_node.node();
|
||||
auto x_attr = xpath_node.attribute();
|
||||
|
||||
if (x_node || x_attr) {
|
||||
if (!x_node) {
|
||||
x_node = xpath_node.parent();
|
||||
}
|
||||
|
||||
yajlpp_gen gen;
|
||||
|
||||
yajl_gen_config(gen, yajl_gen_beautify, false);
|
||||
|
||||
{
|
||||
yajlpp_map attrs(gen);
|
||||
|
||||
for (const auto& attr : x_node.attributes()) {
|
||||
attrs.gen(attr.name());
|
||||
attrs.gen(attr.value());
|
||||
}
|
||||
}
|
||||
|
||||
auto sf = gen.to_string_fragment();
|
||||
|
||||
sqlite3_result_text(ctx,
|
||||
sf.data(),
|
||||
sf.length(),
|
||||
SQLITE_TRANSIENT);
|
||||
sqlite3_result_subtype(ctx, 'J');
|
||||
} else {
|
||||
sqlite3_result_null(ctx);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case XP_COL_NODE_TEXT: {
|
||||
auto& xpath_node = vc.c_results[vc.c_rowid];
|
||||
auto x_node = xpath_node.node();
|
||||
auto x_attr = xpath_node.attribute();
|
||||
|
||||
if (x_node || x_attr) {
|
||||
if (!x_node) {
|
||||
x_node = xpath_node.parent();
|
||||
}
|
||||
|
||||
auto node_text = x_node.text();
|
||||
|
||||
sqlite3_result_text(ctx,
|
||||
node_text.get(),
|
||||
-1,
|
||||
SQLITE_TRANSIENT);
|
||||
} else {
|
||||
sqlite3_result_null(ctx);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case XP_COL_XPATH:
|
||||
sqlite3_result_text(ctx,
|
||||
vc.c_xpath.c_str(),
|
||||
vc.c_xpath.length(),
|
||||
SQLITE_STATIC);
|
||||
break;
|
||||
case XP_COL_VALUE:
|
||||
sqlite3_result_text(ctx,
|
||||
vc.c_value.c_str(),
|
||||
vc.c_value.length(),
|
||||
SQLITE_STATIC);
|
||||
break;
|
||||
}
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
};
|
||||
|
||||
static int rcBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo)
|
||||
{
|
||||
vtab_index_constraints vic(pIdxInfo);
|
||||
vtab_index_usage viu(pIdxInfo);
|
||||
|
||||
for (auto iter = vic.begin(); iter != vic.end(); ++iter) {
|
||||
if (iter->op != SQLITE_INDEX_CONSTRAINT_EQ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (iter->iColumn) {
|
||||
case XP_COL_VALUE:
|
||||
case XP_COL_XPATH:
|
||||
viu.column_used(iter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
viu.allocate_args(2);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
static int rcFilter(sqlite3_vtab_cursor *pVtabCursor,
|
||||
int idxNum, const char *idxStr,
|
||||
int argc, sqlite3_value **argv)
|
||||
{
|
||||
auto *pCur = (xpath_vtab::cursor *)pVtabCursor;
|
||||
|
||||
if (argc != 2) {
|
||||
pCur->c_xpath.clear();
|
||||
pCur->c_value.clear();
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
pCur->c_value = (const char *) sqlite3_value_text(argv[1]);
|
||||
auto parse_res = pCur->c_doc.load_string(pCur->c_value.c_str());
|
||||
if (!parse_res) {
|
||||
pVtabCursor->pVtab->zErrMsg = sqlite3_mprintf(
|
||||
"Invalid XML document at offset %d: %s",
|
||||
parse_res.offset, parse_res.description());
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
|
||||
pCur->c_xpath = (const char *) sqlite3_value_text(argv[0]);
|
||||
pCur->c_query = checkout_query(pCur->c_xpath);
|
||||
if (!pCur->c_query) {
|
||||
auto& res = pCur->c_query.result();
|
||||
pVtabCursor->pVtab->zErrMsg = sqlite3_mprintf(
|
||||
"Invalid XPATH expression at offset %d: %s",
|
||||
res.offset, res.description());
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
|
||||
pCur->c_rowid = 0;
|
||||
pCur->c_results = pCur->c_doc.select_nodes(pCur->c_query);
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
int register_xpath_vtab(sqlite3 *db)
|
||||
{
|
||||
static vtab_module<tvt_no_update<xpath_vtab>> XPATH_MODULE;
|
||||
static help_text xpath_help = help_text("xpath",
|
||||
"A table-valued function that executes an xpath expression over an XML "
|
||||
"string and returns the selected values.")
|
||||
.sql_table_valued_function()
|
||||
.with_parameter({"xpath",
|
||||
"The XPATH expression to evaluate over the XML document."})
|
||||
.with_parameter({"xmldoc",
|
||||
"The XML document as a string."})
|
||||
.with_result({"result",
|
||||
"The result of the XPATH expression."})
|
||||
.with_result({"node_path",
|
||||
"The absolute path to the node containing the result."})
|
||||
.with_result({"node_attr",
|
||||
"The node's attributes stored in JSON object."})
|
||||
.with_result({"node_text",
|
||||
"The node's text value."})
|
||||
.with_tags({"string", "xml"})
|
||||
.with_example({
|
||||
"To select the XML nodes on the path '/abc/def'",
|
||||
"SELECT * FROM xpath('/abc/def', '<abc><def a=\"b\">Hello</def><def>Bye</def></abc>')"
|
||||
})
|
||||
.with_example({
|
||||
"To select all 'a' attributes on the path '/abc/def'",
|
||||
"SELECT * FROM xpath('/abc/def/@a', '<abc><def a=\"b\">Hello</def><def>Bye</def></abc>')"
|
||||
})
|
||||
.with_example({
|
||||
"To select the text nodes on the path '/abc/def'",
|
||||
"SELECT * FROM xpath('/abc/def/text()', '<abc><def a=\"b\">Hello ★</def></abc>')"
|
||||
});
|
||||
|
||||
int rc;
|
||||
|
||||
XPATH_MODULE.vm_module.xBestIndex = rcBestIndex;
|
||||
XPATH_MODULE.vm_module.xFilter = rcFilter;
|
||||
|
||||
rc = XPATH_MODULE.create(db, "xpath");
|
||||
sqlite_function_help.insert(make_pair("xpath", &xpath_help));
|
||||
xpath_help.index_tags();
|
||||
|
||||
ensure(rc == SQLITE_OK);
|
||||
|
||||
return rc;
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2020, Timothy Stack
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Timothy Stack nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef xpath_vtab_hh
|
||||
#define xpath_vtab_hh
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
int register_xpath_vtab(sqlite3 *db);
|
||||
|
||||
#endif
|
@ -0,0 +1,36 @@
|
||||
[2020-12-10 06:56:41,061] INFO [m:108] Calling 'x' with params:
|
||||
|
||||
[2020-12-10 06:56:41,092] DEBUG [connect.client:69] Full request text:
|
||||
<?xml version='1.0' encoding='iso-8859-2'?>
|
||||
<a-request>
|
||||
<head>
|
||||
x
|
||||
</head>
|
||||
<source>
|
||||
x
|
||||
</source>
|
||||
<request>
|
||||
<name>
|
||||
x
|
||||
</name>
|
||||
</request>
|
||||
</a-request>
|
||||
|
||||
[2020-12-10 06:56:41,099] DEBUG [m:85] Full reply text:
|
||||
<?xml version='1.0' encoding='iso-8859-2'?>
|
||||
<a-reply>
|
||||
<head>
|
||||
x
|
||||
</head>
|
||||
<reply>
|
||||
<status>
|
||||
<result>OK</result>
|
||||
</status>
|
||||
<name>
|
||||
x
|
||||
</name>
|
||||
</reply>
|
||||
<technical-track>
|
||||
x
|
||||
</technical-track>
|
||||
</a-reply>
|
@ -0,0 +1,40 @@
|
||||
#! /bin/bash
|
||||
|
||||
run_test ./drive_sql "SELECT * FROM xpath('/abc[', '<abc/>')"
|
||||
|
||||
check_error_output "invalid xpath not reported?" <<EOF
|
||||
error: sqlite3_exec failed -- Invalid XPATH expression at offset 5: Unrecognized node test
|
||||
EOF
|
||||
|
||||
run_test ./drive_sql "SELECT * FROM xpath('/abc', '<abc')"
|
||||
|
||||
check_error_output "invalid XML not reported?" <<EOF
|
||||
error: sqlite3_exec failed -- Invalid XML document at offset 3: Error parsing start element tag
|
||||
EOF
|
||||
|
||||
run_test ./drive_sql "SELECT * FROM xpath('/abc/def', '<abc/>')"
|
||||
|
||||
check_output "got unexpected results" <<EOF
|
||||
EOF
|
||||
|
||||
run_test ./drive_sql "SELECT * FROM xpath('/abc/def[@a=\"b\"]', '<abc><def/><def a=\"b\">ghi</def></abc>')"
|
||||
|
||||
check_output "got unexpected results" <<EOF
|
||||
Row 0:
|
||||
Column result: <def a="b">ghi</def>
|
||||
|
||||
Column node_path: /abc/def[2]
|
||||
Column node_attr: {"a":"b"}
|
||||
Column node_text: ghi
|
||||
EOF
|
||||
|
||||
run_test ./drive_sql "SELECT * FROM xpath('/abc/def', '<abc><def>Hello ></def></abc>')"
|
||||
|
||||
check_output "got unexpected results" <<EOF
|
||||
Row 0:
|
||||
Column result: <def>Hello ></def>
|
||||
|
||||
Column node_path: /abc/def
|
||||
Column node_attr: {}
|
||||
Column node_text: Hello >
|
||||
EOF
|
Loading…
Reference in New Issue