-- $HeadURL$ $LastChangedRevision$

PRAGMA FOREIGN_KEYS = on;

-- define 'true' and 'false' as valid_logicals
CREATE TABLE valid_logicals (
       logical CHAR,
       PRIMARY KEY (logical),
       CHECK (logical = 'true' OR logical == 'false')
);
INSERT INTO valid_logicals VALUES ('true');
INSERT INTO valid_logicals VALUES ('false');

-- define 'pending', 'running' and 'completed', 'cancelled' as allowable job states
CREATE TABLE valid_job_states (
       job_state CHAR,
       PRIMARY KEY (job_state),
       CHECK (job_state = 'pending' OR job_state == 'running' OR job_state == 'completed' OR job_state == 'cancelled')
);
INSERT INTO valid_job_states VALUES ('pending');
INSERT INTO valid_job_states VALUES ('running');
INSERT INTO valid_job_states VALUES ('completed');
INSERT INTO valid_job_states VALUES ('cancelled');

CREATE TABLE resources (
       resource_id CHAR,
       total INT NOT NULL,
       PRIMARY KEY (resource_id)
);

-- jobs (ids assigned automatically), the actual command that is the job, and the job's state
CREATE TABLE jobs (
    job_id           INTEGER,
    job_state        CHAR DEFAULT 'pending',
    priority         INTEGER default 0,
    job_name         CHAR,
    command          CHAR,
    submit_timestamp INTEGER,
    [user]           TEXT,
    pid              INTEGER default 0,
    job_rc           INTEGER default 0,
    PRIMARY KEY (job_id),
    FOREIGN KEY (job_state) REFERENCES valid_job_states(job_state)
);

CREATE TABLE job_resource_requirements (
       job_id INT, 
       resource_id CHAR,
       requirement INT NOT NULL,
       PRIMARY KEY (job_id, resource_id),
       FOREIGN KEY (job_id) REFERENCES jobs(job_id),
       FOREIGN KEY (resource_id) REFERENCES resources(resource_id)
);

CREATE TABLE in_use_resource_instances (
       resource_id CHAR,
       instance_id INT,
       job_id INT NOT NULL,
       PRIMARY KEY (resource_id, instance_id),
       FOREIGN KEY (job_id, resource_id) REFERENCES job_resource_requirements(job_id, resource_id)
);

CREATE VIEW in_use_resource_quantities AS 
       SELECT   resources.resource_id, COALESCE(x.total,0) AS total 
       FROM     resources LEFT OUTER JOIN 
                (SELECT   resource_id, COUNT(*) AS total 
                 FROM     in_use_resource_instances 
                 GROUP BY resource_id) AS x 
       ON       resources.resource_id = x.resource_id;
CREATE VIEW free_resource_quantities AS 
       SELECT   resources.resource_id, resources.total-in_use_resource_quantities.total AS total 
       FROM     in_use_resource_quantities, resources 
       WHERE    in_use_resource_quantities.resource_id = resources.resource_id;
CREATE VIEW runnable_jobs AS 
       SELECT   jobs.job_id 
       FROM     jobs, job_resource_requirements,free_resource_quantities 
       WHERE    jobs.job_id = job_resource_requirements.job_id AND
                job_resource_requirements.resource_id = free_resource_quantities.resource_id AND
                jobs.job_state = 'pending' AND
                requirement <= total 
       GROUP BY jobs.job_id
       HAVING   count(*) == (SELECT COUNT(*) FROM resources)
       ORDER BY priority DESC, jobs.job_id;
CREATE VIEW next_runnable_job as 
       SELECT   * 
       FROM     runnable_jobs 
       LIMIT    1;

CREATE VIEW all_resource_instances AS 
       SELECT resource_id, y.value AS instance_id 
       FROM     resources,
                -- sqlite3 shell has support for GENERATE_SERIES() but 
                -- the DBI/DBD modules do not, or rather Debian still lacks
                -- the DBD::SQLite::BundledExtensions perl module. If it
                -- had support we could use this here:
                -- 
                --     GENERATE_SERIES(1,(SELECT MAX(total) FROM resources),1) AS y 
                -- 
                -- but it doesn't so we need to use this (and the i1000000 table
                -- above):
                (WITH RECURSIVE cnt(value) AS (VALUES(0) UNION ALL SELECT value+1 FROM cnt WHERE value<1000000) SELECT value FROM cnt) as y
       WHERE    y.value <= total;
CREATE VIEW free_resource_instances AS 
       SELECT   all_resource_instances.* 
       FROM     all_resource_instances LEFT OUTER JOIN 
                in_use_resource_instances 
       ON       all_resource_instances.resource_id = in_use_resource_instances.resource_id AND 
                all_resource_instances.instance_id = in_use_resource_instances.instance_id 
       WHERE    in_use_resource_instances.instance_id IS NULL; /* could have used job_id being null */
CREATE VIEW next_runnable_job_resource_instances AS 
       SELECT   b.job_id,b.resource_id,a.instance_id 
       FROM     (SELECT   *, row_number() OVER (partition by resource_id ORDER BY instance_id) AS seqid 
                 FROM     free_resource_instances) AS a, 
                (select * FROM job_resource_requirements) AS b, 
                next_runnable_job AS c 
       WHERE    b.job_id = c.job_id AND
                a.resource_id = b.resource_id AND 
                seqid <= requirement 
       ORDER BY b.job_id,a.resource_id,instance_id;
