APIv5 Query API

Aurora Lahtela edited this page Dec 11, 2022 · 10 revisions

Plan Header

Plan API version 5 - Query API

This page is about API that is available in version 4.9.0 and above.
API can be called on all platforms.

This API requires QUERY_API capability.

See APIv5 for dependency information

💭 What is this API for?

Query API is for accessing the Plan database from within your plugin. This can be used to store data in the database, or to write custom queries against the database.

Table of contents

  • Obtaining QueryService
  • Checking that database is what you expect
  • Performing Queries
    • Available pre-made queries
  • Executing Statements
  • Recap of Thread blocking

Obtaining QueryService

QueryService can be obtained like so:

try {
QueryServicequeryService = QueryService.getInstance();
} catch (IllegalStateExceptionplanIsNotEnabled) {
// Plan is not enabled, handle exception
} 

Checking that database is what you expect

Database schema might change due to optimizations or additions so it might be necessary to check if the schema matches what you need.

You can check if a table exists like this:

booleanhasTable = queryService.getCommonQueries().doesDBHaveTable(tableName);

You can check if a table has a column like this:

booleanhasColumn = queryService.getCommonQueries().doesDBHaveTableColumn(tableName, columName);

Any method called in CommonQueries blocks the thread.

In order to perform queries in the correct SQL dialect, you might need the following code.

StringdbType = queryService.getDBType(); // SQLITE, MYSQL

Performing queries

Queries can be done with QueryService#query method. Queries block the thread.

Here is an example of a completed query

Stringresult = queryService.query(
"SELECT * FROM plan_users WHERE uuid=?",
(PreparedStatement) statement -> {
statement.setString(1, playerUUID.toString());
try (ResultSetresults = statement.executeQuery()) {
returnresults.next ? results.getString("name") : null;
}
}
);

Available pre-made queries

Some queries can be performed without the query method above, they are available with QueryService#getCommonQueries

Any method called in CommonQueries blocks the thread.

Executing statements

Statements can be executed with QueryService#execute method.

Here is an example of a executed statement

Future<?> done = queryService.execute(
"INSERT INTO custom_table (uuid, value) VALUES (?, ?)",
(PreparedStatement) statement -> {
statement.setString(1, playerUUID.toString());
statement.setString(2, value);
statement.execute();
}
);
done.get(); // (Optional) Block thread until transaction was executed

Note: Transactions

execute does not block the thread, because the SQL is executed on another thread concurrently. If you would like to wait for the statement to execute you can call Future#get() to block the thread.

Do not call Future#get() inside execute - This might deadlock the whole database due to blocked transaction thread!

Example of update - insert if not exist procedure
publicvoidstoreProtocolVersion(UUIDuuid, intversion) throwsExecutionException {
Stringupdate = "UPDATE plan_version_protocol SET protocol_version=? WHERE uuid=?";
Stringinsert = "INSERT INTO plan_version_protocol (protocol_version, uuid) VALUES (?, ?)";
AtomicBooleanupdated = newAtomicBoolean(false);
try {
queryService.execute(update, statement -> {
statement.setInt(1, version);
statement.setString(2, uuid.toString());
updated.set(statement.executeUpdate() > 0);
}).get(); // Waitif (!updated.get()) {
queryService.execute(insert, statement -> {
statement.setInt(1, version);
statement.setString(2, uuid.toString());
statement.execute();
});
}
} catch (InterruptedExceptione) {
Thread.currentThread().interrupt();
}
}

Recap on thread blocking

Blocking the server-thread usually leads to a crash. If something blocks a thread it is best to be executed inside an asynchronous task or on a separate thread.

  • QueryService#execute does not block the thread.
  • The Future returned by QueryService#execute can be used to block the thread until SQL executes with Future#get.
  • QueryService#query blocks the thread.
  • All methods in CommonQueries block the thread.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

APIv5 Query API

Aurora Lahtela edited this page Dec 11, 2022 · 10 revisions

Plan Header

Plan API version 5 - Query API

This page is about API that is available in version 4.9.0 and above.
API can be called on all platforms.

This API requires QUERY_API capability.

See APIv5 for dependency information

💭 What is this API for?

Query API is for accessing the Plan database from within your plugin. This can be used to store data in the database, or to write custom queries against the database.

Table of contents

  • Obtaining QueryService
  • Checking that database is what you expect
  • Performing Queries
    • Available pre-made queries
  • Executing Statements
  • Recap of Thread blocking

