Skip to content

Commit 9d5a22f

Browse files
committed
1.4 (add log to realtime updates)
1 parent 86f13a8 commit 9d5a22f

File tree

2 files changed

+174
-2
lines changed

2 files changed

+174
-2
lines changed

EASY-INSTALL-V1.3.SQL renamed to EASY-INSTALL-V1.4.SQL

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
------------------------------
2-
-- Install SupaScript v1.3 ---
2+
-- Install SupaScript v1.4 ---
33
------------------------------
44
CREATE EXTENSION IF NOT EXISTS PLV8;
55
CREATE EXTENSION IF NOT EXISTS HTTP;
@@ -9,7 +9,7 @@ DROP FUNCTION IF EXISTS supascript_init();
99
SET PLV8.START_PROC = 'supascript_init';
1010
ALTER DATABASE POSTGRES SET PLV8.START_PROC TO 'supascript_init';
1111

12-
CREATE TABLE IF NOT EXISTS SUPASCRIPT_LOG
12+
CREATE TABLE IF NOT EXISTS supascript_log
1313
(
1414
id uuid primary key default uuid_generate_v4(),
1515
created timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
@@ -22,6 +22,7 @@ CREATE TABLE IF NOT EXISTS SUPASCRIPT_LOG
2222
query text,
2323
content jsonb
2424
);
25+
ALTER PUBLICATION supabase_realtime ADD TABLE supascript_log;
2526

