From 6355706e8854e51b65eed04ba5dbbaf5f9f664a7 Mon Sep 17 00:00:00 2001 From: abetlen Date: Tue, 8 Dec 2020 02:45:46 -0500 Subject: [PATCH 1/5] Added order_by_column optional virtual table argument for neighbourhood ordering. --- bfsvtab.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/bfsvtab.c b/bfsvtab.c index 88aed33..7a5b5c3 100644 --- a/bfsvtab.c +++ b/bfsvtab.c @@ -390,6 +390,8 @@ struct bfsvtab_vtab { char *zTableName; char *zFromColumn; char *zToColumn; + char *zOrderByColumn; + sqlite3 *db; }; @@ -409,6 +411,7 @@ struct bfsvtab_cursor { char *zTableName; /* Name of table holding edge relation */ char *zFromColumn; /* Name of from column of zTableName */ char *zToColumn; /* Name of to column of zTableName */ + char *zOrderByColumn; /* Name of column to order neighbours by during BFS traversal */ bfsvtab_avl *pVisited; /* Set of Visited Nodes */ @@ -428,6 +431,7 @@ static void bfsvtabFree(bfsvtab_vtab *p) { sqlite3_free(p->zTableName); sqlite3_free(p->zFromColumn); sqlite3_free(p->zToColumn); + sqlite3_free(p->zOrderByColumn); memset(p, 0, sizeof(*p)); sqlite3_free(p); } @@ -512,6 +516,17 @@ static int bfsvtabConnect( } continue; } + zVal = bfsvtabValueOfKey("order_by_column", argv[i]); + if (zVal) { + sqlite3_free(pNew->zOrderByColumn); + pNew->zOrderByColumn = bfsvtabDequote(zVal); + if (pNew->zOrderByColumn == 0) { + rc = SQLITE_NOMEM; + goto connectError; + } + continue; + } + *pzErr = sqlite3_mprintf("unrecognized argument: [%s]\n", argv[i]); bfsvtabFree(pNew); return SQLITE_ERROR; @@ -520,7 +535,7 @@ static int bfsvtabConnect( rc = sqlite3_declare_vtab(db, "CREATE TABLE x(id,parent,distance,shortest_path,root HIDDEN," "tablename HIDDEN,fromcolumn HIDDEN," - "tocolumn HIDDEN)" + "tocolumn HIDDEN, order_by_column HIDDEN)" ); /* For convenience, define symbolic names for the index to each column. */ #define BFSVTAB_COL_ID 0 @@ -531,6 +546,7 @@ static int bfsvtabConnect( #define BFSVTAB_COL_TABLENAME 5 #define BFSVTAB_COL_FROMCOLUMN 6 #define BFSVTAB_COL_TOCOLUMN 7 +#define BFSVTAB_COL_ORDER_BY_COLUMN 8 if (rc != SQLITE_OK) { bfsvtabFree(pNew); } @@ -575,12 +591,14 @@ static void bfsvtabClearCursor(bfsvtab_cursor *pCur) { sqlite3_free(pCur->zTableName); sqlite3_free(pCur->zFromColumn); sqlite3_free(pCur->zToColumn); + sqlite3_free(pCur->zOrderByColumn); sqlite3_finalize(pCur->pStmt); pCur->zTableName = 0; pCur->zFromColumn = 0; pCur->zToColumn = 0; + pCur->zOrderByColumn = 0; pCur->pCurrent = 0; pCur->pVisited = 0; } @@ -731,13 +749,19 @@ static int bfsvtabColumn( pCur->zFromColumn : pCur->pVtab->zFromColumn, -1, SQLITE_TRANSIENT); break; - default: - assert( i==BFSVTAB_COL_TOCOLUMN ); + case BFSVTAB_COL_TOCOLUMN: sqlite3_result_text(ctx, pCur->zToColumn ? pCur->zToColumn : pCur->pVtab->zToColumn, -1, SQLITE_TRANSIENT); break; + default: + assert( i==BFSVTAB_COL_ORDER_BY_COLUMN ); + sqlite3_result_text(ctx, + pCur->zOrderByColumn ? + pCur->zOrderByColumn : pCur->pVtab->zOrderByColumn, + -1, SQLITE_TRANSIENT); + break; } return SQLITE_OK; } @@ -779,6 +803,7 @@ static int bfsvtabFilter( const char *zTableName = pVtab->zTableName; const char *zFromColumn = pVtab->zFromColumn; const char *zToColumn = pVtab->zToColumn; + const char *zOrderByColumn = pVtab->zOrderByColumn; bfsvtab_node *root; bfsvtab_avl *rootAvlNode; @@ -801,10 +826,20 @@ static int bfsvtabFilter( zToColumn = (const char*)sqlite3_value_text(argv[(idxNum>>16)&0x0f]); pCur->zToColumn = sqlite3_mprintf("%s", zToColumn); } + if((idxNum & 0xf00000) != 0) { + zOrderByColumn = (const char*)sqlite3_value_text(argv[(idxNum>>20)&0x0f]); + pCur->zOrderByColumn = sqlite3_mprintf("%s", zOrderByColumn); + } - zSql = sqlite3_mprintf( - "SELECT \"%w\".\"%w\" FROM \"%w\" WHERE \"%w\".\"%w\"=?1", - zTableName, zToColumn, zTableName, zTableName, zFromColumn); + if (zOrderByColumn) { + zSql = sqlite3_mprintf( + "SELECT \"%w\".\"%w\" FROM \"%w\" WHERE \"%w\".\"%w\"=?1 ORDER BY \"%w\".\"%w\"", + zTableName, zToColumn, zTableName, zTableName, zFromColumn, zTableName, zOrderByColumn); + } else { + zSql = sqlite3_mprintf( + "SELECT \"%w\".\"%w\" FROM \"%w\" WHERE \"%w\".\"%w\"=?1", + zTableName, zToColumn, zTableName, zTableName, zFromColumn); + } if (zSql == 0) { return SQLITE_NOMEM; } @@ -937,6 +972,14 @@ static int bfsvtabBestIndex( pIdxInfo->aConstraintUsage[i].argvIndex = ++idx; pIdxInfo->aConstraintUsage[i].omit = 1; } + if ((iPlan & 0xf00000) == 0 + && pConstraint->iColumn == BFSVTAB_COL_ORDER_BY_COLUMN + && pConstraint->op == SQLITE_INDEX_CONSTRAINT_EQ + ) { + iPlan |= idx<<20; + pIdxInfo->aConstraintUsage[i].argvIndex = ++idx; + pIdxInfo->aConstraintUsage[i].omit = 1; + } } if ((pVtab->zTableName == 0 && (iPlan & 0x000f00) == 0) || (pVtab->zFromColumn == 0 && (iPlan & 0x00f000) == 0) From e7cf751666b886a6430cf86e77148abcbbd93f92 Mon Sep 17 00:00:00 2001 From: abetlen Date: Tue, 8 Dec 2020 02:50:50 -0500 Subject: [PATCH 2/5] Update README to include order_by_column --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index dd41c97..9c75b72 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,9 @@ The virtual table requires 4 SQL constraints to be set for all queries: - `tocolumn`: The node id column where an edge goes to (must be integer). - `root`: The root node id of the breadth-first traversal. +As well as an optional constraint to determine the order of node neighbour traversal. +- `order_by_column`: The name of the column used to determine the order of node neighbour traversal. + The virtual table also provides the following columns that can be returned or used as contraints: - `id`: The id of the current node being visited. - `distance`: The shortest distance to the current node from the root node. From d7f790448cc4fb026510127f5a22d9e917e904d2 Mon Sep 17 00:00:00 2001 From: abetlen Date: Fri, 25 Dec 2020 16:17:08 -0500 Subject: [PATCH 3/5] Updated documentation --- bfsvtab.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bfsvtab.c b/bfsvtab.c index 7a5b5c3..b9c9dfe 100644 --- a/bfsvtab.c +++ b/bfsvtab.c @@ -10,8 +10,9 @@ ** ************************************************************************* ** -** This file code for a virtual table that performs a breadth-first search of -** any graph represented in a real table. The virtual table is called "bfs". +** This file contains code for a virtual table that performs a breadth-first +** search of any graph represented in a real or virtual table. +** The virtual table is called "bfsvtab". ** ** A bfsvtab virtual table is created liske this: ** @@ -21,7 +22,7 @@ ** tocolumn=, ** ) ** -** This bfs is minimal, in the sense that it uses only the required +** This bfsvtab extension is minimal, in the sense that it uses only the required ** methods on the sqlite3_module object. As a result, bfsvtab is ** a read-only and eponymous-only table. Those limitation can be removed ** by adding new methods. From 53aee91a49583485da2c37212ac0c9ed0b7c842a Mon Sep 17 00:00:00 2001 From: abetlen Date: Mon, 28 Dec 2020 15:47:40 -0500 Subject: [PATCH 4/5] Re-organize tests --- test.sh | 9 +++++-- test/test_basic.sh | 10 ++++++++ test/{bfsvtab.sql => test_basic_bfsvtab.sql} | 2 +- test/{fixture.sql => test_basic_fixture.sql} | 0 test/{rcte.sql => test_basic_rcte.sql} | 2 +- test/test_memory_leaks.sh | 10 ++++++++ test/test_memory_leaks.sql | 21 +++++++++++++++ test/test_ordered_traversal.sh | 27 ++++++++++++++++++++ 8 files changed, 77 insertions(+), 4 deletions(-) create mode 100755 test/test_basic.sh rename test/{bfsvtab.sql => test_basic_bfsvtab.sql} (84%) rename test/{fixture.sql => test_basic_fixture.sql} (100%) rename test/{rcte.sql => test_basic_rcte.sql} (91%) create mode 100755 test/test_memory_leaks.sh create mode 100644 test/test_memory_leaks.sql create mode 100755 test/test_ordered_traversal.sh diff --git a/test.sh b/test.sh index 0255684..321411f 100755 --- a/test.sh +++ b/test.sh @@ -1,5 +1,10 @@ #!/bin/bash -valgrind --leak-check=full --show-leak-kinds=all --keep-debuginfo=yes -s sqlite3 < test/bfsvtab.sql +set -e + +echo "===== Testing sqlite-bfsvtab-ext ======" + +ls test/test_*.sh | xargs -I - bash - + +echo "========= Testing Completed ===========" -cmp <(sqlite3 < test/rcte.sql) <(sqlite3 < test/bfsvtab.sql) diff --git a/test/test_basic.sh b/test/test_basic.sh new file mode 100755 index 0000000..295f773 --- /dev/null +++ b/test/test_basic.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +FILENAME="$(basename $0)" + +echo "$FILENAME" + +cmp <(sqlite3 < $DIR/test_basic_rcte.sql) <(sqlite3 < $DIR/test_basic_bfsvtab.sql) diff --git a/test/bfsvtab.sql b/test/test_basic_bfsvtab.sql similarity index 84% rename from test/bfsvtab.sql rename to test/test_basic_bfsvtab.sql index 375061d..0958aa3 100644 --- a/test/bfsvtab.sql +++ b/test/test_basic_bfsvtab.sql @@ -1,5 +1,5 @@ .load ./bfsvtab -.read ./test/fixture.sql +.read ./test/test_basic_fixture.sql create virtual table bfs using bfsvtab( tablename='edges', fromcolumn='fromNode', diff --git a/test/fixture.sql b/test/test_basic_fixture.sql similarity index 100% rename from test/fixture.sql rename to test/test_basic_fixture.sql diff --git a/test/rcte.sql b/test/test_basic_rcte.sql similarity index 91% rename from test/rcte.sql rename to test/test_basic_rcte.sql index d3c591c..0e94c57 100644 --- a/test/rcte.sql +++ b/test/test_basic_rcte.sql @@ -1,4 +1,4 @@ -.read ./test/fixture.sql +.read ./test/test_basic_fixture.sql with recursive bfs(id, parent, shortest_path, distance) as ( select 1, null, '/' || 1 || '/', 0 diff --git a/test/test_memory_leaks.sh b/test/test_memory_leaks.sh new file mode 100755 index 0000000..1c2972c --- /dev/null +++ b/test/test_memory_leaks.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +FILENAME="$(basename $0)" + +echo "$FILENAME" + +valgrind --quiet --child-silent-after-fork=yes --leak-check=full --show-leak-kinds=all --keep-debuginfo=yes sqlite3 < $DIR/test_memory_leaks.sql > /dev/null diff --git a/test/test_memory_leaks.sql b/test/test_memory_leaks.sql new file mode 100644 index 0000000..b843ad5 --- /dev/null +++ b/test/test_memory_leaks.sql @@ -0,0 +1,21 @@ +.load ./bfsvtab +create table edges(fromNode integer, toNode integer, primary key(fromNode, toNode)); +insert into edges(fromNode, toNode) values + (1, 2), + (1, 3), + (2, 4), + (3, 4), + (4, 5), + (4, 6), + (5, 7), + (6, 7), + (7, 8), + (7, 9), + (8, 10), + (9, 10); +create virtual table bfs using bfsvtab( + tablename='edges', + fromcolumn='fromNode', + tocolumn='toNode', +); +select id, parent, shortest_path, distance from bfs where root = 1; diff --git a/test/test_ordered_traversal.sh b/test/test_ordered_traversal.sh new file mode 100755 index 0000000..9c91832 --- /dev/null +++ b/test/test_ordered_traversal.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +set -e + +FILENAME="$(basename $0)" + +echo "$FILENAME" + +GOT="$(sqlite3 < root + and order_by_column = 'key'; +EOF +)" +EXPECTED="3 2 1" + +cmp <(echo $GOT) <(echo $EXPECTED) From 9d4309b1a31f038c67ebbf0923dbf397472095a6 Mon Sep 17 00:00:00 2001 From: abetlen Date: Mon, 28 Dec 2020 15:49:16 -0500 Subject: [PATCH 5/5] Updated README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9c75b72..a4389c6 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ The virtual table requires 4 SQL constraints to be set for all queries: - `root`: The root node id of the breadth-first traversal. As well as an optional constraint to determine the order of node neighbour traversal. -- `order_by_column`: The name of the column used to determine the order of node neighbour traversal. +- `order_by_column`: The name of the column used to determine the order of node neighbour traversal. Adjacent edges are traversed in descending order. The virtual table also provides the following columns that can be returned or used as contraints: - `id`: The id of the current node being visited.