Obtaining QueryService

QueryService can be obtained like so:

try {
QueryServicequeryService = QueryService.getInstance();
} catch (IllegalStateExceptionplanIsNotEnabled) {
// Plan is not enabled, handle exception
} 

Checking that database is what you expect

Database schema might change due to optimizations or additions so it might be necessary to check if the schema matches what you need.

You can check if a table exists like this:

booleanhasTable = queryService.getCommonQueries().doesDBHaveTable(tableName);

You can check if a table has a column like this:

booleanhasColumn = queryService.getCommonQueries().doesDBHaveTableColumn(tableName, columName);

Any method called in CommonQueries blocks the thread.

In order to perform queries in the correct SQL dialect, you might need the following code.

StringdbType = queryService.getDBType(); // SQLITE, MYSQL

Performing queries

Queries can be done with QueryService#query method. Queries block the thread.

Here is an example of a completed query

Stringresult = queryService.query(
"SELECT * FROM plan_users WHERE uuid=?",
(PreparedStatement) statement -> {
statement.setString(1, playerUUID.toString());
try (ResultSetresults = statement.executeQuery()) {
returnresults.next ? results.getString("name") : null;
}
}
);

Available pre-made queries

Some queries can be performed without the query method above, they are available with QueryService#getCommonQueries

Any method called in CommonQueries blocks the thread.

Executing statements

Statements can be executed with QueryService#execute method.

Here is an example of a executed statement

Future<?> done = queryService.execute(
"INSERT INTO custom_table (uuid, value) VALUES (?, ?)",
(PreparedStatement) statement -> {
statement.setString(1, playerUUID.toString());
statement.setString(2, value);
statement.execute();
}
);
done.get(); // (Optional) Block thread until transaction was executed

Note: Transactions

execute does not block the thread, because the SQL is executed on another thread concurrently. If you would like to wait for the statement to execute you can call Future#get() to block the thread.

Do not call Future#get() inside execute - This might deadlock the whole database due to blocked transaction thread!

Example of update - insert if not exist procedure
publicvoidstoreProtocolVersion(UUIDuuid, intversion) throwsExecutionException {
Stringupdate = "UPDATE plan_version_protocol SET protocol_version=? WHERE uuid=?";
Stringinsert = "INSERT INTO plan_version_protocol (protocol_version, uuid) VALUES (?, ?)";
AtomicBooleanupdated = newAtomicBoolean(false);
try {
queryService.execute(update, statement -> {
statement.setInt(1, version);
statement.setString(2, uuid.toString());
updated.set(statement.executeUpdate() > 0);
}).get(); // Waitif (!updated.get()) {
queryService.execute(insert, statement -> {
statement.setInt(1, version);
statement.setString(2, uuid.toString());
statement.execute();
});
}
} catch (InterruptedExceptione) {
Thread.currentThread().interrupt();
}
}

Recap on thread blocking

Blocking the server-thread usually leads to a crash. If something blocks a thread it is best to be executed inside an asynchronous task or on a separate thread.

  • QueryService#execute does not block the thread.
  • The Future returned by QueryService#execute can be used to block the thread until SQL executes with Future#get.
  • QueryService#query blocks the thread.
  • All methods in CommonQueries block the thread.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

APIv5 Query API

Aurora Lahtela edited this page Dec 11, 2022 · 10 revisions

Plan Header

Plan API version 5 - Query API

This page is about API that is available in version 4.9.0 and above.
API can be called on all platforms.

This API requires QUERY_API capability.

See APIv5 for dependency information

💭 What is this API for?

Query API is for accessing the Plan database from within your plugin. This can be used to store data in the database, or to write custom queries against the database.

Table of contents

  • Obtaining QueryService
  • Checking that database is what you expect
  • Performing Queries
    • Available pre-made queries
  • Executing Statements
  • Recap of Thread blocking

Obtaining QueryService

QueryService can be obtained like so:

try {
QueryServicequeryService = QueryService.getInstance();
} catch (IllegalStateExceptionplanIsNotEnabled) {
// Plan is not enabled, handle exception
} 

Checking that database is what you expect

Database schema might change due to optimizations or additions so it might be necessary to check if the schema matches what you need.

You can check if a table exists like this:

booleanhasTable = queryService.getCommonQueries().doesDBHaveTable(tableName);

You can check if a table has a column like this:

