Skip to main content

Overview

The query command allows you to execute arbitrary SQL queries against your local blockchain database. Results are automatically formatted as readable tables, making it easy to analyze election data, aggregate votes, and explore the blockchain state.

Syntax

ubu-block --config <CONFIG_FILE> query -q "<SQL_QUERY>"

Parameters

-q, --query
string
required
The SQL query to execute against the blockchain database. Must be a valid SQLite query.Note: Enclose the entire query in quotes to prevent shell interpretation of special characters.

Database Schema

The blockchain database contains several key tables:

Core Tables

  • blocks: Contains all blockchain blocks
  • results: Stores election results from each block
  • stations: Polling station information
  • candidates: Candidate details
  • parties: Political party information
  • wards: Ward-level regional data
  • constituencies: Constituency-level regional data
  • counties: County-level regional data

Schema Details

-- Results table (main data)
results (
  id INTEGER PRIMARY KEY,
  station_id INTEGER,
  candidate_id INTEGER,
  votes INTEGER,
  block_height INTEGER
)

-- Stations table
stations (
  id INTEGER PRIMARY KEY,
  name TEXT,
  ward_code TEXT
)

-- Candidates table
candidates (
  id INTEGER PRIMARY KEY,
  name TEXT,
  party_id INTEGER
)

Examples

Simple Query

Query all results:
ubu-block --config config.toml query -q "SELECT * FROM results"

Aggregate by Candidate

Get total votes per candidate in a constituency:
ubu-block --config config.toml query -q "SELECT
  c.name as candidate,
  SUM(votes) as votes,
  ward_name as ward,
  constituency_name as constituency,
  county_name as county,
  parties.title as party
FROM
  results
  INNER JOIN stations ON stations.id = results.station_id
  INNER JOIN candidates c ON c.id = results.candidate_id
  INNER JOIN wards on stations.ward_code = wards.ward_code
  INNER JOIN parties ON parties.id = c.party_id
  INNER JOIN constituencies ON wards.constituency_code = constituencies.constituency_code
  INNER JOIN counties ON constituencies.county_code = counties.county_code
WHERE
  position_type = 'Mp' and constituency = 'Juja'
GROUP BY candidate"
Expected Output:
+--------+--------------+----------+-----------+-------+-------+
| county | constituency |   ward   | candidate | party | votes |
+--------+--------------+----------+-----------+-------+-------+
| Kiambu |     Juja     | Kalimoni |   Omosh   |  ODM  |  21   |
| Kiambu |     Juja     | Kalimoni |   Mwas    |  PNU  |  66   |
+--------+--------------+----------+-----------+-------+-------+

Count Blocks

Check total number of blocks:
ubu-block --config config.toml query -q "SELECT COUNT(*) as total_blocks FROM blocks"

Latest Results

Get most recent submissions:
ubu-block --config config.toml query -q "SELECT 
  station_id,
  candidate_id,
  votes,
  block_height
FROM results
ORDER BY block_height DESC
LIMIT 10"

Vote Summary by County

ubu-block --config config.toml query -q "SELECT
  county_name,
  COUNT(DISTINCT station_id) as stations_reporting,
  SUM(votes) as total_votes
FROM results
  INNER JOIN stations ON stations.id = results.station_id
  INNER JOIN wards ON stations.ward_code = wards.ward_code
  INNER JOIN constituencies ON wards.constituency_code = constituencies.constituency_code
  INNER JOIN counties ON constituencies.county_code = counties.county_code
GROUP BY county_name
ORDER BY total_votes DESC"

Candidate Performance by Ward

ubu-block --config config.toml query -q "SELECT
  ward_name,
  c.name as candidate,
  SUM(votes) as votes
FROM results
  INNER JOIN stations ON stations.id = results.station_id
  INNER JOIN candidates c ON c.id = results.candidate_id
  INNER JOIN wards ON stations.ward_code = wards.ward_code
WHERE c.id = 1
GROUP BY ward_name
ORDER BY votes DESC"

Query Requirements

Temporary Limitation: For queries displaying election results, you must return these specific columns: candidate, votes, ward, constituency, county, party. This limitation will be removed in future versions.
Required columns for result queries:
  • candidate - Candidate name
  • votes - Vote count
  • ward - Ward name
  • constituency - Constituency name
  • county - County name
  • party - Party name or abbreviation

