blob: b9b429d2e12cac0167a88bbcdaf9bce5f11f4578 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* There is a per-install SQLite database containing all election data
for the site. This file defines/constructs the schema of that database.
Note that foreign key references are defined within this scheme. For
these to be enforced at runtime, you must use a PRAGMA statement:
conn.execute('PRAGMA foreign_keys = ON')
*/
/* ### $ sqlite3 steve.db < steve/v3/schema.sql
###
### OR:
### >>> import sqlite3
### >>> conn = sqlite3.connect('steve.db')
### >>> conn.executescript(open('schema.sql').read())
###
### ? maybe: conn.commit() and/or conn.close() ... the DML statements
### don't seem to require full closure of connection.
*/
/* --------------------------------------------------------------------- */
/* Various Election metadata.
An Election has three states:
1. Editable. The election is being set up. Issues and Persons of
record can be added, edited, and deleted. The Election's title
may be changed (EID is fixed, however).
DEFINITION: salt and opened_key are NULL. closed is n/a.
2. Open. The election is now open for voting.
DEFINITION: salt and opened_key are NOT NULL. closed is NULL or 0.
3. Closed. The election is closed.
DEFINITION: salt and opened_key are NOT NULL. closed is 1.
*/
CREATE TABLE election (
/* The Election ID; 10 hex characters. We do not use AUTOINCREMENT,
so that URLs for Elections cannot be deduced. */
eid TEXT
PRIMARY KEY NOT NULL
CHECK (length(eid) = 10
AND eid GLOB '[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'),
/* Title of this election. */
title TEXT NOT NULL,
/* Who is the owner/creator of this election?
Note: no need to CHECK OWNER_PID as it refers to a foreign table
where its propriety is enforced. */
owner_pid TEXT NOT NULL,
/* What authz group is allowed to edit this election? If NULL,
then only the OWNER_PID can edit. */
/* ### contents/format is TBD; think "which PMC" or "Foundation" */
authz TEXT,
/* A salt value to use for hashing this Election. 16 bytes.
This will be NULL until the Election is opened. */
salt BLOB CHECK (salt IS NULL OR length(salt) = 16),
/* If this Election has been opened for voting, then we store
the OpenedKey here to avoid recomputing. 32 bytes.
This will be NULL until the Election is opened. */
opened_key BLOB CHECK (opened_key IS NULL OR length(opened_key) = 32),
/* Has this election been closed? NULL or 0 for not-closed (see
SALT and OPENED_KEY to determine if the election has been
opened). 1 for closed (implies it was formerly-opened). */
closed INTEGER CHECK (closed IS NULL OR closed IN (0, 1)),
/* The approximate times this Election will be opened and closed,
if known. NULL if unknown. These are purely advisory, for humans,
and have no effect upon the actual Election operation. Look
to OPENED_KEY and CLOSED for the current Election state.
Note: the "prevent_open_close_update" trigger will prevent these
two values from changing once an election is closed. They never
need to be set, but once an election is closed: they are fixed. */
open_at INTEGER, /* seconds since epoch */
close_at INTEGER, /* seconds since epoch */
/* Enforce/declare/document relationships. */
FOREIGN KEY (owner_pid) REFERENCES person(pid)
ON DELETE RESTRICT
ON UPDATE NO ACTION
) STRICT;
/* For posterity, do not allow changes to the time fields, once closed. */
CREATE TRIGGER prevent_open_close_update
BEFORE UPDATE OF open_at, close_at ON election
FOR EACH ROW
WHEN OLD.closed = 1
BEGIN
SELECT RAISE(ABORT, 'Cannot modify open_at or close_at when election is closed');
END;
/* --------------------------------------------------------------------- */
/* The set of Issues to vote upon for a given Election. */
CREATE TABLE issue (
/* The Issue ID; 10 hex characters. We do not use AUTOINCREMENT,
so that URLs for Issues cannot be deduced. */
iid TEXT
PRIMARY KEY NOT NULL
CHECK (length(iid) = 10
AND iid GLOB '[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'),
/* Which election is this issue associated with?
Note: no need to CHECK EID as it refers to a foreign table where
its propriety is enforced. */
eid TEXT NOT NULL,
/* Simple one-line title for this issue. */
title TEXT NOT NULL,
/* An optional, longer description of the issue. */
description TEXT,
/* The type of this issue's vote mechanism (eg. yna, stv, ...). This
is one of an enumerated set of values.
### see <here> for the enumeration. */
type TEXT NOT NULL,
/* Per-type set of key/value pairs specifying additional data. This
value is JSON-formatted */
kv TEXT,
/* Enforce/declare/document relationships. */
FOREIGN KEY (eid) REFERENCES election(eid)
ON DELETE RESTRICT
ON UPDATE NO ACTION
) STRICT;
CREATE INDEX idx_issue_eid ON issue(eid);
/* --------------------------------------------------------------------- */
/* The set of Persons ever seen, across all Elections. */
CREATE TABLE person (
/* An id assigned to the person (eg. an LDAP username). */
pid TEXT PRIMARY KEY NOT NULL,
/* Optional human-readable name for this person. */
name TEXT,
/* How to contact this person (eg. to send a ballot link). */
email TEXT NOT NULL
) STRICT;
/* --------------------------------------------------------------------- */
/* The set of Persons who may vote on an Issue (aka eligible/allowed). */
CREATE TABLE mayvote (
/* The Person who may vote... */
pid TEXT NOT NULL,
/* ... on this Issue. */
iid TEXT NOT NULL,
/* A salt value for hashing this Person/Issue pair into a vote_token.
Also used via key-stretching to create an encryption key for the
vote values. This will be NULL until the Election (containing IID)
is opened. 16 bytes. */
salt BLOB CHECK (salt IS NULL OR length(salt) = 16),
/* The pair should be unique. */
PRIMARY KEY (pid, iid),
/* Note: no need to check PID/IID columns as they refer to a foreign
table where their propriety is enforced. */
/* Enforce/declare/document relationships. */
FOREIGN KEY (pid) REFERENCES person(pid)
ON DELETE RESTRICT
ON UPDATE NO ACTION,
FOREIGN KEY (iid) REFERENCES issue(iid)
ON DELETE RESTRICT
ON UPDATE NO ACTION
) STRICT;
/* --------------------------------------------------------------------- */
/* The registered votes, once the Election has been opened. Note that
duplicates of (person, issue) may occur (the vote_token will be the
same), as re-voting is allowed. Only the latest is used. */
CREATE TABLE vote (
/* The key is auto-incrementing to provide a record of insert-order,
so that we have an ordering to find the "most recent" when
re-voting on an issue.
Note: an integer primary key is an alias for _ROWID_. */
vid INTEGER PRIMARY KEY AUTOINCREMENT,
/* A hashed-based token (32 bytes) based on a (Person, Issue) pair
from the MAYVOTE table. Used to produce a key for encryption. */
vote_token BLOB NOT NULL CHECK (length(vote_token) = 32),
/* An encrypted form of the vote. */
ciphertext BLOB NOT NULL
) STRICT;
/* ### review queries.yaml to figure out proper indexes */
CREATE INDEX idx_by_vote_token ON vote (vote_token);
/* --------------------------------------------------------------------- */