booleanhasColumn = queryService.getCommonQueries().doesDBHaveTableColumn(tableName, columName);

Any method called in CommonQueries blocks the thread.

In order to perform queries in the correct SQL dialect, you might need the following code.

StringdbType = queryService.getDBType(); // SQLITE, MYSQL

Performing queries

Queries can be done with QueryService#query method. Queries block the thread.

Here is an example of a completed query

Stringresult = queryService.query(
"SELECT * FROM plan_users WHERE uuid=?",
(PreparedStatement) statement -> {
statement.setString(1, playerUUID.toString());
try (ResultSetresults = statement.executeQuery()) {
returnresults.next ? results.getString("name") : null;
}
}
);

Available pre-made queries

Some queries can be performed without the query method above, they are available with QueryService#getCommonQueries

Any method called in CommonQueries blocks the thread.

Executing statements

Statements can be executed with QueryService#execute method.

Here is an example of a executed statement

Future<?> done = queryService.execute(
"INSERT INTO custom_table (uuid, value) VALUES (?, ?)",
(PreparedStatement) statement -> {
statement.setString(1, playerUUID.toString());
statement.setString(2, value);
statement.execute();
}
);
done.get(); // (Optional) Block thread until transaction was executed

Note: Transactions

execute does not block the thread, because the SQL is executed on another thread concurrently. If you would like to wait for the statement to execute you can call Future#get() to block the thread.

Do not call Future#get() inside execute - This might deadlock the whole database due to blocked transaction thread!

Example of update - insert if not exist procedure
publicvoidstoreProtocolVersion(UUIDuuid, intversion) throwsExecutionException {
Stringupdate = "UPDATE plan_version_protocol SET protocol_version=? WHERE uuid=?";
Stringinsert = "INSERT INTO plan_version_protocol (protocol_version, uuid) VALUES (?, ?)";
AtomicBooleanupdated = newAtomicBoolean(false);
try {
queryService.execute(update, statement -> {
statement.setInt(1, version);
statement.setString(2, uuid.toString());
updated.set(statement.executeUpdate() > 0);
}).get(); // Waitif (!updated.get()) {
queryService.execute(insert, statement -> {
statement.setInt(1, version);
statement.setString(2, uuid.toString());
statement.execute();
});
}
} catch (InterruptedExceptione) {
Thread.currentThread().interrupt();
}
}

Recap on thread blocking

Blocking the server-thread usually leads to a crash. If something blocks a thread it is best to be executed inside an asynchronous task or on a separate thread.

  • QueryService#execute does not block the thread.
  • The Future returned by QueryService#execute can be used to block the thread until SQL executes with Future#get.
  • QueryService#query blocks the thread.
  • All methods in CommonQueries block the thread.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

APIv5 Query API

Aurora Lahtela edited this page Dec 11, 2022 · 10 revisions

Plan Header

Plan API version 5 - Query API

This page is about API that is available in version 4.9.0 and above.
API can be called on all platforms.

This API requires QUERY_API capability.

See APIv5 for dependency information

💭 What is this API for?

Query API is for accessing the Plan database from within your plugin. This can be used to store data in the database, or to write custom queries against the database.

Table of contents

  • Obtaining QueryService
  • Checking that database is what you expect
  • Performing Queries
    • Available pre-made queries
  • Executing Statements
  • Recap of Thread blocking

Obtaining QueryService

QueryService can be obtained like so:

try {
QueryServicequeryService = QueryService.getInstance();
} catch (IllegalStateExceptionplanIsNotEnabled) {
// Plan is not enabled, handle exception
} 

Checking that database is what you expect

Database schema might change due to optimizations or additions so it might be necessary to check if the schema matches what you need.

You can check if a table exists like this:

booleanhasTable = queryService.getCommonQueries().doesDBHaveTable(tableName);

You can check if a table has a column like this:

booleanhasColumn = queryService.getCommonQueries().doesDBHaveTableColumn(tableName, columName);

Any method called in CommonQueries blocks the thread.

In order to perform queries in the correct SQL dialect, you might need the following code.

StringdbType = queryService.getDBType(); // SQLITE, MYSQL

Performing queries

Queries can be done with QueryService#query method. Queries block the thread.

Here is an example of a completed query

