2016-03-26 08:41:35 +03:00
|
|
|
-- dblink is necessary to be able to sub-transactions (autonomous
|
|
|
|
-- transactions) to the stream table. This is necessary to be able to
|
|
|
|
-- modify the stream table from the perspective of outside callers
|
|
|
|
-- because actual code can be long-lived and it's direct updates will
|
|
|
|
-- not be seen until the process completes.
|
2016-05-03 07:02:29 +03:00
|
|
|
|
|
|
|
CREATE SCHEMA io
|
|
|
|
|
|
|
|
CREATE TABLE stream (
|
|
|
|
stream_id integer,
|
|
|
|
open boolean,
|
|
|
|
data varchar,
|
|
|
|
rl_prompt varchar -- prompt for readline input
|
|
|
|
);
|
|
|
|
|
|
|
|
-- stdin
|
|
|
|
INSERT INTO io.stream (stream_id, open, data, rl_prompt)
|
|
|
|
VALUES (0, false, '', '');
|
|
|
|
-- stdout
|
|
|
|
INSERT INTO io.stream (stream_id, open, data, rl_prompt)
|
|
|
|
VALUES (1, false, '', '');
|
2016-03-26 08:41:35 +03:00
|
|
|
|
|
|
|
-- ---------------------------------------------------------
|
|
|
|
|
2016-05-03 07:02:29 +03:00
|
|
|
CREATE FUNCTION io.open(sid integer) RETURNS void AS $$
|
2016-03-26 08:41:35 +03:00
|
|
|
DECLARE
|
|
|
|
query varchar;
|
|
|
|
BEGIN
|
2016-05-03 07:02:29 +03:00
|
|
|
--RAISE NOTICE 'io.open start';
|
|
|
|
query := format('UPDATE io.stream
|
|
|
|
SET data = '''', rl_prompt = '''', open = true
|
|
|
|
WHERE stream_id = %L', sid);
|
2016-03-26 08:41:35 +03:00
|
|
|
PERFORM dblink('dbname=mal', query);
|
2016-05-03 07:02:29 +03:00
|
|
|
--RAISE NOTICE 'io.open done';
|
2016-03-26 08:41:35 +03:00
|
|
|
END; $$ LANGUAGE 'plpgsql' STRICT;
|
|
|
|
|
2016-05-03 07:02:29 +03:00
|
|
|
CREATE FUNCTION io.close(sid integer) RETURNS void AS $$
|
2016-03-26 08:41:35 +03:00
|
|
|
DECLARE
|
|
|
|
query varchar;
|
|
|
|
BEGIN
|
2016-05-03 07:02:29 +03:00
|
|
|
--RAISE NOTICE 'io.close start';
|
|
|
|
query := format('UPDATE io.stream
|
|
|
|
SET rl_prompt = '''', open = false
|
|
|
|
WHERE stream_id = %L', sid);
|
2016-03-26 08:41:35 +03:00
|
|
|
PERFORM dblink('dbname=mal', query);
|
2016-05-03 07:02:29 +03:00
|
|
|
--RAISE NOTICE 'io.close done';
|
2016-03-26 08:41:35 +03:00
|
|
|
END; $$ LANGUAGE 'plpgsql' STRICT;
|
|
|
|
|
|
|
|
|
2016-03-26 09:44:43 +03:00
|
|
|
-- called from read via dblink
|
2016-05-03 07:02:29 +03:00
|
|
|
CREATE FUNCTION io.__read(sid integer) RETURNS varchar AS $$
|
2016-03-26 08:41:35 +03:00
|
|
|
DECLARE
|
2016-05-03 07:02:29 +03:00
|
|
|
input varchar;
|
|
|
|
isopen boolean;
|
2016-03-26 08:41:35 +03:00
|
|
|
BEGIN
|
2016-05-03 07:02:29 +03:00
|
|
|
LOCK io.stream;
|
|
|
|
SELECT data, open INTO input, isopen FROM io.stream
|
|
|
|
WHERE stream_id = sid;
|
|
|
|
IF input <> '' THEN
|
|
|
|
UPDATE io.stream SET data = '' WHERE stream_id = sid;
|
|
|
|
RETURN input;
|
|
|
|
END IF;
|
|
|
|
IF isopen = false THEN
|
|
|
|
RETURN NULL;
|
2016-03-26 08:41:35 +03:00
|
|
|
END IF;
|
2016-05-03 07:02:29 +03:00
|
|
|
RETURN input;
|
2016-03-26 08:41:35 +03:00
|
|
|
END; $$ LANGUAGE 'plpgsql' STRICT;
|
|
|
|
|
|
|
|
-- read:
|
|
|
|
-- read from stream stream_id in stream table. Waits until there is
|
|
|
|
-- either data to return or the stream closes (NULL data). Returns
|
|
|
|
-- NULL when stream is closed.
|
2016-05-03 07:02:29 +03:00
|
|
|
CREATE FUNCTION io.read(sid integer DEFAULT 0) RETURNS varchar AS $$
|
2016-03-25 08:05:54 +03:00
|
|
|
DECLARE
|
|
|
|
query varchar;
|
|
|
|
input varchar;
|
|
|
|
sleep real = 0.05;
|
|
|
|
BEGIN
|
|
|
|
-- poll / wait for input
|
2016-05-03 07:02:29 +03:00
|
|
|
query := format('SELECT io.__read(%L);', sid);
|
|
|
|
|
2016-03-25 08:05:54 +03:00
|
|
|
WHILE true
|
|
|
|
LOOP
|
|
|
|
-- atomic get and set to empty
|
|
|
|
SELECT cur_data INTO input FROM dblink('dbname=mal', query)
|
|
|
|
AS t1(cur_data varchar);
|
2016-03-26 08:41:35 +03:00
|
|
|
IF input <> '' OR input IS NULL THEN
|
2016-03-25 08:05:54 +03:00
|
|
|
RETURN input;
|
|
|
|
END IF;
|
|
|
|
PERFORM pg_sleep(sleep);
|
|
|
|
IF sleep < 0.5 THEN
|
|
|
|
sleep := sleep * 1.1; -- backoff
|
|
|
|
END IF;
|
|
|
|
END LOOP;
|
2016-03-26 07:11:40 +03:00
|
|
|
END; $$ LANGUAGE 'plpgsql' STRICT;
|
2016-03-25 08:05:54 +03:00
|
|
|
|
2016-03-26 08:41:35 +03:00
|
|
|
-- read_or_error:
|
|
|
|
-- similar to read, but throws exception when stream is closed
|
2016-05-03 07:02:29 +03:00
|
|
|
CREATE FUNCTION io.read_or_error(sid integer DEFAULT 0) RETURNS varchar AS $$
|
2016-03-26 08:41:35 +03:00
|
|
|
DECLARE
|
|
|
|
input varchar;
|
|
|
|
BEGIN
|
2016-05-03 07:02:29 +03:00
|
|
|
input := io.read(sid);
|
2016-03-26 08:41:35 +03:00
|
|
|
IF input IS NULL THEN
|
2016-05-03 07:02:29 +03:00
|
|
|
raise EXCEPTION 'Stream ''%'' is closed', sid;
|
2016-03-26 08:41:35 +03:00
|
|
|
ELSE
|
|
|
|
RETURN input;
|
|
|
|
END IF;
|
|
|
|
END; $$ LANGUAGE 'plpgsql' STRICT;
|
|
|
|
|
|
|
|
|
2016-03-25 08:05:54 +03:00
|
|
|
-- readline:
|
|
|
|
-- set prompt and wait for readline style input on the stream
|
2016-05-03 07:02:29 +03:00
|
|
|
CREATE FUNCTION io.readline(prompt varchar, sid integer DEFAULT 0)
|
|
|
|
RETURNS varchar AS $$
|
2016-03-25 08:05:54 +03:00
|
|
|
DECLARE
|
|
|
|
query varchar;
|
|
|
|
BEGIN
|
|
|
|
-- set prompt / request readline style input
|
2016-05-03 07:02:29 +03:00
|
|
|
IF sid = 0 THEN
|
|
|
|
PERFORM io.wait_flushed(1);
|
|
|
|
ELSIF sid = 1 THEN
|
|
|
|
PERFORM io.wait_flushed(0);
|
|
|
|
END IF;
|
|
|
|
query := format('LOCK io.stream; UPDATE io.stream SET rl_prompt = %L',
|
|
|
|
prompt);
|
2016-03-25 08:05:54 +03:00
|
|
|
PERFORM dblink('dbname=mal', query);
|
|
|
|
|
2016-05-03 07:02:29 +03:00
|
|
|
RETURN io.read(sid);
|
2016-03-26 07:11:40 +03:00
|
|
|
END; $$ LANGUAGE 'plpgsql' STRICT;
|
2016-03-25 08:05:54 +03:00
|
|
|
|
2016-05-03 07:02:29 +03:00
|
|
|
CREATE FUNCTION io.write(data varchar, sid integer DEFAULT 1)
|
2016-03-25 08:05:54 +03:00
|
|
|
RETURNS void AS $$
|
|
|
|
DECLARE
|
|
|
|
query varchar;
|
|
|
|
BEGIN
|
2016-05-03 07:02:29 +03:00
|
|
|
query := format('LOCK io.stream;
|
|
|
|
UPDATE io.stream SET data = data || %L WHERE stream_id = %L',
|
|
|
|
data, sid);
|
2016-03-25 08:05:54 +03:00
|
|
|
--RAISE NOTICE 'write query: %', query;
|
|
|
|
PERFORM dblink('dbname=mal', query);
|
2016-03-26 07:11:40 +03:00
|
|
|
END; $$ LANGUAGE 'plpgsql' STRICT;
|
2016-03-25 08:05:54 +03:00
|
|
|
|
2016-05-03 07:02:29 +03:00
|
|
|
CREATE FUNCTION io.writeline(data varchar, sid integer DEFAULT 1)
|
2016-03-25 08:05:54 +03:00
|
|
|
RETURNS void AS $$
|
|
|
|
BEGIN
|
2016-05-03 07:02:29 +03:00
|
|
|
PERFORM io.write(data || E'\n', sid);
|
2016-03-26 07:11:40 +03:00
|
|
|
END; $$ LANGUAGE 'plpgsql' STRICT;
|
2016-03-25 08:05:54 +03:00
|
|
|
|
2016-03-26 09:44:43 +03:00
|
|
|
-- ---------------------------------------------------------
|
|
|
|
|
|
|
|
-- called from wait_rl_prompt via dblink
|
2016-05-03 07:02:29 +03:00
|
|
|
CREATE FUNCTION io.__wait_rl_prompt(sid integer) RETURNS varchar AS $$
|
2016-03-26 09:44:43 +03:00
|
|
|
DECLARE
|
2016-05-03 07:02:29 +03:00
|
|
|
isopen boolean;
|
2016-03-26 09:44:43 +03:00
|
|
|
prompt varchar;
|
2016-05-03 07:02:29 +03:00
|
|
|
datas integer;
|
2016-03-26 09:44:43 +03:00
|
|
|
BEGIN
|
2016-05-03 07:02:29 +03:00
|
|
|
LOCK io.stream;
|
|
|
|
SELECT open, rl_prompt INTO isopen, prompt FROM io.stream
|
|
|
|
WHERE stream_id = sid;
|
|
|
|
SELECT count(stream_id) INTO datas FROM io.stream WHERE data <> '';
|
|
|
|
|
|
|
|
IF isopen = false THEN
|
2016-03-26 09:44:43 +03:00
|
|
|
return NULL;
|
|
|
|
--raise EXCEPTION 'Stream ''%'' is closed', sid;
|
|
|
|
END IF;
|
2016-05-03 07:02:29 +03:00
|
|
|
|
|
|
|
IF datas = 0 AND prompt <> '' THEN
|
|
|
|
UPDATE io.stream SET rl_prompt = '' WHERE stream_id = sid;
|
2016-03-26 09:44:43 +03:00
|
|
|
-- There is pending data on some stream
|
2016-05-03 07:02:29 +03:00
|
|
|
RETURN prompt;
|
2016-03-26 09:44:43 +03:00
|
|
|
END IF;
|
2016-05-03 07:02:29 +03:00
|
|
|
RETURN ''; -- '' -> no input
|
2016-03-26 09:44:43 +03:00
|
|
|
END; $$ LANGUAGE 'plpgsql' STRICT;
|
|
|
|
|
2016-03-25 08:05:54 +03:00
|
|
|
-- wait_rl_prompt:
|
|
|
|
-- wait for rl_prompt to be set on the given stream and return the
|
2016-03-26 09:44:43 +03:00
|
|
|
-- rl_prompt value. Errors if stream is already closed.
|
2016-05-03 07:02:29 +03:00
|
|
|
CREATE FUNCTION io.wait_rl_prompt(sid integer DEFAULT 0) RETURNS varchar AS $$
|
2016-03-25 08:05:54 +03:00
|
|
|
DECLARE
|
|
|
|
query varchar;
|
|
|
|
prompt varchar;
|
|
|
|
sleep real = 0.05;
|
|
|
|
BEGIN
|
2016-05-03 07:02:29 +03:00
|
|
|
query := format('SELECT io.__wait_rl_prompt(%L);', sid);
|
2016-03-25 08:05:54 +03:00
|
|
|
WHILE true
|
|
|
|
LOOP
|
2016-03-26 09:44:43 +03:00
|
|
|
SELECT rl_prompt INTO prompt FROM dblink('dbname=mal', query)
|
|
|
|
AS t1(rl_prompt varchar);
|
|
|
|
IF prompt IS NULL THEN
|
2016-05-03 07:02:29 +03:00
|
|
|
raise EXCEPTION 'Stream ''%'' is closed', sid;
|
2016-03-26 09:44:43 +03:00
|
|
|
END IF;
|
|
|
|
IF prompt <> '' THEN
|
|
|
|
sleep := 0.05; -- reset sleep timer
|
|
|
|
RETURN prompt;
|
2016-03-25 08:05:54 +03:00
|
|
|
END IF;
|
|
|
|
PERFORM pg_sleep(sleep);
|
|
|
|
IF sleep < 0.5 THEN
|
|
|
|
sleep := sleep * 1.1; -- backoff
|
|
|
|
END IF;
|
|
|
|
END LOOP;
|
2016-03-26 07:11:40 +03:00
|
|
|
END; $$ LANGUAGE 'plpgsql' STRICT;
|
2016-03-25 08:05:54 +03:00
|
|
|
|
2016-05-03 07:02:29 +03:00
|
|
|
CREATE FUNCTION io.wait_flushed(sid integer DEFAULT 1) RETURNS void AS $$
|
2016-03-26 08:41:35 +03:00
|
|
|
DECLARE
|
|
|
|
query varchar;
|
|
|
|
pending integer;
|
|
|
|
sleep real = 0.05;
|
|
|
|
BEGIN
|
2016-05-03 07:02:29 +03:00
|
|
|
query := format('SELECT count(stream_id) FROM io.stream
|
|
|
|
WHERE stream_id = %L AND data <> ''''', sid);
|
2016-03-26 08:41:35 +03:00
|
|
|
WHILE true
|
|
|
|
LOOP
|
|
|
|
SELECT p INTO pending FROM dblink('dbname=mal', query)
|
|
|
|
AS t1(p integer);
|
|
|
|
IF pending = 0 THEN RETURN; END IF;
|
|
|
|
PERFORM pg_sleep(sleep);
|
|
|
|
IF sleep < 0.5 THEN
|
|
|
|
sleep := sleep * 1.1; -- backoff
|
|
|
|
END IF;
|
|
|
|
END LOOP;
|
|
|
|
END; $$ LANGUAGE 'plpgsql' STRICT;
|
|
|
|
|