1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2026-01-22 10:54:46 +00:00

Compare commits

...

76 Commits

Author SHA1 Message Date
Jeremy Ruston
6983564194 Merge branch 'master' into demo-alternate-store 2023-12-11 20:39:57 +00:00
Jeremy Ruston
4897248809 Fix indentation
Thanks @pmario
2023-11-10 21:29:40 +00:00
Jeremy Ruston
5d20e983b6 Refactor filter compilation into its own source file 2023-11-08 09:29:57 +00:00
Jeremy Ruston
6997c61bc1 Refactor filter compilation to allow SQL engine to optimise queries 2023-11-07 21:08:58 +00:00
Jeremy Ruston
d7f0c5cb6b Minor refactoring 2023-11-07 20:44:55 +00:00
Jeremy Ruston
02f3065e4f Revert attempt at optimising filter execution
At the moment the optimiser returns a list of chainable functions, it would be simpler to just return a single function
2023-11-07 10:34:56 +00:00
Jeremy Ruston
9493084f95 Merge branch 'master' into demo-alternate-store 2023-11-07 10:26:53 +00:00
Jeremy Ruston
8c1f7a6928 SQL Console: Add number of rows returned 2023-10-30 10:03:14 +00:00
Jeremy Ruston
d98265868e Remove all plugins to simplify benchmarking 2023-10-27 14:11:07 +01:00
Jeremy Ruston
e4af21a169 Don't use LEFT JOIN unless we have to 2023-10-27 14:10:30 +01:00
Jeremy Ruston
a58f119c50 Enable performance instrumentation 2023-10-27 10:31:58 +01:00
Jeremy Ruston
01e1882083 More indexes 2023-10-27 10:31:39 +01:00
Jeremy Ruston
e9d640b61b Fix sqlAllTitles wrongly including shadow tiddlers 2023-10-25 18:02:56 +01:00
Jeremy Ruston
3a4f5b8cfc Cache allTitles and allShadowTitles 2023-10-25 18:02:39 +01:00
Jeremy Ruston
dc94ed8be8 Refactor indexer implementation
Previously, we were using the existing addIndexer method to piggyback adding our own internal indexers.
2023-10-25 17:49:13 +01:00
Jeremy Ruston
12c6cb35a0 Add indexes for columns used in joins
Doesn't actually appear to make any appreciable difference
2023-10-25 11:38:08 +01:00
Jeremy Ruston
f49b9faab0 Move tags into their own tables
This roughly halves the bootup time of the prerelease wiki
2023-10-25 11:29:38 +01:00
Jeremy Ruston
d4dec0ca65 Use the empty string as special plugin name for ordinary tiddlers
Using NULL was working against the grain of SQL
2023-10-24 18:41:34 +01:00
Jeremy Ruston
863066d41f Merge branch 'master' into demo-alternate-store 2023-10-23 10:13:44 +01:00
Jeremy Ruston
fd3d8aef36 Merge branch 'master' into demo-alternate-store 2023-10-21 21:10:08 +01:00
Jeremy Ruston
c52014c66f Simplify the SQL schema
And introduce some very simple tests
2023-10-21 12:00:51 +01:00
Jeremy Ruston
1754be279f Merge branch 'master' into demo-alternate-store 2023-10-15 18:11:28 +01:00
Jeremy Ruston
25138ecd04 Merge branch 'master' into demo-alternate-store 2023-09-19 21:58:33 +01:00
Jeremy Ruston
66cba18e0f Merge branch 'master' into demo-alternate-store 2023-08-22 17:47:57 +01:00
Jeremy Ruston
e6309e95c9 Fix tag collation syntax 2023-07-29 11:48:35 +01:00
Jeremy Ruston
bb41ae0c9b Merge branch 'master' into demo-alternate-store 2023-07-28 11:42:40 +01:00
Jeremy Ruston
7eeaa20e7e Merge branch 'master' into demo-alternate-store 2023-07-22 11:06:06 +01:00
Jeremy Ruston
39d04517dd Experiment with optimising specific filters with direct SQL equivalents 2023-07-21 13:21:04 +01:00
Jeremy Ruston
b9245da91f Sort tag lookups according to TW semantics
Perhaps it would be better to keep the tags in the desired order in the database...
2023-07-20 19:30:02 +01:00
Jeremy Ruston
7fd2dd5a3e Wire up the tag indexer properly
Improves rendering performance by a factor of 4, but we're still 5 times slower than the plain JS store
2023-07-19 21:52:28 +01:00
Jeremy Ruston
2d3027faab Sql console styling 2023-07-19 19:52:44 +01:00
Jeremy Ruston
2cd2a057f8 Fix tag saving 2023-07-19 19:52:34 +01:00
Jeremy Ruston
09b0e2815c Style update for sql console 2023-07-19 19:19:57 +01:00
Jeremy Ruston
b4fe89657b Styling for SQL console 2023-07-19 18:08:35 +01:00
Jeremy Ruston
88c8c2c9f3 SQL console - process query on enter key 2023-07-19 09:04:38 +01:00
Jeremy Ruston
979a1f746d Introduce sql console
Very bare bones, but functional. Results are displayed in JSON for the moment. The console should also perhaps be hidden by default, with a keyboard shortcut, and a setting in local storage.
2023-07-18 21:54:01 +01:00
Jeremy Ruston
83e7d32c8e Merge branch 'master' into demo-alternate-store 2023-07-18 19:33:31 +01:00
Jeremy Ruston
cc2cd20e32 Add tags tables and tag indexer and make custom collator be optional
This commit (a) is very much work in progress (b) improves performance significantly and (c) is actually broken

Right now, theme stylesheets don't get loaded for some reason.

I plan to spend some time improving debuggability by adding a SQL console
2023-07-18 19:33:21 +01:00
Jeremy Ruston
709669b714 Merge branch 'master' into demo-alternate-store 2023-07-14 12:42:14 +01:00
Jeremy Ruston
f48bddb167 Merge branch 'master' into demo-alternate-store 2023-07-13 19:28:02 +01:00
Jeremy Ruston
e3255a4d2a Merge branch 'master' into demo-alternate-store 2023-07-13 17:57:42 +01:00
Jeremy Ruston
b557deac79 Update comment 2023-07-07 10:15:40 +01:00
Jeremy Ruston
64ffa52da9 Write tiddlers with string fields
Otherwise date fields will get saved as JS date objects, which are not properly defined in JSON.
2023-07-07 08:22:36 +01:00
Jeremy Ruston
d2e21ddd3c Add a custom collator that matches JS ordering 2023-07-07 08:20:57 +01:00
jeremy@jermolene.com
f3bc32a2e9 Merge branch 'master' into demo-alternate-store 2023-07-06 11:55:38 +01:00
jeremy@jermolene.com
21ef2d7646 Make the tests work in the browser 2023-07-06 11:55:32 +01:00
jeremy@jermolene.com
9e190a46db Use a temporary database so that multiple wiki stores can coexist 2023-07-06 11:54:03 +01:00
jeremy@jermolene.com
2d229e2159 Add logging utility 2023-07-06 11:53:41 +01:00
Jeremy Ruston
71c02e57c0 Merge branch 'master' into demo-alternate-store 2023-07-01 14:38:47 +01:00
jeremy@jermolene.com
687b1df0c3 Merge branch 'master' into demo-alternate-store 2023-06-30 10:50:38 +01:00
jeremy@jermolene.com
1f4be3e92f I experimented with custom collations to match JS sort order, but 5x slower 2023-06-30 10:46:27 +01:00
Jeremy Ruston
b29af447e5 Fix typo 2023-06-29 22:49:08 +01:00
jeremy@jermolene.com
6ded5e68bf Merge branch 'master' into demo-alternate-store 2023-06-29 15:49:04 +01:00
Jeremy Ruston
87213f2c65 Refactpr sql-wiki-store into two files 2023-06-29 07:55:59 +01:00
Jeremy Ruston
12a19bb9cf Remove instrumentation
Makes the code complex and is hard to keep up to date
2023-06-28 17:27:01 +01:00
jeremy@jermolene.com
2099c4f9a8 Turn on performance instrumentation for testing 2023-06-28 15:04:44 +01:00
jeremy@jermolene.com
8399538814 Merge branch 'master' into demo-alternate-store 2023-06-28 11:45:42 +01:00
jeremy@jermolene.com
ede5f1e9ad Prepare the save tiddler query 2023-06-28 11:43:57 +01:00
jeremy@jermolene.com
9cb8721343 Use sql functions for processing shadow tiddlers 2023-06-27 19:10:42 +01:00
jeremy@jermolene.com
7e6072611e Wire the sql functions into the wiki object
At this point, the result is incredibly, painfully slow – the wiki will probably fail to load entirely on mobile
2023-06-25 21:05:28 +01:00
jeremy@jermolene.com
9ac21f167f Simplify the plain JS store implementation
Removing indexers and title cache
2023-06-25 17:03:28 +01:00
jeremy@jermolene.com
9427cf7ac6 Additional method to retrieve all titles 2023-06-23 17:33:35 +01:00
jeremy@jermolene.com
8690936805 Cleanup and clarify 2023-06-23 16:58:46 +01:00
jeremy@jermolene.com
831fb3996d Tiddler sql function tests should show custom field 2023-06-23 14:52:14 +01:00
jeremy@jermolene.com
c43bc8f92a Start prototyping some tiddler operations in sql 2023-06-23 14:47:35 +01:00
jeremy@jermolene.com
6f24f33a3d Include sqlite3 in the empty edition
Makes for an empty size of 4.1MB
2023-06-23 12:42:32 +01:00
jeremy@jermolene.com
449e2274e2 Test code to exercise the database 2023-06-23 12:40:59 +01:00
jeremy@jermolene.com
146a22b894 Make rawmarkup code dynamically load dependencies
empty.html with the plugin is now 4.1MB
2023-06-22 15:24:39 +01:00
jeremy@jermolene.com
0546a14201 Restructure the sqlite3 store as a separate plugin 2023-06-22 15:04:55 +01:00
jeremy@jermolene.com
544e079033 Proof of concept of instantiating sqlite3 without needing external dependencies
We get a reference to sqlite3 but we're not yet doing anything with it