Stringresult = queryService.query(
"SELECT * FROM plan_users WHERE uuid=?",
(PreparedStatement) statement -> {
statement.setString(1, playerUUID.toString());
try (ResultSetresults = statement.executeQuery()) {
returnresults.next ? results.getString("name") : null;
}
}
);

Available pre-made queries

Some queries can be performed without the query method above, they are available with QueryService#getCommonQueries

Any method called in CommonQueries blocks the thread.

Executing statements

Statements can be executed with QueryService#execute method.

Here is an example of a executed statement

Future<?> done = queryService.execute(
"INSERT INTO custom_table (uuid, value) VALUES (?, ?)",
(PreparedStatement) statement -> {
statement.setString(1, playerUUID.toString());
statement.setString(2, value);
statement.execute();
}
);
done.get(); // (Optional) Block thread until transaction was executed

Note: Transactions

execute does not block the thread, because the SQL is executed on another thread concurrently. If you would like to wait for the statement to execute you can call Future#get() to block the thread.

Do not call Future#get() inside execute - This might deadlock the whole database due to blocked transaction thread!

Example of update - insert if not exist procedure
publicvoidstoreProtocolVersion(UUIDuuid, intversion) throwsExecutionException {
Stringupdate = "UPDATE plan_version_protocol SET protocol_version=? WHERE uuid=?";
Stringinsert = "INSERT INTO plan_version_protocol (protocol_version, uuid) VALUES (?, ?)";
AtomicBooleanupdated = newAtomicBoolean(false);
try {
queryService.execute(update, statement -> {
statement.setInt(1, version);
statement.setString(2, uuid.toString());
updated.set(statement.executeUpdate() > 0);
}).get(); // Waitif (!updated.get()) {
queryService.execute(insert, statement -> {
statement.setInt(1, version);
statement.setString(2, uuid.toString());
statement.execute();
});
}
} catch (InterruptedExceptione) {
Thread.currentThread().interrupt();
}
}

Recap on thread blocking

Blocking the server-thread usually leads to a crash. If something blocks a thread it is best to be executed inside an asynchronous task or on a separate thread.

  • QueryService#execute does not block the thread.
  • The Future returned by QueryService#execute can be used to block the thread until SQL executes with Future#get.
  • QueryService#query blocks the thread.
  • All methods in CommonQueries block the thread.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

APIv5 Query API

Aurora Lahtela edited this page Dec 11, 2022 · 10 revisions

Plan Header

Plan API version 5 - Query API

This page is about API that is available in version 4.9.0 and above.
API can be called on all platforms.

This API requires QUERY_API capability.

See APIv5 for dependency information

💭 What is this API for?

Query API is for accessing the Plan database from within your plugin. This can be used to store data in the database, or to write custom queries against the database.

Table of contents

  • Obtaining QueryService
  • Checking that database is what you expect
  • Performing Queries
    • Available pre-made queries
  • Executing Statements
  • Recap of Thread blocking

Obtaining QueryService

QueryService can be obtained like so:

try {
QueryServicequeryService = QueryService.getInstance();
} catch (IllegalStateExceptionplanIsNotEnabled) {
// Plan is not enabled, handle exception
} 

Checking that database is what you expect

Database schema might change due to optimizations or additions so it might be necessary to check if the schema matches what you need.

You can check if a table exists like this:

booleanhasTable = queryService.getCommonQueries().doesDBHaveTable(tableName);

You can check if a table has a column like this:

booleanhasColumn = queryService.getCommonQueries().doesDBHaveTableColumn(tableName, columName);

Any method called in CommonQueries blocks the thread.

In order to perform queries in the correct SQL dialect, you might need the following code.

StringdbType = queryService.getDBType(); // SQLITE, MYSQL

Performing queries

Queries can be done with QueryService#query method. Queries block the thread.

Here is an example of a completed query

Stringresult = queryService.query(
"SELECT * FROM plan_users WHERE uuid=?",
(PreparedStatement) statement -> {
statement.setString(1, playerUUID.toString());
try (ResultSetresults = statement.executeQuery()) {
returnresults.next ? results.getString("name") : null;
}
}
);

Available pre-made queries

Some queries can be performed without the query method above, they are available with QueryService#getCommonQueries

Any method called in CommonQueries blocks the thread.

Executing statements

Statements can be executed with QueryService#execute method.

Here is an example of a executed statement

Future<?> done = queryService.execute(
"INSERT INTO custom_table (uuid, value) VALUES (?, ?)",
(PreparedStatement) statement -> {
statement.setString(1, playerUUID.toString());
statement.setString(2, value);
statement.execute();
}
);
done.get(); // (Optional) Block thread until transaction was executed

