1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
|
#!/usr/bin/env sh
# test-search-db-schema.sh
#
# Schema contract between spine's --sqlite-update output and the search form
# in sisudoc-spine-search-cgi.
#
# Spine holds the only copy of the DDL; the search form holds no DDL at all,
# only a small set of column references. Nothing connects the two, so a
# column renamed in spine is found by whoever runs a search next. This is the
# check that finds it earlier: build a search database, then read the column
# references out of the search form's source and require every one of them to
# exist in what was built.
#
# It also requires the three-way agreement on schema version: the version
# spine declares, the version stamped into the database it built, and the
# version the search form expects.
#
# Usage:
# ./test/test-search-db-schema.sh [/path/to/spine]
#
# Environment:
# SpinePOD pod directory to build the db from, relative to the
# spine directory (default: data/pod, the bundled set)
# SpineSearchCGIsrc path to the search cgi source file or its repository
# (default: ../sisudoc-spine-search-cgi)
#
# sqlite3 is needed. If it is not on PATH the script re-runs itself inside a
# nix shell that has it.
#
# Exit codes:
# 0 spine's database satisfies every expectation the search form has
# 1 a mismatch was found (printed)
# 2 set-up problem (no binary, no cgi source, no sqlite3, no samples)
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SPINE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
if ! command -v sqlite3 > /dev/null 2>&1; then
if command -v nix > /dev/null 2>&1; then
echo "sqlite3 not on PATH, re-running inside nix shell \"nixpkgs#sqlite\""
exec nix shell "nixpkgs#sqlite" -c "$0" "$@"
fi
echo "ERROR: sqlite3 not on PATH and nix not available." >&2
exit 2
fi
SPINE_BIN="${1:-}"
if [ -z "$SPINE_BIN" ]; then
if [ -x "$SPINE_DIR/result/bin/spine" ]; then SPINE_BIN="$SPINE_DIR/result/bin/spine"
elif [ -x "$SPINE_DIR/bin/spine-ldc" ]; then SPINE_BIN="$SPINE_DIR/bin/spine-ldc"
elif [ -x "$SPINE_DIR/bin/spine" ]; then SPINE_BIN="$SPINE_DIR/bin/spine"
else
echo "ERROR: spine binary not found. Specify path as argument." >&2
exit 2
fi
fi
# the search form's source, the other half of the contract
CGI_SRC="${SpineSearchCGIsrc:-$SPINE_DIR/../sisudoc-spine-search-cgi}"
if [ -d "$CGI_SRC" ]; then
CGI_SRC="$CGI_SRC/src/sisudoc/spine_search.d"
fi
if [ ! -f "$CGI_SRC" ]; then
echo "ERROR: search cgi source not found at $CGI_SRC" >&2
echo " set \$SpineSearchCGIsrc to the sisudoc-spine-search-cgi checkout" >&2
exit 2
fi
SAMPLES_RAW="$SPINE_DIR/${SpinePOD:-data/pod}"
if [ -d "$SAMPLES_RAW" ]; then
SAMPLES_DIR="$(cd "$SAMPLES_RAW" && pwd)"
else
echo "ERROR: sample documents not found at $SAMPLES_RAW" >&2
exit 2
fi
OUT_DIR="$SCRIPT_DIR/current-search-db"
DB_DIR="$OUT_DIR/sqlite-database"
DB_NAME="spine.search.db"
DB="$DB_DIR/$DB_NAME"
echo "spine binary: $SPINE_BIN"
echo "search cgi: $CGI_SRC"
echo "samples: $SAMPLES_DIR"
rm -rf "$OUT_DIR"
mkdir -p "$DB_DIR"
echo "Building search database ..."
"$SPINE_BIN" --sqlite-db-recreate \
--sqlite-db-path="$DB_DIR" --sqlite-db-filename="$DB_NAME" > /dev/null
# the update has to be able to fail the test: spine exits non-zero when any
# sqlite statement failed, and that is exactly the case this test is here for
if ! "$SPINE_BIN" --sqlite-update \
--sqlite-db-path="$DB_DIR" --sqlite-db-filename="$DB_NAME" \
--output="$OUT_DIR" "$SAMPLES_DIR"/* > "$OUT_DIR/build.log" 2>&1; then
echo "FAIL: spine --sqlite-update reported failure"
grep "ERROR SQLite\|run FAILED" "$OUT_DIR/build.log" | head -20
exit 1
fi
FAILURES=0
fail() { echo " MISMATCH $*"; FAILURES=$((FAILURES + 1)); }
# ── the database was built at all
if [ ! -f "$DB" ]; then
echo "FAIL: no database at $DB"
exit 1
fi
# ── the tables the search form joins
echo "Checking tables ..."
for t in metadata_and_text doc_objects topic_register; do
found=$(sqlite3 "$DB" \
"select count(*) from sqlite_master where type='table' and name='$t';")
[ "$found" = "1" ] || fail "table $t missing"
done
# ── the tables are populated, an empty db satisfies a column check trivially
docs=$(sqlite3 "$DB" "select count(*) from metadata_and_text;" 2>/dev/null || echo 0)
objs=$(sqlite3 "$DB" "select count(*) from doc_objects;" 2>/dev/null || echo 0)
echo " documents: $docs, objects: $objs"
[ "$docs" -gt 0 ] || fail "metadata_and_text is empty"
[ "$objs" -gt 0 ] || fail "doc_objects is empty"
# ── schema version: spine's declaration, the stamp in the db, the search
# form's expectation; all three have to agree
echo "Checking schema version ..."
SPINE_SQL_SRC="$SPINE_DIR/src/sisudoc/outputs/io_out/sqlite.d"
declared=$(sed -n 's/^enum sqlite_db_schema_version *= *\([0-9][0-9]*\);.*/\1/p' \
"$SPINE_SQL_SRC" | head -1)
stamped=$(sqlite3 "$DB" "pragma user_version;")
expected=$(sed -n 's/^enum sqlite_db_schema_version_expected *= *\([0-9][0-9]*\);.*/\1/p' \
"$CGI_SRC" | head -1)
echo " spine declares: ${declared:-unset}, db stamped: ${stamped:-unset}, cgi expects: ${expected:-unset}"
if [ -z "$declared" ]; then
fail "spine declares no sqlite_db_schema_version in $SPINE_SQL_SRC"
elif [ "$stamped" != "$declared" ]; then
fail "db stamped user_version $stamped, spine declares $declared"
fi
if [ -z "$expected" ]; then
fail "search form declares no sqlite_db_schema_version_expected"
elif [ -n "$declared" ] && [ "$expected" != "$declared" ]; then
fail "search form expects schema version $expected, spine writes $declared"
fi
# ── every column the search form names has to exist
# the references are qualified, table.column, which is what makes them
# findable without parsing D
echo "Checking columns named by the search form ..."
COLS=$(grep -o \
'metadata_and_text\.[a-z_]*\|doc_objects\.[a-z_]*\|topic_register\.[a-z_]*' \
"$CGI_SRC" | sort -u)
if [ -z "$COLS" ]; then
fail "no column references found in $CGI_SRC (has the source changed shape?)"
fi
count=0
for ref in $COLS; do
table=${ref%%.*}
column=${ref#*.}
[ -n "$column" ] || continue
count=$((count + 1))
found=$(sqlite3 "$DB" \
"select count(*) from pragma_table_info('$table') where name='$column';")
[ "$found" = "1" ] || fail "$table.$column named by the search form, absent from the db"
done
echo " column references checked: $count"
echo ""
if [ "$FAILURES" -eq 0 ]; then
echo "PASS: spine's search database satisfies the search form's expectations"
exit 0
else
echo "FAIL: $FAILURES mismatch(es)"
exit 1
fi
|