Also note that this approach leads to duplication - there will be two copies of sqlite3.js and sqlite3.wasm in each generated HTML file. The plan is to dynamically retrieve those tiddlers from the store area rather than baking them into the raw markup area
2023-06-22 14:27:37 +01:00
jeremy@jermolene.com
1d0b9280d8 Incorporate @joshuafontany's plain JS wiki implementation
And make the demo storage areas switchable

@joshuafontany's implementation was in #7521
2023-06-22 09:36:25 +01:00
jeremy@jermolene.com
3561318515 Merge branch 'master' into demo-alternate-store 2023-06-22 09:17:55 +01:00
jeremy@jermolene.com
b6bc197f76 Remove unneeded override
See https://github.com/Jermolene/TiddlyWiki5/pull/7329/files#r1126709318
2023-05-10 22:27:40 +01:00
jeremy@jermolene.com
62337101c3 Merge branch 'master' into demo-alternate-store 2023-05-10 22:23:35 +01:00
jeremy@jermolene.com
7fdd8a5164 Merge branch 'master' into demo-alternate-store 2023-05-10 22:11:55 +01:00
jeremy@jermolene.com
fdec12f43b Initial commit
This is the very barebones beginnings of a demo implementation of an alternate tiddler store. It is not functional. If using the Vercel builds, open developer tools in the browser to see it failing due to the absence of basic wiki methods.

The plan is to build it up into the smallest possible plain JS wiki store implementation, sharing as much implementation as possible with the existing core implementation with as little code duplication as possible. It could then serve as the basis for future experiments with wiki stores based on SQLite (@linonetwo), or a custom append only database (@yaisog).
2023-03-04 21:08:00 +00:00
29 changed files with 13595 additions and 104 deletions

View File