Note: Transactions

execute does not block the thread, because the SQL is executed on another thread concurrently. If you would like to wait for the statement to execute you can call Future#get() to block the thread.

Do not call Future#get() inside execute - This might deadlock the whole database due to blocked transaction thread!

Example of update - insert if not exist procedure
publicvoidstoreProtocolVersion(UUIDuuid, intversion) throwsExecutionException {
Stringupdate = "UPDATE plan_version_protocol SET protocol_version=? WHERE uuid=?";
Stringinsert = "INSERT INTO plan_version_protocol (protocol_version, uuid) VALUES (?, ?)";
AtomicBooleanupdated = newAtomicBoolean(false);
try {
queryService.execute(update, statement -> {
statement.setInt(1, version);
statement.setString(2, uuid.toString());
updated.set(statement.executeUpdate() > 0);
}).get(); // Waitif (!updated.get()) {
queryService.execute(insert, statement -> {
statement.setInt(1, version);
statement.setString(2, uuid.toString());
statement.execute();
});
}
} catch (InterruptedExceptione) {
Thread.currentThread().interrupt();
}
}

Recap on thread blocking

Blocking the server-thread usually leads to a crash. If something blocks a thread it is best to be executed inside an asynchronous task or on a separate thread.

  • QueryService#execute does not block the thread.
  • The Future returned by QueryService#execute can be used to block the thread until SQL executes with Future#get.
  • QueryService#query blocks the thread.
  • All methods in CommonQueries block the thread.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

APIv5 Query API

Aurora Lahtela edited this page Dec 11, 2022 · 10 revisions

Plan Header

Plan API version 5 - Query API

This page is about API that is available in version 4.9.0 and above.
API can be called on all platforms.

This API requires QUERY_API capability.

See APIv5 for dependency information

💭 What is this API for?

Query API is for accessing the Plan database from within your plugin. This can be used to store data in the database, or to write custom queries against the database.

Table of contents

  • Obtaining QueryService
  • Checking that database is what you expect
  • Performing Queries
    • Available pre-made queries
  • Executing Statements
  • Recap of Thread blocking

Obtaining QueryService

QueryService can be obtained like so:

try {
QueryServicequeryService = QueryService.getInstance();
} catch (IllegalStateExceptionplanIsNotEnabled) {
// Plan is not enabled, handle exception
} 

Checking that database is what you expect

Database schema might change due to optimizations or additions so it might be necessary to check if the schema matches what you need.

You can check if a table exists like this:

booleanhasTable = queryService.getCommonQueries().doesDBHaveTable(tableName);

You can check if a table has a column like this:

booleanhasColumn = queryService.getCommonQueries().doesDBHaveTableColumn(tableName, columName);

Any method called in CommonQueries blocks the thread.

In order to perform queries in the correct SQL dialect, you might need the following code.

StringdbType = queryService.getDBType(); // SQLITE, MYSQL

Performing queries

Queries can be done with QueryService#query method. Queries block the thread.

Here is an example of a completed query

Stringresult = queryService.query(
"SELECT * FROM plan_users WHERE uuid=?",
(PreparedStatement) statement -> {
statement.setString(1, playerUUID.toString());
try (ResultSetresults = statement.executeQuery()) {
returnresults.next ? results.getString("name") : null;
}
}
);

Available pre-made queries

Some queries can be performed without the query method above, they are available with QueryService#getCommonQueries

Any method called in CommonQueries blocks the thread.

Executing statements

Statements can be executed with QueryService#execute method.

Here is an example of a executed statement

Future<?> done = queryService.execute(
"INSERT INTO custom_table (uuid, value) VALUES (?, ?)",
(PreparedStatement) statement -> {
statement.setString(1, playerUUID.toString());
statement.setString(2, value);
statement.execute();
}
);
done.get(); // (Optional) Block thread until transaction was executed

Note: Transactions

execute does not block the thread, because the SQL is executed on another thread concurrently. If you would like to wait for the statement to execute you can call Future#get() to block the thread.

Do not call Future#get() inside execute - This might deadlock the whole database due to blocked transaction thread!

