Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. 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.
- `distance`: The shortest distance to the current node from the root node.
Expand Down
62 changes: 53 additions & 9 deletions bfsvtab.c
Original file line number Diff line number Diff line change
Expand Up @@ -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:
**
Expand All @@ -21,7 +22,7 @@
** tocolumn=<columname>,
** )
**
** 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.
Expand Down Expand Up @@ -390,6 +391,8 @@ struct bfsvtab_vtab {
char *zTableName;
char *zFromColumn;
char *zToColumn;
char *zOrderByColumn;

sqlite3 *db;
};

Expand All @@ -409,6 +412,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 */

Expand All @@ -428,6 +432,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);
}
Expand Down Expand Up @@ -512,6 +517,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;
Expand All @@ -520,7 +536,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
Expand All @@ -531,6 +547,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);
}
Expand Down Expand Up @@ -575,12 +592,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;
}
Expand Down Expand Up @@ -731,13 +750,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;
}
Expand Down Expand Up @@ -779,6 +804,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;

Expand All @@ -801,10 +827,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;
}
Expand Down Expand Up @@ -937,6 +973,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)
Expand Down
9 changes: 7 additions & 2 deletions test.sh
Original file line number Diff line number Diff line change
@@ -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)
10 changes: 10 additions & 0 deletions test/test_basic.sh
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion test/bfsvtab.sql → test/test_basic_bfsvtab.sql
Original file line number Diff line number Diff line change
@@ -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',
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion test/rcte.sql → test/test_basic_rcte.sql
Original file line number Diff line number Diff line change
@@ -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
Expand Down
10 changes: 10 additions & 0 deletions test/test_memory_leaks.sh
Original file line number Diff line number Diff line change
@@ -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
21 changes: 21 additions & 0 deletions test/test_memory_leaks.sql
Original file line number Diff line number Diff line change
@@ -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;
27 changes: 27 additions & 0 deletions test/test_ordered_traversal.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/bash

set -e

FILENAME="$(basename $0)"

echo "$FILENAME"

GOT="$(sqlite3 <<EOF
.load ./bfsvtab
create table edges(src integer, dst integer, key integer);
insert into edges(src, dst, key) values
(0, 1, 3),
(0, 2, 2),
(0, 3, 1);
select id from bfsvtab
where
tablename = 'edges'
and fromcolumn = 'src'
and tocolumn = 'dst'
and root = 0 and id <> root
and order_by_column = 'key';
EOF
)"
EXPECTED="3 2 1"

cmp <(echo $GOT) <(echo $EXPECTED)