@@ -1096,6 +1096,39 @@ $tw.Tiddler.prototype.isEqual = function(tiddler,excludeFields) {
return differences.length === 0;
};
$tw.Tiddler.prototype.getFieldString = function(field,defaultValue) {
var value = this.fields[field];
// Check for a missing field
if(value === undefined || value === null) {
return defaultValue || "";
}
// Stringify the field with the associated tiddler field module (if any)
var fieldModule = $tw.Tiddler.fieldModules[field];
if(fieldModule && fieldModule.stringify) {
return fieldModule.stringify.call(this,value);
} else {
return value.toString();
}
};
/*
Get all the fields as a hashmap of strings. Options:
exclude: an array of field names to exclude
*/
$tw.Tiddler.prototype.getFieldStrings = function(options) {
options = options || {};
var exclude = options.exclude || [];
var fields = {};
for(var field in this.fields) {
if($tw.utils.hop(this.fields,field)) {
if(exclude.indexOf(field) === -1) {
fields[field] = this.getFieldString(field);
}
}
}
return fields;
};
/*
Register and install the built in tiddler field modules
*/
@@ -1132,7 +1165,7 @@ Wiki constructor. State is stored in private members that only a small number of
options include:
enableIndexers - Array of indexer names to enable, or null to use all available indexers
*/
$tw.Wiki = function(options) {
$tw.Wiki = $tw.Wiki || function(options) {
options = options || {};
var self = this,
tiddlers = Object.create(null), // Hashmap of tiddlers
@@ -1494,7 +1527,7 @@ $tw.Wiki.prototype.clearGlobalCache =
$tw.Wiki.prototype.enqueueTiddlerEvent = function() {};
// Add an array of tiddlers
$tw.Wiki.prototype.addTiddlers = function(tiddlers) {
$tw.Wiki.prototype.addTiddlers = $tw.Wiki.prototype.addTiddlers || function(tiddlers) {
for(var t=0; t<tiddlers.length; t++) {
this.addTiddler(tiddlers[t]);
}
@@ -1503,7 +1536,7 @@ $tw.Wiki.prototype.addTiddlers = function(tiddlers) {
/*
Define all modules stored in ordinary tiddlers
*/
$tw.Wiki.prototype.defineTiddlerModules = function() {
$tw.Wiki.prototype.defineTiddlerModules = $tw.Wiki.prototype.defineTiddlerModules || function() {
this.each(function(tiddler,title) {
if(tiddler.hasField("module-type")) {
switch (tiddler.fields.type) {
@@ -1527,7 +1560,7 @@ $tw.Wiki.prototype.defineTiddlerModules = function() {
/*
Register all the module tiddlers that have a module type
*/
$tw.Wiki.prototype.defineShadowModules = function() {
$tw.Wiki.prototype.defineShadowModules = $tw.Wiki.prototype.defineShadowModules || function() {
var self = this;
this.eachShadow(function(tiddler,title) {
// Don't define the module if it is overidden by an ordinary tiddler
@@ -1541,7 +1574,7 @@ $tw.Wiki.prototype.defineShadowModules = function() {
/*
Enable safe mode by deleting any tiddlers that override a shadow tiddler
*/
$tw.Wiki.prototype.processSafeMode = function() {
$tw.Wiki.prototype.processSafeMode = $tw.Wiki.prototype.processSafeMode || function() {
var self = this,
overrides = [];
// Find the overriding tiddlers
@@ -1572,7 +1605,7 @@ $tw.Wiki.prototype.processSafeMode = function() {
/*
Extracts tiddlers from a typed block of text, specifying default field values
*/
$tw.Wiki.prototype.deserializeTiddlers = function(type,text,srcFields,options) {
$tw.Wiki.prototype.deserializeTiddlers = $tw.Wiki.prototype.deserializeTiddlers || function(type,text,srcFields,options) {
srcFields = srcFields || Object.create(null);
options = options || {};
var deserializer = $tw.Wiki.tiddlerDeserializerModules[options.deserializer],
@@ -2438,6 +2471,7 @@ $tw.boot.initStartup = function(options) {
$tw.utils.registerFileType("image/svg+xml","utf8",".svg",{flags:["image"]});
$tw.utils.registerFileType("image/vnd.microsoft.icon","base64",".ico",{flags:["image"]});
$tw.utils.registerFileType("image/x-icon","base64",".ico",{flags:["image"]});
$tw.utils.registerFileType("application/wasm","base64",".wasm");
$tw.utils.registerFileType("application/font-woff","base64",".woff");
$tw.utils.registerFileType("application/x-font-ttf","base64",".woff");
$tw.utils.registerFileType("application/font-woff2","base64",".woff2");

View File

@@ -171,7 +171,7 @@ exports.parseFilter = function(filterString) {
}
if(match[3]) {
operation.suffixes = [];
$tw.utils.each(match[3].split(":"),function(subsuffix) {
$tw.utils.each(match[3].split(":"),function(subsuffix) {
operation.suffixes.push([]);
$tw.utils.each(subsuffix.split(","),function(entry) {
entry = $tw.utils.trim(entry);
@@ -179,7 +179,7 @@ exports.parseFilter = function(filterString) {
operation.suffixes[operation.suffixes.length -1].push(entry);
}
});
});
});
}
}
if(match[4]) { // Opening square bracket
@@ -225,13 +225,17 @@ source: an iterator function for the source tiddlers, called source(iterator), w
widget: an optional widget node for retrieving the current tiddler etc.
*/
exports.compileFilter = function(filterString) {
var self = this;
// Set up the filter function cache
if(!this.filterCache) {
this.filterCache = Object.create(null);
this.filterCacheCount = 0;
}
// Use the cached version of this filter function if it exists
if(this.filterCache[filterString] !== undefined) {
return this.filterCache[filterString];
}
// Parse the filter string
var filterParseTree;
try {
filterParseTree = this.parseFilter(filterString);
@@ -241,10 +245,42 @@ exports.compileFilter = function(filterString) {
return [$tw.language.getString("Error/Filter") + ": " + e];
};
}
// Get the filter function
var fnFilter = this.optimiseFilter && this.optimiseFilter(filterString,filterParseTree);
if(!fnFilter) {
fnFilter = this.compileFilterToJavaScript(filterParseTree);
}
// Add recursion detection
var fnGuardedFilter = function guardedFilterFunction(source,widget) {
var results;
self.filterRecursionCount = (self.filterRecursionCount || 0) + 1;
if(self.filterRecursionCount < MAX_FILTER_DEPTH) {
results = fnFilter(source,widget);
} else {
results = ["/**-- Excessive filter recursion --**/"];
}
self.filterRecursionCount = self.filterRecursionCount - 1;
return results;
}
// Add performance measurement
var fnMeasured = $tw.perf.measure("filter: " + filterString,fnGuardedFilter);
// Cache the final filter function
if(this.filterCacheCount >= 2000) {
// To prevent memory leak, we maintain an upper limit for cache size.
// Reset if exceeded. This should give us 95% of the benefit
// that no cache limit would give us.
this.filterCache = Object.create(null);
this.filterCacheCount = 0;
}
this.filterCache[filterString] = fnMeasured;
this.filterCacheCount++;
return fnMeasured;
};
exports.compileFilterToJavaScript = function(filterParseTree) {
var operationFunctions = [];
// Get the hashmap of filter operator functions
var filterOperators = this.getFilterOperators();
// Assemble array of functions, one for each operation
var operationFunctions = [];
// Step through the operations
var self = this;
$tw.utils.each(filterParseTree,function(operation) {
@@ -334,8 +370,8 @@ exports.compileFilter = function(filterString) {
}
})());
});
// Return a function that applies the operations to a source iterator of tiddler titles
var fnMeasured = $tw.perf.measure("filter: " + filterString,function filterFunction(source,widget) {
// Make the filter function
return function filterFunction(source,widget) {
if(!source) {
source = self.each;
} else if(typeof source === "object") { // Array or hashmap
@@ -345,27 +381,12 @@ exports.compileFilter = function(filterString) {
widget = $tw.rootWidget;
}
var results = new $tw.utils.LinkedList();
self.filterRecursionCount = (self.filterRecursionCount || 0) + 1;
if(self.filterRecursionCount < MAX_FILTER_DEPTH) {
$tw.utils.each(operationFunctions,function(operationFunction) {
operationFunction(results,source,widget);
});
} else {
results.push("/**-- Excessive filter recursion --**/");
}
self.filterRecursionCount = self.filterRecursionCount - 1;
$tw.utils.each(operationFunctions,function(operationFunction) {
operationFunction(results,source,widget);
});
return results.toArray();
});
if(this.filterCacheCount >= 2000) {
// To prevent memory leak, we maintain an upper limit for cache size.
// Reset if exceeded. This should give us 95% of the benefit
// that no cache limit would give us.
this.filterCache = Object.create(null);
this.filterCacheCount = 0;
}
this.filterCache[filterString] = fnMeasured;
this.filterCacheCount++;
return fnMeasured;
};
};
})();

View File

@@ -24,21 +24,6 @@ exports.isDraft = function() {
return this.hasField("draft.of");
};
exports.getFieldString = function(field,defaultValue) {
var value = this.fields[field];
// Check for a missing field
if(value === undefined || value === null) {
return defaultValue || "";
}
// Stringify the field with the associated tiddler field module (if any)
var fieldModule = $tw.Tiddler.fieldModules[field];
if(fieldModule && fieldModule.stringify) {
return fieldModule.stringify.call(this,value);
} else {
return value.toString();
}
};
/*
Get the value of a field as a list
*/
@@ -51,24 +36,6 @@ exports.getFieldList = function(field) {
return $tw.utils.parseStringArray(value);
};
/*
Get all the fields as a hashmap of strings. Options:
exclude: an array of field names to exclude
*/
exports.getFieldStrings = function(options) {
options = options || {};
var exclude = options.exclude || [];
var fields = {};
for(var field in this.fields) {
if($tw.utils.hop(this.fields,field)) {
if(exclude.indexOf(field) === -1) {
fields[field] = this.getFieldString(field);
}
}
}
return fields;
};
/*
Get all the fields as a name:value block. Options:
exclude: an array of field names to exclude

View File

@@ -1,2 +1,2 @@
title: $:/config/Performance/Instrumentation
text: no
text: yes

View File

@@ -1,6 +1,7 @@
{
"description": "Empty edition",
"plugins": [
"tiddlywiki/sqlite3store"
],
"themes": [
"tiddlywiki/vanilla",

View File

@@ -0,0 +1,2 @@
title: $:/config/Performance/Instrumentation
text: yes

View File

@@ -1,31 +1,11 @@
{
"description": "Content for the current prerelease",
"plugins": [
"tiddlywiki/browser-sniff",
"tiddlywiki/help",
"tiddlywiki/stacked-view",
"tiddlywiki/powered-by-tiddlywiki",
"tiddlywiki/internals",
"tiddlywiki/highlight",
"tiddlywiki/bibtex",
"tiddlywiki/savetrail",
"tiddlywiki/external-attachments",
"tiddlywiki/dynaview",
"tiddlywiki/dynannotate",
"tiddlywiki/codemirror",
"tiddlywiki/menubar",
"tiddlywiki/jszip"
"tiddlywiki/sqlite3store"
],
"themes": [
"tiddlywiki/vanilla",
"tiddlywiki/snowwhite",
"tiddlywiki/starlight",
"tiddlywiki/seamless",
"tiddlywiki/centralised",
"tiddlywiki/heavier",
"tiddlywiki/tight",
"tiddlywiki/tight-heavier",
"tiddlywiki/readonly"
"tiddlywiki/snowwhite"
],
"languages": [
],

View File

@@ -1,7 +1,8 @@
{
"description": "TiddlyWiki core tests",
"plugins": [
"tiddlywiki/jasmine"
"tiddlywiki/jasmine",
"tiddlywiki/sqlite3store"
],
"themes": [
"tiddlywiki/vanilla",

View File

@@ -0,0 +1,2 @@
title: $:/config/Performance/Instrumentation
text: yes

View File

@@ -1,24 +1,11 @@
{
"description": "Documentation from https://tiddlywiki.com",
"plugins": [
"tiddlywiki/nodewebkitsaver",
"tiddlywiki/browser-sniff",
"tiddlywiki/railroad",
"tiddlywiki/evernote",
"tiddlywiki/internals",
"tiddlywiki/menubar",
"tiddlywiki/qrcode"
"tiddlywiki/sqlite3store"
],
"themes": [
"tiddlywiki/vanilla",
"tiddlywiki/snowwhite",
"tiddlywiki/starlight",
"tiddlywiki/seamless",
"tiddlywiki/centralised",
"tiddlywiki/tight",
"tiddlywiki/heavier",
"tiddlywiki/tight-heavier",
"tiddlywiki/readonly"
"tiddlywiki/snowwhite"
],
"languages": [
],

View File

@@ -0,0 +1,6 @@
{
"title": "$:/plugins/tiddlywiki/demo-alternate-store",
"name": "Demo alternate store",
"description": "Developer demo of an alternate wiki store implementation",
"list": "readme"
}

View File

@@ -0,0 +1,357 @@
/*\
title: $:/plugins/tiddlywiki/demo-alternate-store/rawmarkup.js
type: text/plain
tags: $:/tags/AlternateStoreArea
Startup code injected as raw markup
\*/
(function() {
// Need to initialise these because we run before bootprefix.js and boot.js
$tw = window.$tw || Object.create(null);
$tw.hooks = $tw.hooks || { names: {}};
$tw.boot = $tw.boot || {};
$tw.boot.preloadDirty = $tw.boot.preloadDirty || [];
$tw.Wiki = function(options) {
options = options || {};
var self = this,
tiddlers = Object.create(null), // Hashmap of tiddlers
getTiddlerTitles = function() {
return Object.keys(tiddlers).sort(function(a,b) {return a.localeCompare(b);});
},
pluginTiddlers = [], // Array of tiddlers containing registered plugins, ordered by priority
pluginInfo = Object.create(null), // Hashmap of parsed plugin content
shadowTiddlers = Object.create(null), // Hashmap by title of {source:, tiddler:}
getShadowTiddlerTitles = function() {
return Object.keys(shadowTiddlers);
};
//$tw.utils replacements
var eachObj = function(object,callback) {
var next,f,length;
if(object) {
if(Object.prototype.toString.call(object) == "[object Array]") {
for (f=0, length=object.length; f<length; f++) {
next = callback(object[f],f,object);
if(next === false) {
break;
}
}
} else {
var keys = Object.keys(object);
for (f=0, length=keys.length; f<length; f++) {
var key = keys[f];
next = callback(object[key],key,object);
if(next === false) {
break;
}
}
}
}
},
hop = function(object,property) {
return object ? Object.prototype.hasOwnProperty.call(object,property) : false;
},
insertSortedArray = function(array,value) {
var low = 0, high = array.length - 1, mid, cmp;
while(low <= high) {
mid = (low + high) >> 1;
cmp = value.localeCompare(array[mid]);
if(cmp > 0) {
low = mid + 1;
} else if(cmp < 0) {
high = mid - 1;
} else {
return array;
}
}
array.splice(low,0,value);
return array;
},
parseJSONSafe = function(text,defaultJSON) {
try {
return JSON.parse(text);
} catch(e) {
if(typeof defaultJSON === "function") {
return defaultJSON(e);
} else {
return defaultJSON || {};
}
}
};
this.addIndexer = function(indexer,name) {
return;
};
this.getIndexer = function(name) {
return null;
};
// Add a tiddler to the store
this.addTiddler = function(tiddler) {
if(!(tiddler instanceof $tw.Tiddler)) {
tiddler = new $tw.Tiddler(tiddler);
}
// Save the tiddler
if(tiddler) {
var title = tiddler.fields.title;
if(title) {
// Save the new tiddler
tiddlers[title] = tiddler;
// Update caches
this.clearCache(title);
this.clearGlobalCache();
// Queue a change event
this.enqueueTiddlerEvent(title);
}
}
};
// Delete a tiddler
this.deleteTiddler = function(title) {
// Uncomment the following line for detailed logs of all tiddler deletions
// console.log("Deleting",title)
if(hop(tiddlers,title)) {
// Delete the tiddler
delete tiddlers[title];
// Update caches
this.clearCache(title);
this.clearGlobalCache();
// Queue a change event
this.enqueueTiddlerEvent(title,true);
}
};
// Get a tiddler from the store
this.getTiddler = function(title) {
if(title) {
var t = tiddlers[title];
if(t !== undefined) {
return t;
} else {
var s = shadowTiddlers[title];
if(s !== undefined) {
return s.tiddler;
}
}
}
return undefined;
};
// Get an array of all tiddler titles
this.allTitles = function() {
return getTiddlerTitles();
};
// Iterate through all tiddler titles
this.each = function(callback) {
var titles = getTiddlerTitles(),
index,titlesLength,title;
for(index = 0, titlesLength = titles.length; index < titlesLength; index++) {
title = titles[index];
callback(self.getTiddler(title),title);
}
};
// Get an array of all shadow tiddler titles
this.allShadowTitles = function() {
return getShadowTiddlerTitles();
};
// Iterate through all shadow tiddler titles
this.eachShadow = function(callback) {
var titles = getShadowTiddlerTitles(),
index,titlesLength,title;
for(index = 0, titlesLength = titles.length; index < titlesLength; index++) {
title = titles[index];
if(self.tiddlerExists(title)) {
callback(self.getTiddler(title),title);
} else {
var shadowInfo = shadowTiddlers[title];
callback(shadowInfo.tiddler,title);
}
}
};
// Iterate through all tiddlers and then the shadows
this.eachTiddlerPlusShadows = function(callback) {
var index,titlesLength,title,
titles = getTiddlerTitles();
for(index = 0, titlesLength = titles.length; index < titlesLength; index++) {
title = titles[index];
callback(self.getTiddler(title),title);
}
titles = getShadowTiddlerTitles();
for(index = 0, titlesLength = titles.length; index < titlesLength; index++) {
title = titles[index];
if(!self.tiddlerExists(title)) {
var shadowInfo = shadowTiddlers[title];
callback(shadowInfo.tiddler,title);
}
}
};
// Iterate through all the shadows and then the tiddlers
this.eachShadowPlusTiddlers = function(callback) {
var index,titlesLength,title,
titles = getShadowTiddlerTitles();
for(index = 0, titlesLength = titles.length; index < titlesLength; index++) {
title = titles[index];
if(self.tiddlerExists(title)) {
callback(self.getTiddler(title),title);
} else {
var shadowInfo = shadowTiddlers[title];
callback(shadowInfo.tiddler,title);
}
}
titles = getTiddlerTitles();
for(index = 0, titlesLength = titles.length; index < titlesLength; index++) {
title = titles[index];
if(!shadowTiddlers[title]) {
callback(self.getTiddler(title),title);
}
}
};
this.tiddlerExists = function(title) {
return !!hop(tiddlers,title);
};
this.isShadowTiddler = function(title) {
return hop(shadowTiddlers,title);
};
this.getShadowSource = function(title) {
if(hop(shadowTiddlers,title)) {
return shadowTiddlers[title].source;
}
return null;
};
// Get an array of all the currently recognised plugin types
this.getPluginTypes = function() {
var types = [];
eachObj(pluginTiddlers,function(pluginTiddler) {
var pluginType = pluginTiddler.fields["plugin-type"];
if(pluginType && types.indexOf(pluginType) === -1) {
types.push(pluginType);
}
});
return types;
};
// Read plugin info for all plugins, or just an array of titles. Returns the number of plugins updated or deleted
this.readPluginInfo = function(titles) {
var results = {
modifiedPlugins: [],
deletedPlugins: []
};
eachObj(titles || getTiddlerTitles(),function(title) {
var tiddler = self.getTiddler(title);
if(tiddler) {
if(tiddler.fields.type === "application/json" && tiddler.hasField("plugin-type") && tiddler.fields.text) {
pluginInfo[tiddler.fields.title] = parseJSONSafe(tiddler.fields.text);
results.modifiedPlugins.push(tiddler.fields.title);
}
} else {
if(pluginInfo[title]) {
delete pluginInfo[title];
results.deletedPlugins.push(title);
}
}
});
return results;
};
// Get plugin info for a plugin
this.getPluginInfo = function(title) {
return pluginInfo[title];
};
// Register the plugin tiddlers of a particular type, or null/undefined for any type, optionally restricting registration to an array of tiddler titles. Return the array of titles affected
this.registerPluginTiddlers = function(pluginType,titles) {
var self = this,
registeredTitles = [],
checkTiddler = function(tiddler,title) {
if(tiddler && tiddler.fields.type === "application/json" && tiddler.fields["plugin-type"] && (!pluginType || tiddler.fields["plugin-type"] === pluginType)) {
var disablingTiddler = self.getTiddler("$:/config/Plugins/Disabled/" + title);
if(title === "$:/core" || !disablingTiddler || (disablingTiddler.fields.text || "").trim() !== "yes") {
self.unregisterPluginTiddlers(null,[title]); // Unregister the plugin if it's already registered
pluginTiddlers.push(tiddler);
registeredTitles.push(tiddler.fields.title);
}
}
};
if(titles) {
eachObj(titles,function(title) {
checkTiddler(self.getTiddler(title),title);
});
} else {
this.each(function(tiddler,title) {
checkTiddler(tiddler,title);
});
}
return registeredTitles;
};
// Unregister the plugin tiddlers of a particular type, or null/undefined for any type, optionally restricting unregistering to an array of tiddler titles. Returns an array of the titles affected
this.unregisterPluginTiddlers = function(pluginType,titles) {
var self = this,
unregisteredTitles = [];
// Remove any previous registered plugins of this type
for(var t=pluginTiddlers.length-1; t>=0; t--) {
var tiddler = pluginTiddlers[t];
if(tiddler.fields["plugin-type"] && (!pluginType || tiddler.fields["plugin-type"] === pluginType) && (!titles || titles.indexOf(tiddler.fields.title) !== -1)) {
unregisteredTitles.push(tiddler.fields.title);
pluginTiddlers.splice(t,1);
}
}
return unregisteredTitles;
};
// Unpack the currently registered plugins, creating shadow tiddlers for their constituent tiddlers
this.unpackPluginTiddlers = function() {
var self = this;
// Sort the plugin titles by the `plugin-priority` field
pluginTiddlers.sort(function(a,b) {
if("plugin-priority" in a.fields && "plugin-priority" in b.fields) {
return a.fields["plugin-priority"] - b.fields["plugin-priority"];
} else if("plugin-priority" in a.fields) {
return -1;
} else if("plugin-priority" in b.fields) {
return +1;
} else if(a.fields.title < b.fields.title) {
return -1;
} else if(a.fields.title === b.fields.title) {
return 0;
} else {
return +1;
}
});
// Now go through the plugins in ascending order and assign the shadows
shadowTiddlers = Object.create(null);
eachObj(pluginTiddlers,function(tiddler) {
// Extract the constituent tiddlers
if(hop(pluginInfo,tiddler.fields.title)) {
eachObj(pluginInfo[tiddler.fields.title].tiddlers,function(constituentTiddler,constituentTitle) {
// Save the tiddler object
if(constituentTitle) {
shadowTiddlers[constituentTitle] = {
source: tiddler.fields.title,
tiddler: new $tw.Tiddler(constituentTiddler,{title: constituentTitle})
};
}
});
}
});
shadowTiddlerTitles = null;
this.clearCache(null);
this.clearGlobalCache();
};
};
})();
//# sourceURL=$:/plugins/tiddlywiki/demo-alternate-store/rawmarkup.js

View File

@@ -0,0 +1,6 @@
title: $:/plugins/tiddlywiki/demo-alternate-store/rawmarkup
tags: $:/tags/RawMarkupWikified
`<script>`
{{$:/plugins/tiddlywiki/demo-alternate-store/rawmarkup.js}}
`</script>`

View File

@@ -0,0 +1,3 @@
title: $:/plugins/tiddlywiki/demo-alternate-store/readme
Developer demo of an alternate wiki store implementation

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -0,0 +1,19 @@
{
"tiddlers": [
{
"file": "sqlite3.js",
"fields": {
"type": "text/plain",
"title": "$:/plugins/tiddlywiki/sqlite3store/sqlite3.js"
}
},
{
"file": "sqlite3.wasm",
"encoding": "base64",
"fields": {
"type": "application/wasm",
"title": "$:/plugins/tiddlywiki/sqlite3store/sqlite3.wasm"
}
}
]
}

View File

@@ -0,0 +1,66 @@
/*\
title: $:/plugins/tiddlywiki/sqlite3store/init-sqlite3.js
type: application/javascript
Initialise sqlite3 and then boot TiddlyWiki
This file is spliced into the HTML file to be executed after the boot kernel has been loaded.
\*/
(function() {
// Get the main tiddler store out of the HTML file
var storeEl = document.querySelector("script.tiddlywiki-tiddler-store"),
tiddlerStore = JSON.parse(storeEl.textContent);
// Helper to get a tiddler from the store by title
function getTiddler(title) {
for(var t=0; t<tiddlerStore.length; t++) {
var tiddler = tiddlerStore[t];
if(tiddler.title === title) {
return tiddler;
}
}
return undefined;
}
// Get the shadow tiddlers of this plugin
var thisPlugin = getTiddler("$:/plugins/tiddlywiki/sqlite3store"),
thisPluginTiddlers = JSON.parse(thisPlugin.text).tiddlers;
// Execute the sqlite3 module
var sqlite3js = thisPluginTiddlers["$:/plugins/tiddlywiki/sqlite3store/sqlite3.js"].text,
context = {
exports: {}
};
$tw.utils.evalSandboxed(sqlite3js,context,"$:/plugins/tiddlywiki/sqlite3store/sqlite3.js",true);
// Create a Blob URL for the wasm data
var sqlite3wasm = thisPluginTiddlers["$:/plugins/tiddlywiki/sqlite3store/sqlite3.wasm"].text;
var decodedData = window.atob(sqlite3wasm),
uInt8Array = new Uint8Array(decodedData.length);
for (var i = 0; i < decodedData.length; ++i) {
uInt8Array[i] = decodedData.charCodeAt(i);
}
var blobUrl = URL.createObjectURL(new Blob([uInt8Array],{type: "application/wasm"}));
// Pass sqlite an URLSearchParams object containing the Blob URL of our wasm data
self.sqlite3InitModuleState.urlParams = new URLSearchParams();
self.sqlite3InitModuleState.urlParams.set("sqlite3.wasm",blobUrl);
// Initialise sqlite
self.sqlite3InitModule().then((sqlite3)=>{
// Save a reference to the sqlite3 object
$tw.sqlite3 = sqlite3;
var capi = $tw.sqlite3.capi, // C-style API
oo = $tw.sqlite3.oo1; // High-level OO API
// Boot the console
$tw.sqlConsole = new $tw.SqlConsole();
// Get version numbers
console.log("sqlite3 version",capi.sqlite3_libversion());
// Run tests
if($tw.testSqlFunctions) {
$tw.testSqlFunctions();
}
// Boot TiddlyWiki
$tw.boot.boot();
});
})();
//# sourceURL=$:/plugins/tiddlywiki/sqlite3store/init-sqlite3.js

View File

@@ -0,0 +1,6 @@
{
"title": "$:/plugins/tiddlywiki/sqlite3store",
"name": "Sqlite3-based store",
"description": "Sqlite3-based wiki store implementation",
"list": "readme"
}

View File

@@ -0,0 +1,7 @@
title: $:/plugins/tiddlywiki/sqlite3store/rawmarkup/bottombody
tags: $:/tags/RawMarkupWikified/BottomBody
`<script>`
{{$:/plugins/tiddlywiki/sqlite3store/sql-console.js}}
{{$:/plugins/tiddlywiki/sqlite3store/init-sqlite3.js}}
`</script>`

View File

@@ -0,0 +1,13 @@
title: $:/plugins/tiddlywiki/sqlite3store/rawmarkup-bottomhead
tags: $:/tags/RawMarkupWikified
`<style>`
{{$:/plugins/tiddlywiki/sqlite3store/sql-console/styles}}
`</style>
<script>`
{{$:/plugins/tiddlywiki/sqlite3store/suppress-boot.js}}
{{$:/plugins/tiddlywiki/sqlite3store/sql-functions.js}}
{{$:/plugins/tiddlywiki/sqlite3store/test-sql-functions.js}}
{{$:/plugins/tiddlywiki/sqlite3store/sql-wiki-store.js}}
{{$:/plugins/tiddlywiki/sqlite3store/sql-filter-compiler.js}}
`</script>`

View File

@@ -0,0 +1,3 @@
title: $:/plugins/tiddlywiki/sqlite3store/readme
Wiki store implementation based on sqlite3 WASM build.

View File

@@ -0,0 +1,20 @@
title: $:/plugins/tiddlywiki/sqlite3store/sql-console/styles
code-body: yes
\rules only filteredtranscludeinline transcludeinline macrodef macrocallinline macrocallblock
.sql-console * {
}
.sql-console-input,
.sql-console-output-input {
font-family: monospace;
white-space: pre;
box-sizing: border-box;
width: 100%;
background: #222222;
color: #00ff00;
border-radius: 4px;
padding: 4px;
margin: 4px;
}

View File

@@ -0,0 +1,94 @@
/*\
title: $:/plugins/tiddlywiki/sqlite3store/sql-console.js
type: application/javascript
SQL console for debugging
\*/
(function() {
$tw.SqlConsole = function SqlConsole() {
var self = this,
dm = $tw.utils.domMaker;
// Input box
this.consoleInput = dm("textarea",{
"class": "sql-console-input",
attributes: {
"rows": "10"
}
});
// Run button
this.consoleRunButton = dm("button",{
text: "run sql"
});
this.consoleRunButton.addEventListener("click",this.runQuery.bind(this));
// Clear output button
this.consoleClearButton = dm("button",{
text: "clear output"
});
this.consoleClearButton.addEventListener("click",this.clearOutput.bind(this));
// Output
this.consoleOutput = dm("div",{
"class": "sql-console-output-container"
});
// Container
this.consoleContainer = dm("div",{
"class": "sql-console",
children: [
document.createTextNode("console for sqlite3"),
this.consoleInput,
this.consoleRunButton,
this.consoleClearButton,
this.consoleOutput
]
});
// Insert into DOM
document.body.insertBefore(this.consoleContainer,document.body.firstChild);
};
$tw.SqlConsole.prototype.clearOutput = function() {
while(this.consoleOutput.firstChild) {
this.consoleOutput.removeChild(this.consoleOutput.firstChild);
}
};
$tw.SqlConsole.prototype.runQuery = function() {
var self = this,
dm = $tw.utils.domMaker,
sql = this.consoleInput.value,
resultRows = [],
exception;
// Execute the query
try {
$tw.wiki.sqlFunctions.db.exec({
sql: sql,
rowMode: "object",
resultRows: resultRows
});
} catch(e) {
exception = e.toString();
}
// Display the result
var output = dm("div",{
"class": "sql-console-output",
children: [
dm("div",{
"class": "sql-console-output-input",
text: sql
}),
dm("div",{
"class": "sql-console-output-count",
text: "" + resultRows.length
}),
dm("div",{
"class": "sql-console-output-output",
text: exception || JSON.stringify(resultRows)
})
]
});
this.consoleOutput.insertBefore(output,this.consoleOutput.firstChild);
};
})();
//# sourceURL=$:/plugins/tiddlywiki/sqlite3store/sql-console.js

View File

@@ -0,0 +1,26 @@
/*\
title: $:/plugins/tiddlywiki/sqlite3store/sql-filter-compiler.js
type: application/javascript
A sqlite3 implementation of a wiki store object
This file is spliced into the HTML file to be executed before the boot kernel has been loaded.
\*/
(function() {
/*
If possible, return a filter evaluation function with the signature (source,widget) that executes the provided filter parse tree
*/
$tw.Wiki.prototype.optimiseFilter = function(filterString,filterParseTree) {
// switch($tw.utils.trim(filterString)) {
// case "[all[shadows+tiddlers]prefix[$:/language/Docs/Types/]get[name]length[]maxall[]]":
// return [this.sqlFunctions.sqlQuickFilterAllShadowsTiddlersPrefixDocTypeMaxLength()];
// break;
// }
return undefined;
};
})();
//# sourceURL=$:/plugins/tiddlywiki/sqlite3store/sql-filter-compiler.js

View File

@@ -0,0 +1,475 @@
/*\
title: $:/plugins/tiddlywiki/sqlite3store/sql-functions.js
type: application/javascript
Functions to perform basic tiddler operations with a sqlite3 database
This file is spliced into the HTML file to be executed before the boot kernel has been loaded.
\*/
(function() {
$tw.SqlFunctions = function(options) {
options = options || {};
var self = this;
// Setting useCustomCollation to true allows the tests to pass (run `tiddlywiki editions/test/ --build index`)
// - but it takes 6 times longer to boot the prerelease than with useCustomCollation set to false
var useCustomCollation = false;
var COLLATION_CLAUSE = useCustomCollation ? "COLLATE custom_collation" : "";
// Create anonymous database
this.db = new $tw.sqlite3.oo1.DB("","c");
// Setup custom collation to precisely match existing sort orders
// Create field with `title TEXT NOT NULL COLLATE custom_collation`
// Use it like `... order by shadow collate custom_collation`
if(useCustomCollation) {
function customCollation(ptr,lenA,a,lenB,b) {
// There may be a problem here: lenA and lenB are the lengths of the two UTF8 strings in bytes,
// and yet we're using them with JS slice() method which counts in characters
var jsA = $tw.sqlite3.wasm.cstrToJs(a).slice(0,lenA),
jsB = $tw.sqlite3.wasm.cstrToJs(b).slice(0,lenB);
return jsA.localeCompare(jsB);
}
var SQLITE_UTF8 = 1; /* IMP: R-37514-35566 */
var SQLITE_UTF16LE = 2; /* IMP: R-03371-37637 */
var SQLITE_UTF16BE = 3; /* IMP: R-51971-34154 */
var SQLITE_UTF16 = 4; /* Use native byte order */
var SQLITE_ANY = 5; /* Deprecated */
var SQLITE_UTF16_ALIGNED = 8; /* sqlite3_create_collation only */
var collationResult = $tw.sqlite3.capi.sqlite3_create_collation_v2(this.db.pointer,"custom_collation",SQLITE_UTF8,this,customCollation,0);
}
/*
Create tables and indexes
*/
self.db.exec({
sql: `
DROP TABLE IF EXISTS plugins;
CREATE TABLE plugins (
plugintitle TEXT NOT NULL, -- Empty string shoud be the highest priority
priority INTEGER NOT NULL,
PRIMARY KEY(plugintitle)
);
CREATE INDEX IF NOT EXISTS plugins_plugintitle_index ON plugins(plugintitle);
DROP TABLE IF EXISTS tiddlers;
CREATE TABLE tiddlers (
title TEXT NOT NULL ${COLLATION_CLAUSE},
plugintitle TEXT NOT NULL, -- Empty string for tiddlers that are not part of a plugin
meta TEXT NOT NULL,
text TEXT NOT NULL,
PRIMARY KEY(title,plugintitle)
);
CREATE INDEX IF NOT EXISTS tiddlers_title_index ON tiddlers(title);
CREATE INDEX IF NOT EXISTS tiddlers_plugintitle_index ON tiddlers(plugintitle);
DROP TABLE IF EXISTS titles;
CREATE TABLE titles (
title TEXT NOT NULL ${COLLATION_CLAUSE},
plugintitle TEXT NOT NULL, -- Empty string for tiddlers that are not part of a plugin
PRIMARY KEY(title)
);
CREATE INDEX IF NOT EXISTS titles_title_index ON titles(title);
CREATE INDEX IF NOT EXISTS titles_plugintitle_index ON titles(plugintitle);
DROP TABLE IF EXISTS tags;
CREATE TABLE tags (
tag_id INTEGER PRIMARY KEY,
tag TEXT NOT NULL
);
DROP TABLE IF EXISTS tiddler_tags;
CREATE TABLE tiddler_tags (
title TEXT NOT NULL,
plugintitle INTEGER NOT NULL,
tag_id INTEGER NOT NULL,
FOREIGN KEY (title, plugintitle) REFERENCES tiddlers (title, plugintitle) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags (tag_id) ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY (title, plugintitle, tag_id)
);
CREATE INDEX IF NOT EXISTS tiddler_tags_title_index ON tiddler_tags(title);
CREATE INDEX IF NOT EXISTS tiddler_tags_plugintitle_index ON tiddler_tags(plugintitle);
CREATE INDEX IF NOT EXISTS tiddler_tags_tag_id_index ON tiddler_tags(tag_id);
`
});
/*
Debugging
*/
var statementLogTiddlersTable = self.db.prepare("select title, plugintitle, meta, text from tiddlers order by title, plugintitle;"),
statementLogPluginsTable = self.db.prepare("select plugintitle, priority from plugins order by plugintitle;"),
statementLogTitlesTable = self.db.prepare("select title, plugintitle from titles order by title;"),
statementLogTagsTable = self.db.prepare("select tag_id, tag from tags order by tag_id;"),
statementLogTiddlerTagsTable = self.db.prepare("select title, plugintitle, tag_id from tiddler_tags order by title, plugintitle;");
function sqlLogTable(statement) {
let resultRows = [];
while(statement.step()) {
var row = statement.get({});
resultRows.push(row);
}
statement.reset();
return resultRows;
}
this.sqlLogTables = function() {
console.log("tiddlers",sqlLogTable(statementLogTiddlersTable));
console.log("plugins",sqlLogTable(statementLogPluginsTable));
console.log("titles",sqlLogTable(statementLogTitlesTable));
console.log("tags",sqlLogTable(statementLogTagsTable));
console.log("tiddlertags",sqlLogTable(statementLogTiddlerTagsTable));
};
/*
Set the plugin priorities
*/
this.sqlSetPluginPriorities = function(prioritisedPluginTitles) {
const plugintitles = prioritisedPluginTitles.concat([""]);
self.db.exec({
sql: "DELETE FROM plugins"
});
let priority = 1;
for(const plugintitle of plugintitles) {
self.db.exec({
sql: "insert or replace into plugins (plugintitle, priority) values ($plugintitle, $priority)",
bind: {
$plugintitle: plugintitle,
$priority: priority++
}
});
}
};
/*
Save a tiddler
*/
var querySaveTiddlerTableTiddlers = self.db.prepare(`
-- Insert the new tiddler into the tiddlers table
INSERT OR REPLACE INTO tiddlers (title, plugintitle, meta, text)
VALUES ($title, $plugintitle, $meta, $text);
`);
var querySaveTiddlerTableTitles = self.db.prepare(`
-- Insert the new title into the titles table
INSERT OR REPLACE INTO titles (title, plugintitle)
SELECT
t.title,
(SELECT t2.plugintitle
FROM tiddlers AS t2
JOIN plugins AS p ON t2.plugintitle = p.plugintitle
WHERE t2.title = t.title
ORDER BY p.priority DESC
LIMIT 1
) AS plugintitle
FROM tiddlers AS t
WHERE t.title = $title;
`);
var querySaveTiddlerTableTags = self.db.prepare(`
-- Parse and insert tags from the $tags JSON array
WITH tag_values AS (
SELECT json_each.value AS tag
FROM json_each($tags)
)
INSERT INTO tags (tag)
SELECT DISTINCT tag
FROM tag_values
WHERE tag NOT IN (
SELECT tag
FROM tags
);
`);
var querySaveTiddlerTableTiddlerTags = self.db.prepare(`
-- Associate the new tiddler with the tags in the tiddler_tags table
WITH tag_values AS (
SELECT json_each.value AS tag
FROM json_each($tags)
)
INSERT OR IGNORE INTO tiddler_tags (title, plugintitle, tag_id)
SELECT $title, $plugintitle, tags.tag_id
FROM tag_values
JOIN tags ON tag_values.tag = tags.tag;
`);
this.sqlSaveTiddler = function(tiddlerFields,plugintitle) {
plugintitle = plugintitle || "";
// Normalise the tags by removing any double square brackets
let tags = tiddlerFields.tags;
if(typeof tags === "string") {
tags = $tw.utils.parseStringArray(tags);
}
const normalisedTags = (tags || []).map(tag => {
const match = /^[^\S\xA0]*\[\[(.*)\]\][^\S\xA0]*$/mg.exec(tag);
if(match) {
return match[1];
} else {
return tag;
}
});
const jsonNormalisedTags = JSON.stringify(normalisedTags);
querySaveTiddlerTableTiddlers.bind({
$title: tiddlerFields.title,
$plugintitle: plugintitle,
$meta: JSON.stringify(Object.assign({},tiddlerFields,{title: undefined, text: undefined})),
$text: tiddlerFields.text || ""
});
querySaveTiddlerTableTiddlers.step();
querySaveTiddlerTableTiddlers.reset();
querySaveTiddlerTableTitles.bind({
$title: tiddlerFields.title,
});
querySaveTiddlerTableTitles.step();
querySaveTiddlerTableTitles.reset();
querySaveTiddlerTableTags.bind({
$tags: jsonNormalisedTags
});
querySaveTiddlerTableTags.step();
querySaveTiddlerTableTags.reset();
querySaveTiddlerTableTiddlerTags.bind({
$title: tiddlerFields.title,
$plugintitle: plugintitle || "",
$tags: jsonNormalisedTags
});
querySaveTiddlerTableTiddlerTags.step();
querySaveTiddlerTableTiddlerTags.reset();
};
/*
Delete a tiddler
*/
var statementDeleteTiddlerDeleteFromTiddlers = self.db.prepare(`
DELETE FROM tiddlers
WHERE title = $title AND plugintitle = $plugintitle;
`);
var statementDeleteTiddlerFindShadow = self.db.prepare(`
SELECT t.title, t.plugintitle
FROM tiddlers AS t
JOIN plugins AS p ON t.plugintitle = p.plugintitle
WHERE t.title = $title
ORDER BY p.priority DESC
LIMIT 1;
`);
this.sqlDeleteTiddler = function(title,plugintitle) {
plugintitle = plugintitle || "";
// Delete the tiddler from the tiddlers table
statementDeleteTiddlerDeleteFromTiddlers.bind({
$title: title,
$plugintitle: plugintitle
});
statementDeleteTiddlerDeleteFromTiddlers.step();
statementDeleteTiddlerDeleteFromTiddlers.reset();
// Find any corresponding shadow tiddler
statementDeleteTiddlerFindShadow.bind({
$title: title
});
if(statementDeleteTiddlerFindShadow.step()) {
var row = statementDeleteTiddlerFindShadow.get({});
statementDeleteTiddlerFindShadow.reset();
// Replace the tiddler with the shadow
self.db.exec({
sql: "insert or replace into titles (title, plugintitle) values ($title, $plugintitle)",
bind: {
$title: title,
$plugintitle: row.plugintitle
}
});
} else {
statementDeleteTiddlerFindShadow.reset();
// There is no shadow tiddler, so just delete the tiddler
self.db.exec({
sql: "delete from titles where title = $title",
bind: {
$title: title
}
});
}
};
/*
Remove all shadow tiddlers
*/
this.sqlClearShadows = function() {
self.db.exec({
sql: "delete from tiddlers where plugintitle != '';"
});
self.db.exec({
sql: "delete from titles where plugintitle != '';"
});
};
/*
Check whether a tiddler exists
*/
var statementTiddlerExists = self.db.prepare(`select title from titles where title = $title and plugintitle = '';`)
this.sqlTiddlerExists = function(title) {
statementTiddlerExists.bind({
$title: title
});
if(statementTiddlerExists.step()) {
statementTiddlerExists.reset();
return true;
} else {
statementTiddlerExists.reset();
return false;
}
};
/*
Get the value of a tiddler
*/
var statementGetTiddler = self.db.prepare(`
select t.title, ti.meta, ti.text
FROM titles AS t
JOIN tiddlers AS ti
ON t.title = ti.title AND t.plugintitle = ti.plugintitle
WHERE t.title = $title;
`);
this.sqlGetTiddler = function(title) {
statementGetTiddler.bind({
$title: title
});
if(statementGetTiddler.step()) {
var row = statementGetTiddler.get({});
statementGetTiddler.reset();
return Object.assign({},JSON.parse(row.meta),{title: row.title, text: row.text});
} else {
statementGetTiddler.reset();
return undefined;
}
};
/*
Get the plugin from which a tiddler came
*/
var statementGetShadowSource = self.db.prepare(`
SELECT t.title, t.plugintitle
FROM tiddlers AS t
JOIN plugins AS p ON t.plugintitle = p.plugintitle
WHERE t.title = $title AND t.plugintitle <> ''
ORDER BY p.priority DESC
LIMIT 1;
`);
this.sqlGetShadowSource = function(title) {
statementGetShadowSource.bind({
$title: title
});
if(statementGetShadowSource.step()) {
var row = statementGetShadowSource.get({});
statementGetShadowSource.reset();
return row.plugintitle;
} else {
statementGetShadowSource.reset();
return null;
}
};
/*
Get all titles
*/
var statementAllTitles = self.db.prepare(`select title from titles where plugintitle = '' order by title ${COLLATION_CLAUSE}`);
this.sqlAllTitles = function() {
let resultRows = [];
while(statementAllTitles.step()) {
var row = statementAllTitles.get({});
resultRows.push(row.title);
}
statementAllTitles.reset();
return resultRows;
};
/*
All shadow titles
*/
var statementAllShadowTitles = self.db.prepare(`
SELECT title
FROM tiddlers
WHERE plugintitle != ''
ORDER BY title ${COLLATION_CLAUSE}
`);
this.sqlAllShadowTitles = function() {
let resultRows = [];
while(statementAllShadowTitles.step()) {
var row = statementAllShadowTitles.get({});
resultRows.push(row.title);
}
statementAllShadowTitles.reset();
return resultRows;
};
/*
Iterate through each tiddler
*/
var statementEachTiddler = self.db.prepare(`
SELECT t.title, ti.meta, ti.text
FROM titles AS t
JOIN tiddlers AS ti ON t.title = ti.title AND t.plugintitle = ti.plugintitle
WHERE t.plugintitle == ''
ORDER BY t.title ${COLLATION_CLAUSE}
`);
this.sqlEachTiddler = function(callback) {
while(statementEachTiddler.step()) {
var row = statementEachTiddler.get({}),
tiddlerFields = Object.assign({},JSON.parse(row.meta),{title: row.title, text: row.text});
callback(tiddlerFields,row.title);
}
statementEachTiddler.reset();
};
/*
Iterate through each tiddler that is a shadow (including overridden shadows)
*/
var statementEachShadowTiddler = self.db.prepare(`
SELECT DISTINCT t.title, td.meta, td.text
FROM titles AS t
JOIN tiddlers AS td ON t.title = td.title
WHERE td.plugintitle != ''
ORDER BY t.title ${COLLATION_CLAUSE};
`);
this.sqlEachShadowTiddler = function(callback) {
while(statementEachShadowTiddler.step()) {
var row = statementEachShadowTiddler.get({});
var tiddlerFields = Object.assign({},JSON.parse(row.meta),{title: row.title, text: row.text});
callback(tiddlerFields,row.title);
}
statementEachShadowTiddler.reset();
};
/*
Iterate all tiddlers, and then the shadows
*/
this.sqlEachTiddlerPlusShadows = function(callback) {
const titles = Object.create(null);
self.sqlEachTiddler(function(fields,title) {
titles[title] = true;
callback(fields,title);
});
self.sqlEachShadowTiddler(function(fields,title) {
if(!titles[title]) {
callback(fields,title);
}
});
};
/*
Iterate all shadows, and then the tiddlers
*/
this.sqlEachShadowPlusTiddlers = function(callback) {
const titles = Object.create(null);
self.sqlEachShadowTiddler(function(fields,title) {
titles[title] = true;
callback(fields,title);
});
self.sqlEachTiddler(function(fields,title) {
if(!titles[title]) {
callback(fields,title);
}
});
};
/*
Return all tiddlers with a given tag
*/
var statementGetTiddlersWithTag = self.db.prepare(`
SELECT titles.title
FROM titles
JOIN tiddlers ON titles.title = tiddlers.title AND titles.plugintitle = tiddlers.plugintitle
JOIN plugins ON titles.plugintitle = plugins.plugintitle
JOIN tiddler_tags ON tiddlers.title = tiddler_tags.title AND tiddlers.plugintitle = tiddler_tags.plugintitle
JOIN tags ON tiddler_tags.tag_id = tags.tag_id
WHERE tags.tag = $tag
ORDER BY CASE
WHEN titles.plugintitle <> '' THEN 1
ELSE 2
END, titles.title ${COLLATION_CLAUSE} ASC;
`);
this.sqlGetTiddlersWithTag = function(tag,method) {
statementGetTiddlersWithTag.bind({
$tag: tag
});
var resultRows = [];
while(statementGetTiddlersWithTag.step()) {
var row = statementGetTiddlersWithTag.get({});
resultRows.push(row.title);
}
statementGetTiddlersWithTag.reset();
return resultRows;
};
};
})();
//# sourceURL=$:/plugins/tiddlywiki/sqlite3store/sql-functions.js

View File

@@ -0,0 +1,397 @@
/*\
title: $:/plugins/tiddlywiki/sqlite3store/sql-wiki-store.js
type: application/javascript
A sqlite3 implementation of a wiki store object
This file is spliced into the HTML file to be executed before the boot kernel has been loaded.
\*/
(function() {
$tw.Wiki = function(options) {
options = options || {};
this.sqlFunctions = new $tw.SqlFunctions();
this.sqlFunctions.sqlSetPluginPriorities([]);
// Adapted version of the boot.js wiki store implementation follows
var self = this,
cachedTiddlerTitles = null;
getTiddlerTitles = function() {
if(!cachedTiddlerTitles) {
cachedTiddlerTitles = self.sqlFunctions.sqlAllTitles();
}
return cachedTiddlerTitles;
},
pluginTiddlers = [], // Array of tiddlers containing registered plugins, ordered by priority
pluginInfo = Object.create(null), // Hashmap of parsed plugin content
cachedShadowTiddlerTitles = null;
getShadowTiddlerTitles = function() {
if(!cachedShadowTiddlerTitles) {
cachedShadowTiddlerTitles = self.sqlFunctions.sqlAllShadowTitles();
}
return cachedShadowTiddlerTitles;
},
enableIndexers = options.enableIndexers || null,
indexers = [],
indexersByName = Object.create(null);
this.clearAllCaches = function(title) {
cachedTiddlerTitles = null;
cachedShadowTiddlerTitles = null;
if(title !== undefined) {
this.clearCache(title);
}
this.clearGlobalCache();
$tw.utils.each(indexers,function(indexer) {
indexer.update();
});
};
this.addIndexer = function(indexer,name) {
// We stub out this method because this store doesn't support external indexers
};
this.addInternalIndexer = function(indexer,name) {
// Bail if this indexer is not enabled
if(enableIndexers && enableIndexers.indexOf(name) === -1) {
return;
}
console.log("Added indexer",name)
indexers.push(indexer);
indexersByName[name] = indexer;
indexer.init();
};
this.getIndexer = function(name) {
return indexersByName[name] || null;
};
// $tw.utils replacements
var eachObj = function(object,callback) {
var next,f,length;
if(object) {
if(Object.prototype.toString.call(object) == "[object Array]") {
for (f=0, length=object.length; f<length; f++) {
next = callback(object[f],f,object);
if(next === false) {
break;
}
}
} else {
var keys = Object.keys(object);
for (f=0, length=keys.length; f<length; f++) {
var key = keys[f];
next = callback(object[key],key,object);
if(next === false) {
break;
}
}
}
}
},
hop = function(object,property) {
return object ? Object.prototype.hasOwnProperty.call(object,property) : false;
},
insertSortedArray = function(array,value) {
var low = 0, high = array.length - 1, mid, cmp;
while(low <= high) {
mid = (low + high) >> 1;
cmp = value.localeCompare(array[mid]);
if(cmp > 0) {
low = mid + 1;
} else if(cmp < 0) {
high = mid - 1;
} else {
return array;
}
}
array.splice(low,0,value);
return array;
},
parseJSONSafe = function(text,defaultJSON) {
try {
return JSON.parse(text);
} catch(e) {
if(typeof defaultJSON === "function") {
return defaultJSON(e);
} else {
return defaultJSON || {};
}
}
};
this.logTables = function() {
self.sqlFunctions.sqlLogTables();
}
// Add a tiddler to the store
this.addTiddler = function(tiddler) {
if(!(tiddler instanceof $tw.Tiddler)) {
tiddler = new $tw.Tiddler(tiddler);
}
// Save the tiddler
if(tiddler) {
var title = tiddler.fields.title;
if(title) {
// Save the new tiddler
self.sqlFunctions.sqlSaveTiddler(tiddler.fields);
// Update caches
this.clearAllCaches(title);
// Queue a change event
this.enqueueTiddlerEvent(title);
}
}
};
// Delete a tiddler
this.deleteTiddler = function(title) {
// Uncomment the following line for detailed logs of all tiddler deletions
// console.log("Deleting",title)
if(self.tiddlerExists(title)) {
// Delete the tiddler
self.sqlFunctions.sqlDeleteTiddler(title);
// Update caches
this.clearAllCaches(title);
// Queue a change event
this.enqueueTiddlerEvent(title,true);
}
};
// Get a tiddler from the store
this.getTiddler = function(title) {
if(title) {
var t = self.sqlFunctions.sqlGetTiddler(title);
if(t !== undefined) {
return new $tw.Tiddler(t);
}
}
return undefined;
};
// Get an array of all tiddler titles
this.allTitles = function() {
return getTiddlerTitles();
};
// Iterate through all tiddler titles
this.each = function(callback) {
self.sqlFunctions.sqlEachTiddler(function(tiddlerFields,title) {
callback(new $tw.Tiddler(tiddlerFields),title);
});
};
// Get an array of all shadow tiddler titles
this.allShadowTitles = function() {
return getShadowTiddlerTitles();
};
// Iterate through all shadow tiddler titles
this.eachShadow = function(callback) {
self.sqlFunctions.sqlEachShadowTiddler(function(tiddlerFields,title) {
callback(new $tw.Tiddler(tiddlerFields),title);
});
};
// Iterate through all tiddlers and then the shadows
this.eachTiddlerPlusShadows = function(callback) {
self.sqlFunctions.sqlEachTiddlerPlusShadows(function(tiddlerFields,title) {
callback(new $tw.Tiddler(tiddlerFields),title);
});
};
// Iterate through all the shadows and then the tiddlers
this.eachShadowPlusTiddlers = function(callback) {
self.sqlFunctions.sqlEachShadowPlusTiddlers(function(tiddlerFields,title) {
callback(new $tw.Tiddler(tiddlerFields),title);
});
};
this.tiddlerExists = function(title) {
return self.sqlFunctions.sqlTiddlerExists(title);
};
this.isShadowTiddler = function(title) {
return !!self.sqlFunctions.sqlGetShadowSource(title);
};
this.getShadowSource = function(title) {
return self.sqlFunctions.sqlGetShadowSource(title);
};
// Get an array of all the currently recognised plugin types
this.getPluginTypes = function() {
var types = [];
eachObj(pluginTiddlers,function(pluginTiddler) {
var pluginType = pluginTiddler.fields["plugin-type"];
if(pluginType && types.indexOf(pluginType) === -1) {
types.push(pluginType);
}
});
return types;
};
// Read plugin info for all plugins, or just an array of titles. Returns the number of plugins updated or deleted
this.readPluginInfo = function(titles) {
var results = {
modifiedPlugins: [],
deletedPlugins: []
};
eachObj(titles || getTiddlerTitles(),function(title) {
var tiddler = self.getTiddler(title);
if(tiddler) {
if(tiddler.fields.type === "application/json" && tiddler.hasField("plugin-type") && tiddler.fields.text) {
pluginInfo[tiddler.fields.title] = parseJSONSafe(tiddler.fields.text);
results.modifiedPlugins.push(tiddler.fields.title);
}
} else {
if(pluginInfo[title]) {
delete pluginInfo[title];
results.deletedPlugins.push(title);
}
}
});
return results;
};
// Get plugin info for a plugin
this.getPluginInfo = function(title) {
return pluginInfo[title];
};
// Register the plugin tiddlers of a particular type, or null/undefined for any type, optionally restricting registration to an array of tiddler titles. Return the array of titles affected
this.registerPluginTiddlers = function(pluginType,titles) {
var self = this,
registeredTitles = [],
checkTiddler = function(tiddler,title) {
if(tiddler && tiddler.fields.type === "application/json" && tiddler.fields["plugin-type"] && (!pluginType || tiddler.fields["plugin-type"] === pluginType)) {
var disablingTiddler = self.getTiddler("$:/config/Plugins/Disabled/" + title);
if(title === "$:/core" || !disablingTiddler || (disablingTiddler.fields.text || "").trim() !== "yes") {
self.unregisterPluginTiddlers(null,[title]); // Unregister the plugin if it's already registered
pluginTiddlers.push(tiddler);
registeredTitles.push(tiddler.fields.title);
}
}
};
if(titles) {
eachObj(titles,function(title) {
checkTiddler(self.getTiddler(title),title);
});
} else {
this.each(function(tiddler,title) {
checkTiddler(tiddler,title);
});
}
return registeredTitles;
};
// Unregister the plugin tiddlers of a particular type, or null/undefined for any type, optionally restricting unregistering to an array of tiddler titles. Returns an array of the titles affected
this.unregisterPluginTiddlers = function(pluginType,titles) {
var self = this,
unregisteredTitles = [];
// Remove any previous registered plugins of this type
for(var t=pluginTiddlers.length-1; t>=0; t--) {
var tiddler = pluginTiddlers[t];
if(tiddler.fields["plugin-type"] && (!pluginType || tiddler.fields["plugin-type"] === pluginType) && (!titles || titles.indexOf(tiddler.fields.title) !== -1)) {
unregisteredTitles.push(tiddler.fields.title);
pluginTiddlers.splice(t,1);
}
}
return unregisteredTitles;
};
// Unpack the currently registered plugins, creating shadow tiddlers for their constituent tiddlers
this.unpackPluginTiddlers = function() {
var self = this;
// Sort the plugin titles by the `plugin-priority` field
pluginTiddlers.sort(function(a,b) {
if("plugin-priority" in a.fields && "plugin-priority" in b.fields) {
return a.fields["plugin-priority"] - b.fields["plugin-priority"];
} else if("plugin-priority" in a.fields) {
return -1;
} else if("plugin-priority" in b.fields) {
return +1;
} else if(a.fields.title < b.fields.title) {
return -1;
} else if(a.fields.title === b.fields.title) {
return 0;
} else {
return +1;
}
});
// Now go through the plugins in ascending order and assign the shadows
self.sqlFunctions.sqlClearShadows();
self.sqlFunctions.sqlSetPluginPriorities(pluginTiddlers.map(tiddler => tiddler.fields.title));
eachObj(pluginTiddlers,function(tiddler) {
// Extract the constituent tiddlers
if(hop(pluginInfo,tiddler.fields.title)) {
eachObj(pluginInfo[tiddler.fields.title].tiddlers,function(constituentTiddler,constituentTitle) {
// Save the tiddler object
if(constituentTitle) {
var shadowTiddler = Object.assign({},constituentTiddler,{title: constituentTitle})
self.sqlFunctions.sqlSaveTiddler(shadowTiddler,tiddler.fields.title);
}
});
}
});
this.clearAllCaches();
};
function TagSubIndexer(indexer,iteratorMethod) {
this.indexer = indexer;
this.iteratorMethod = iteratorMethod;
this.cache = Object.create(null); // Hashmap by title containing arrays of titles
}
TagSubIndexer.prototype.addIndexMethod = function() {
var self = this;
this.indexer.wiki[this.iteratorMethod].byTag = function(tag) {
return self.lookup(tag).slice(0);
};
};
TagSubIndexer.prototype.update = function() {
this.cache = Object.create(null);
};
TagSubIndexer.prototype.lookup = function(tag) {
var cachedResult = this.cache[tag];
if(cachedResult) {
return cachedResult;
}
var listing = self.sqlFunctions.sqlGetTiddlersWithTag(tag,this.iteratorMethod);
if(this.indexer.wiki.sortByList) {
listing = this.indexer.wiki.sortByList(listing,tag);
}
this.cache[tag] = listing;
return listing;
};
function TagIndexer(wiki) {
this.wiki = wiki;
this.subIndexers = [
new TagSubIndexer(this,"each"),
new TagSubIndexer(this,"eachShadow"),
new TagSubIndexer(this,"eachTiddlerPlusShadows"),
new TagSubIndexer(this,"eachShadowPlusTiddlers")
];
$tw.utils.each(this.subIndexers,function(subIndexer) {
subIndexer.addIndexMethod();
});
}
TagIndexer.prototype.init = function() {
};
TagIndexer.prototype.update = function() {
$tw.utils.each(this.subIndexers,function(subIndexer) {
subIndexer.update();
});
};
this.addInternalIndexer(new TagIndexer(this),"TagIndexer");
};
})();
//# sourceURL=$:/plugins/tiddlywiki/sqlite3store/sql-wiki-store.js

View File

@@ -0,0 +1,23 @@
/*\
title: $:/plugins/tiddlywiki/sqlite3store/suppress-boot.js
type: application/javascript
Suppress the usual synchronous startup process so that it can instead be done within the callback from sqlite3 initialisation.
This file is spliced into the HTML file to be executed before the boot kernel has been loaded.
\*/
(function() {
// Initialse skeleton TiddlyWiki global because we run before bootprefix.js and boot.js
window.$tw = window.$tw || Object.create(null);
$tw.hooks = $tw.hooks || { names: {}};
$tw.boot = $tw.boot || {};
$tw.boot.preloadDirty = $tw.boot.preloadDirty || [];
// Tell TiddlyWiki not to boot itself
$tw.boot.suppressBoot = true;
})();
//# sourceURL=$:/plugins/tiddlywiki/sqlite3store/suppress-boot.js

View File

@@ -0,0 +1,169 @@
/*\
title: $:/plugins/tiddlywiki/sqlite3store/test-sql-functions.js
type: application/javascript
Test harness for the functions in sql-functions.js
\*/
$tw.testSqlFunctions = function() {
// Deep equal
function deepEqual(obj1, obj2) {
if (obj1 === undefined && obj2 === undefined) {
return true;
}
if (obj1 === undefined || obj2 === undefined) {
return false;
}
if (obj1 === obj2) {
return true;
}
if (isPrimitive(obj1) && isPrimitive(obj2)) {
return obj1 === obj2;
}
if (Object.keys(obj1).length !== Object.keys(obj2).length) {
return false;
}
for (let key in obj1) {
if (!(key in obj2)) {
return false;
}
if (!deepEqual(obj1[key], obj2[key])) {
return false;
}
}
return true;
}
function isPrimitive(obj) {
return (obj !== Object(obj));
}
let tests = [];
// Define a test
function test(name, fn) {
tests.push({
name: name,
fn: fn
});
}
// Run all the tests
function run() {
while(tests.length > 0) {
const test = tests.shift();
try {
test.fn();
console.log("✅", test.name);
} catch (e) {
console.log("❌", test.name);
console.log(e.stack);
}
}
}
let assert = {
equal: function(obj1,obj2,message) {
if(!deepEqual(obj1,obj2)) {
throw new Error("" + (message || "assert.equal failed"));
}
}
}
// Define the tests
test("Instantiate the database", function () {
const sqlFunctions = new $tw.SqlFunctions();
test("Write a tiddler and retrieve it", function() {
// Utilities
function checkExists(title,result,message) {
const exists = sqlFunctions.sqlTiddlerExists(title);
assert.equal(exists,result,message);
}
function checkTiddler(title,result,message) {
const tiddler = sqlFunctions.sqlGetTiddler(title);
assert.equal(tiddler,result,message);
}
function checkShadowSource(title,result,message) {
const tiddler = sqlFunctions.sqlGetShadowSource(title);
assert.equal(tiddler,result,message);
}
function checkAllTitles(result,message) {
const titles = sqlFunctions.sqlAllTitles();
assert.equal(titles,result,message + " (sqlAllTitles)");
const accumulator = [];
sqlFunctions.sqlEachTiddler(function(tiddlerFields,title) {
accumulator.push(title);
});
assert.equal(accumulator,result,message + " (sqlEachTiddler)");
}
function checkAllShadowTitles(result,message) {
const titles = sqlFunctions.sqlAllShadowTitles();
assert.equal(titles,result,message);
}
// Set priorities for the plugins we'll use
sqlFunctions.sqlSetPluginPriorities([]);
// Save and verify an ordinary tiddler
sqlFunctions.sqlSaveTiddler({
title: "HelloThere",
text: "This is a tiddler"
});
checkExists("HelloThere",true,"Check the tiddler exists");
checkTiddler("HelloThere",{
title: "HelloThere",
text: "This is a tiddler"
},"Retrieve the tiddler");
checkShadowSource("HelloThere",null,"Check that the shadow source is correct");
// Delete the tiddler and check it no longer exists
sqlFunctions.sqlDeleteTiddler("HelloThere");
checkTiddler("HelloThere",undefined,"Try to retrieve the deleted tiddler");
checkExists("HelloThere",false,"Check the tiddler doesn't exist");
checkAllTitles([],"Check that the title list is correct");
checkAllShadowTitles([],"Check that the shadow title list is correct");
// Save and verify a shadow tiddler
sqlFunctions.sqlSetPluginPriorities(["myplugin"]);
sqlFunctions.sqlSaveTiddler({
title: "HelloThere",
text: "This is a shadow tiddler"
},"myplugin");
// Check that the shadow tiddler exists and has the expected value
checkExists("HelloThere",false,"Check the shadow tiddler does not exist");
checkTiddler("HelloThere",{
title: "HelloThere",
text: "This is a shadow tiddler"
},"Retrieve the tiddler");
checkShadowSource("HelloThere","myplugin","Check that the shadow source is correct");
sqlFunctions.sqlLogTables();
checkAllShadowTitles(["HelloThere"],"Check that the shadow title list is correct");
// Save an ordinary tiddler over the top and check it can be retrieved
sqlFunctions.sqlSaveTiddler({
title: "HelloThere",
text: "This is a tiddler"
});
checkExists("HelloThere",true,"Check the tiddler exists");
checkTiddler("HelloThere",{
title: "HelloThere",
text: "This is a tiddler"
},"Retrieve the tiddler");
checkAllTitles(["HelloThere"],"Check that the title list is correct");
checkShadowSource("HelloThere","myplugin","Check that the shadow source is correct");
checkAllShadowTitles(["HelloThere"],"Check that the shadow title list is correct");
// Delete the ordinary tiddler and check that the shadow tiddler is still available
sqlFunctions.sqlDeleteTiddler("HelloThere");
checkTiddler("HelloThere",{
title: "HelloThere",
text: "This is a shadow tiddler"
},"Try to retrieve the shadow tiddler exposed by the deleted tiddler");
checkShadowSource("HelloThere","myplugin","Check that the shadow source is correct");
checkAllShadowTitles(["HelloThere"],"Check that the shadow title list is correct");
});
});
// Run the tests
run();
};
//# sourceURL=$:/plugins/tiddlywiki/sqlite3store/test-sql-functions.js