Example of update - insert if not exist procedure
publicvoidstoreProtocolVersion(UUIDuuid, intversion) throwsExecutionException {
Stringupdate = "UPDATE plan_version_protocol SET protocol_version=? WHERE uuid=?";
Stringinsert = "INSERT INTO plan_version_protocol (protocol_version, uuid) VALUES (?, ?)";
AtomicBooleanupdated = newAtomicBoolean(false);
try {
queryService.execute(update, statement -> {
statement.setInt(1, version);
statement.setString(2, uuid.toString());
updated.set(statement.executeUpdate() > 0);
}).get(); // Waitif (!updated.get()) {
queryService.execute(insert, statement -> {
statement.setInt(1, version);
statement.setString(2, uuid.toString());
statement.execute();
});
}
} catch (InterruptedExceptione) {
Thread.currentThread().interrupt();
}
}

Recap on thread blocking

Blocking the server-thread usually leads to a crash. If something blocks a thread it is best to be executed inside an asynchronous task or on a separate thread.

  • QueryService#execute does not block the thread.
  • The Future returned by QueryService#execute can be used to block the thread until SQL executes with Future#get.
  • QueryService#query blocks the thread.
  • All methods in CommonQueries block the thread.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

APIv5 Query API

Aurora Lahtela edited this page Dec 11, 2022 · 10 revisions

Plan Header

Plan API version 5 - Query API

This page is about API that is available in version 4.9.0 and above.
API can be called on all platforms.

This API requires QUERY_API capability.

See APIv5 for dependency information

💭 What is this API for?

Query API is for accessing the Plan database from within your plugin. This can be used to store data in the database, or to write custom queries against the database.

Table of contents

  • Obtaining QueryService
  • Checking that database is what you expect
  • Performing Queries
    • Available pre-made queries
  • Executing Statements
  • Recap of Thread blocking

Obtaining QueryService

QueryService can be obtained like so:

try {
QueryServicequeryService = QueryService.getInstance();
} catch (IllegalStateExceptionplanIsNotEnabled) {
// Plan is not enabled, handle exception
} 

Checking that database is what you expect

Database schema might change due to optimizations or additions so it might be necessary to check if the schema matches what you need.

You can check if a table exists like this:

booleanhasTable = queryService.getCommonQueries().doesDBHaveTable(tableName);

You can check if a table has a column like this:

booleanhasColumn = queryService.getCommonQueries().doesDBHaveTableColumn(tableName, columName);

Any method called in CommonQueries blocks the thread.

In order to perform queries in the correct SQL dialect, you might need the following code.

StringdbType = queryService.getDBType(); // SQLITE, MYSQL

Performing queries

Queries can be done with QueryService#query method. Queries block the thread.

Here is an example of a completed query

Stringresult = queryService.query(
"SELECT * FROM plan_users WHERE uuid=?",
(PreparedStatement) statement -> {
statement.setString(1, playerUUID.toString());
try (ResultSetresults = statement.executeQuery()) {
returnresults.next ? results.getString("name") : null;
}
}
);

Available pre-made queries

Some queries can be performed without the query method above, they are available with QueryService#getCommonQueries

Any method called in CommonQueries blocks the thread.

Executing statements

Statements can be executed with QueryService#execute method.

Here is an example of a executed statement

Future<?> done = queryService.execute(
"INSERT INTO custom_table (uuid, value) VALUES (?, ?)",
(PreparedStatement) statement -> {
statement.setString(1, playerUUID.toString());
statement.setString(2, value);
statement.execute();
}
);
done.get(); // (Optional) Block thread until transaction was executed

Note: Transactions

execute does not block the thread, because the SQL is executed on another thread concurrently. If you would like to wait for the statement to execute you can call Future#get() to block the thread.

Do not call Future#get() inside execute - This might deadlock the whole database due to blocked transaction thread!

Example of update - insert if not exist procedure
publicvoidstoreProtocolVersion(UUIDuuid, intversion) throwsExecutionException {
Stringupdate = "UPDATE plan_version_protocol SET protocol_version=? WHERE uuid=?";
Stringinsert = "INSERT INTO plan_version_protocol (protocol_version, uuid) VALUES (?, ?)";
AtomicBooleanupdated = newAtomicBoolean(false);
try {
queryService.execute(update, statement -> {
statement.setInt(1, version);
statement.setString(2, uuid.toString());
updated.set(statement.executeUpdate() > 0);
}).get(); // Waitif (!updated.get()) {
queryService.execute(insert, statement -> {
statement.setInt(1, version);
statement.setString(2, uuid.toString());
statement.execute();
});
}
} catch (InterruptedExceptione) {
Thread.currentThread().interrupt();
}
}