2627
CREATE TABLE IF NOT EXISTS SUPASCRIPT_JS_MODULES (MODULE text UNIQUE PRIMARY KEY,
2728
AUTOLOAD BOOL DEFAULT FALSE,

supascript--1.4.sql

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
--complain if script is sourced in psql, rather than via CREATE EXTENSION
2+
\echo Use "CREATE EXTENSION supascript CASCADE" to load this file. \quit
3+
4+
/* the alter database line below needs to be run ONCE on your database */
5+
SET PLV8.START_PROC = 'supascript_init';
6+
ALTER DATABASE POSTGRES SET PLV8.START_PROC TO 'supascript_init';
7+
8+
CREATE TABLE IF NOT EXISTS supascript_log
9+
(
10+
id uuid primary key default uuid_generate_v4(),
11+
created timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
12+
_catalog text DEFAULT CURRENT_CATALOG,
13+
_user text DEFAULT CURRENT_USER,
14+
_schema text DEFAULT CURRENT_SCHEMA,
15+
_schemas name[] DEFAULT CURRENT_SCHEMAS(true),
16+
_pid int DEFAULT PG_BACKEND_PID(),
17+
log_type text,
18+
query text,
19+
content jsonb
20+
);
21+
ALTER PUBLICATION supabase_realtime ADD TABLE supascript_log;
22+
23+
CREATE TABLE IF NOT EXISTS SUPASCRIPT_JS_MODULES (MODULE text UNIQUE PRIMARY KEY,
24+
AUTOLOAD BOOL DEFAULT FALSE,
25+
SOURCE text);
26+
27+
CREATE OR REPLACE FUNCTION supascript_init() RETURNS VOID
28+
AS $$
29+
30+
this.moduleCache = {};
31+
32+
// this handles "TypeError: Do not know how to serialize a BigInt"
33+
function toJson(data) {
34+
if (data !== undefined) {
35+
return JSON.stringify(data, (_, v) => typeof v === 'bigint' ? `${v}#bigint` : v)
36+
.replace(/"(-?\d+)#bigint"/g, (_, a) => a);
37+
}
38+
}
39+
40+
this.console = {
41+
timers:{},
42+
write_to_log: function() {
43+
const arr = [];
44+
for (let i=0; i < arguments.length; i++) {
45+
if (!(i === 1 && arguments[0] === 'ASSERT')) {
46+
const arg = JSON.parse(toJson(arguments[i])); // required to handle bigint
47+
arr.push(arg);
48+
}
49+
}
50+
let query = '';
51+
try {
52+
query = JSON.stringify(sql('select current_query()')[0].current_query);
53+
if (query.length > 23 && query.substr(0,23).toLowerCase() === '"with pgrst_source as (') {
54+
query = query.substr(23);
55+
let index = query.indexOf('AS pgrst_scalar');
56+
if (index < 0) { index = 999; };
57+
query = query.substr(0,index);
58+
query = query.replace(new RegExp(String.fromCharCode(92, 92, 34), 'g'), String.fromCharCode(34));
59+
}
60+
} catch (queryParseError) {
61+
query = 'query parse error';
62+
}
63+
const log_type = arr.shift();
64+
sql(`insert into supascript_log (content, log_type, query) values ($1, $2, $3)`,[arr, log_type, query]);
65+
},
66+
log: function() {
67+
console.write_to_log('LOG', ...arguments);
68+
},
69+
info: function() {
70+
console.write_to_log('INFO', ...arguments);
71+
},
72+
warn: function() {
73+
console.write_to_log('WARN', ...arguments);
74+
},
75+
assert: function() {
76+
if (arguments[0] === false) {
77+
// arguments.shift(); // remove assert expression
78+
console.write_to_log('ASSERT', ...arguments); // log rest of arguments (1 to n)
79+
}
80+
},
81+
error: function() {
82+
console.write_to_log('ERROR', ...arguments);
83+
},
84+
time: function(label = 'DEFAULT_TIMER') {
85+
this.timers[label] = +new Date();
86+
},
87+
timeEnd: function(label = 'DEFAULT_TIMER') {
88+
console.write_to_log('TIMER',label,+new Date() - this.timers[label]);
89+
delete this.timers[label];
90+
}
91+
92+
};
93+
94+
95+
// execute a Postgresql function
96+
// i.e. exec('my_function',['parm1', 123, {"item_name": "test json object"}])
97+
this.exec = function(function_name, parms) {
98+
var func = plv8.find_function(function_name);
99+
return func(...parms);
100+
}
101+
102+
this.load = function(key, source) {
103+
var module = {exports: {}};
104+
try {
105+
eval("(function(module, exports) {" + source + "; })")(module, module.exports);
106+
} catch (err) {
107+
plv8.elog(ERROR, `eval error in source: ${err} (SOURCE): ${source}`);
108+
}
109+
110+
// store in cache
111+
moduleCache[key] = module.exports;
112+
return module.exports;
113+
};
114+
115+
// execute a sql statement against the Postgresql database with optional args
116+
// i.e. sql('select * from people where first_name = $1 and last_name = $2', ['John', 'Smith'])
117+
this.sql = function(sql_statement, args) {
118+
if (args) {
119+
return plv8.execute(sql_statement, args);
120+
} else {
121+
return plv8.execute(sql_statement);
122+
}
123+
};
124+
125+
// emulate node.js "require", with automatic download from the internet via CDN sites
126+
// optional autoload (boolean) parameter allows the module to be preloaded later
127+
// i.e. var myModule = require('https://some.cdn.com/module_content.js', true)
128+
this.require = function(module, autoload) {
129+
if (module === 'http' || module === 'https') {
130+
// emulate NodeJS require('http')
131+
module = 'https://raw.githubusercontent.com/burggraf/SupaScript/main/modules/http.js';
132+
}
133+
if(moduleCache[module])
134+
return moduleCache[module];
135+
var rows = plv8.execute(
136+
'select source from supascript_js_modules where module = $1',
137+
[module]
138+
);
139+
140+
if (rows.length === 0 && module.substr(0,4) === 'http') {
141+
try {
142+
source = plv8.execute(`SELECT content FROM http_get('${module}');`)[0].content;
143+
} catch (err) {
144+
plv8.elog(ERROR, `Could not load module through http: ${module}`, JSON.stringify(err));
145+
}
146+
try {
147+
/* the line below is written purely for esthetic reasons, so as not to mess up the online source editor */
148+
/* when using standard regExp expressions, the single-quote char messes up the code highlighting */
149+
/* in the editor and everything looks funky */
150+
const quotedSource = source.replace(new RegExp(String.fromCharCode(39), 'g'), String.fromCharCode(39, 39));
151+
152+
plv8.execute(`insert into supascript_js_modules (module, autoload, source) values ('${module}', ${autoload ? true : false}, '${quotedSource}')`);
153+
} catch (err) {
154+
plv8.elog(ERROR, `Error inserting module into supascript_js_modules: ${err} ${module}, ${autoload ? true : false}, '${plv8.quote_literal(source)}'`);
155+
}
156+
return load(module, source);
157+
} else if(rows.length === 0) {
158+
plv8.elog(NOTICE, `Could not load module: ${module}`);
159+
return null;
160+
} else {
161+
return load(module, rows[0].source);
162+
}
163+
164+
};
165+
166+
// Grab modules worth auto-loading at context start and let them cache
167+
var query = `select module, source from supascript_js_modules where autoload = true`;
168+
plv8.execute(query).forEach(function(row) {
169+
this.load(row.module, row.source);
170+
});
171+
$$ LANGUAGE PLV8;

0 commit comments

Comments
 (0)