Table Formatting

Query results are automatically formatted using the tabled library:
  • Headers: Column names are displayed in the first row
  • Alignment: Numbers are right-aligned, text is left-aligned
  • Borders: ASCII borders separate columns and rows
  • Spacing: Automatic column width adjustment

Supported SQL Features

As Ubu-Block uses SQLite, you have access to:

Standard Operations

  • SELECT, FROM, WHERE, GROUP BY, ORDER BY
  • INNER JOIN, LEFT JOIN, RIGHT JOIN
  • Aggregate functions: COUNT(), SUM(), AVG(), MIN(), MAX()
  • String functions: LIKE, UPPER(), LOWER(), SUBSTR()
  • Math operations: +, -, *, /, %

SQLite-Specific

  • LIMIT and OFFSET for pagination
  • DISTINCT for unique values
  • CASE statements for conditional logic
  • Common Table Expressions (CTEs) with WITH
  • Window functions (SQLite 3.25+)
Read-Only: The query command only reads data. It cannot modify the blockchain using UPDATE, DELETE, or INSERT statements. The blockchain is immutable and changes only through the submit command.

Use Cases

Aggregate and analyze election results across different geographic levels (station, ward, constituency, county).
Compare submitted votes against registered voter counts to calculate turnout percentages.
Identify anomalies, outliers, or suspicious patterns in submitted results.
Generate reports for media, observers, and stakeholders with formatted data.
Examine blockchain structure, block contents, and submission history.

Advanced Queries

Results Timeline

Track when results were submitted:
SELECT 
  b.height,
  b.timestamp,
  COUNT(r.id) as results_in_block,
  SUM(r.votes) as total_votes
FROM blocks b
  LEFT JOIN results r ON r.block_height = b.height
GROUP BY b.height
ORDER BY b.height

Duplicate Detection

Find stations that submitted multiple times:
SELECT 
  station_id,
  candidate_id,
  COUNT(*) as submission_count
FROM results
GROUP BY station_id, candidate_id
HAVING submission_count > 1

Block Statistics

SELECT
  COUNT(*) as total_blocks,
  MIN(height) as first_block,
  MAX(height) as latest_block,
  (SELECT COUNT(DISTINCT station_id) FROM results) as stations_reported,
  (SELECT SUM(votes) FROM results) as total_votes_cast
FROM blocks

Error Handling

SQL Syntax Error

Error: Could not query
Caused by: near "FORM": syntax error
Solution: Check your SQL syntax. Common issues include:
  • Typos in keywords (e.g., FORM instead of FROM)
  • Missing quotes around strings
  • Unmatched parentheses
  • Invalid column names

Table Not Found

Error: no such table: result
Solution: Verify table names match the schema exactly. Table names are case-sensitive in some SQLite configurations.

Column Not Found

Error: no such column: vote
Solution: Check column names in your schema. Use PRAGMA table_info(table_name) to list columns.

Performance Tips

  1. Use Indexes: Key columns are indexed, so filtering on station_id, candidate_id, or block_height is fast
  2. Limit Results: Use LIMIT for large datasets to avoid overwhelming output
  3. Aggregate Early: Use WHERE clauses before GROUP BY to reduce data processed
  4. Avoid SELECT*: Specify only needed columns for better performance

Troubleshooting

Empty Results

If queries return no data:
  • Confirm blocks have been added with SELECT COUNT(*) FROM blocks
  • Check if results were submitted with SELECT COUNT(*) FROM results
  • Verify joins are correct (use LEFT JOIN to debug)

Incorrect Values

If results don’t match expectations:
  • Validate data in individual tables first
  • Check for NULL values affecting aggregations
  • Verify foreign key relationships are correct

Implementation Details

The query command is implemented in apps/cli/src/query.rs:13-22 and:
  • Connects to the main blockchain database (read-only)
  • Uses sqlx for async database operations
  • Formats output with the tabled crate
  • Supports any valid SQLite query
  • Returns structured data as VoteResult for special formatting

Next Steps

Validate Chain

Verify blockchain integrity

Database Schema

Explore the complete database schema

REST API

Query data via HTTP API

Submit Results

Add more data to query

Build docs developers (and LLMs) love