Recap on thread blocking

Blocking the server-thread usually leads to a crash. If something blocks a thread it is best to be executed inside an asynchronous task or on a separate thread.

  • QueryService#execute does not block the thread.
  • The Future returned by QueryService#execute can be used to block the thread until SQL executes with Future#get.
  • QueryService#query blocks the thread.
  • All methods in CommonQueries block the thread.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

APIv5 Query API

Aurora Lahtela edited this page Dec 11, 2022 · 10 revisions

Plan Header

Plan API version 5 - Query API

This page is about API that is available in version 4.9.0 and above.
API can be called on all platforms.

This API requires QUERY_API capability.

See APIv5 for dependency information

💭 What is this API for?

Query API is for accessing the Plan database from within your plugin. This can be used to store data in the database, or to write custom queries against the database.

Table of contents

  • Obtaining QueryService
  • Checking that database is what you expect
  • Performing Queries
    • Available pre-made queries
  • Executing Statements
  • Recap of Thread blocking

Obtaining QueryService

QueryService can be obtained like so:

try {
QueryServicequeryService = QueryService.getInstance();
} catch (IllegalStateExceptionplanIsNotEnabled) {
// Plan is not enabled, handle exception
} 

Checking that database is what you expect

Database schema might change due to optimizations or additions so it might be necessary to check if the schema matches what you need.

You can check if a table exists like this:

booleanhasTable = queryService.getCommonQueries().doesDBHaveTable(tableName);

You can check if a table has a column like this:

booleanhasColumn = queryService.getCommonQueries().doesDBHaveTableColumn(tableName, columName);

Any method called in CommonQueries blocks the thread.

In order to perform queries in the correct SQL dialect, you might need the following code.

StringdbType = queryService.getDBType(); // SQLITE, MYSQL

Performing queries

Queries can be done with QueryService#query method. Queries block the thread.

Here is an example of a completed query

Stringresult = queryService.query(
"SELECT * FROM plan_users WHERE uuid=?",
(PreparedStatement) statement -> {
statement.setString(1, playerUUID.toString());
try (ResultSetresults = statement.executeQuery()) {
returnresults.next ? results.getString("name") : null;
}
}
);

Available pre-made queries

Some queries can be performed without the query method above, they are available with QueryService#getCommonQueries

Any method called in CommonQueries blocks the thread.

Executing statements

Statements can be executed with QueryService#execute method.

Here is an example of a executed statement

Future<?> done = queryService.execute(
"INSERT INTO custom_table (uuid, value) VALUES (?, ?)",
(PreparedStatement) statement -> {
statement.setString(1, playerUUID.toString());
statement.setString(2, value);
statement.execute();
}
);
done.get(); // (Optional) Block thread until transaction was executed

Note: Transactions

execute does not block the thread, because the SQL is executed on another thread concurrently. If you would like to wait for the statement to execute you can call Future#get() to block the thread.

Do not call Future#get() inside execute - This might deadlock the whole database due to blocked transaction thread!

Example of update - insert if not exist procedure
publicvoidstoreProtocolVersion(UUIDuuid, intversion) throwsExecutionException {
Stringupdate = "UPDATE plan_version_protocol SET protocol_version=? WHERE uuid=?";
Stringinsert = "INSERT INTO plan_version_protocol (protocol_version, uuid) VALUES (?, ?)";
AtomicBooleanupdated = newAtomicBoolean(false);
try {
queryService.execute(update, statement -> {
statement.setInt(1, version);
statement.setString(2, uuid.toString());
updated.set(statement.executeUpdate() > 0);
}).get(); // Waitif (!updated.get()) {
queryService.execute(insert, statement -> {
statement.setInt(1, version);
statement.setString(2, uuid.toString());
statement.execute();
});
}
} catch (InterruptedExceptione) {
Thread.currentThread().interrupt();
}
}

Recap on thread blocking

Blocking the server-thread usually leads to a crash. If something blocks a thread it is best to be executed inside an asynchronous task or on a separate thread.

  • QueryService#execute does not block the thread.
  • The Future returned by QueryService#execute can be used to block the thread until SQL executes with Future#get.
  • QueryService#query blocks the thread.
  • All methods in CommonQueries block the thread.

Clone this wiki locally