diff --git a/.claude/memory/advanced_search.md b/.claude/memory/advanced_search.md
new file mode 100644
index 000000000..bcb762464
--- /dev/null
+++ b/.claude/memory/advanced_search.md
@@ -0,0 +1,77 @@
+# Advanced Search Feature
+
+## Status: Implemented and working (v4)
+
+## What was built
+Multi-condition advanced search panel for the Rows tab. Accessible via an "Advanced" toggle button next to the existing Apply/× buttons.
+
+### Features (v1)
+- Multiple filter conditions (unlimited rows)
+- "Advanced Filter Active" badge in pagination row when active
+- Right-click "Filter Rows By Value" appends a pre-filled row when panel is open
+- Resets on table switch and basic × reset button
+- `#output` top offset recalculated when panel opens/closes
+
+### Features added in v2
+- **Per-row AND/OR connector pills** — each row after the first has AND/OR buttons; first row shows "WHERE" label; enables `(A) AND (B) OR (C)` style expressions
+- **Show Query button** — toggles a `
` box showing the full `SELECT * FROM "schema"."table" WHERE ...;` with a Copy button (reuses `copyToClipboard()`)
+- **Expanded operator set** (18 operators, grouped with ``):
+ - Comparison: `=`, `<>`, `<`, `>`, `<=`, `>=`
+ - List: `IN` (comma-sep → `IN ('a','b')`), `NOT IN`
+ - Null: `IS NULL`, `IS NOT NULL`
+ - Range: `BETWEEN` (From/To inputs), `NOT BETWEEN`
+ - Pattern: Contains, Not contains, Has prefix, Has suffix + case-insensitive variants (ILIKE)
+ - Regex: Matches regex (`~`), Matches regex case insensitive (`~*`)
+- **`getOpInputType(op)`** helper: returns `"none" | "single" | "list" | "range"` — controls which input variant is shown
+- **`buildFullQuery()`** — builds full SELECT string using `getCurrentObject().name`
+
+## Files changed
+- `static/index.html` — Advanced button, `#advanced_search_panel`, `#adv_search_active_badge`, Show Query button, `#adv_query_display`
+- `static/css/app.css` — appended styles for panel, connector pills, range inputs, query display box
+- `static/js/app.js` — see key functions below
+
+## Key JS functions (app.js)
+- `var advancedSearchActive = false` — global flag
+- `escapeSqlLiteral(val)` — doubles single-quotes (also applied to simple filter)
+- `getOpInputType(op)` — returns input variant type for an operator
+- `buildAdvancedSearchRow(isFirst)` — builds a condition row; `isFirst=true` → WHERE label, no pill
+- `buildAdvancedWhereClause()` — iterates rows, reads `data-row-conj` per row, builds SQL
+- `buildFullQuery()` — wraps WHERE clause in full SELECT statement
+- `applyAdvancedSearch()` — stores WHERE in panel `.data("where")`, sets flag, reloads
+- `resetAdvancedSearch()` — clears flag/badge, empties rows, adds `buildAdvancedSearchRow(true)`
+- `adjustOutputTop()` — sets `#output` CSS top to `#pagination` outerHeight
+- `bindAdvancedOpHandlers()` — delegated handler showing correct input variant per operator
+
+## Bug fix (v4b): first row showed unnecessary − delete button
+- `buildAdvancedSearchRow(isFirst)` now only appends the remove button when `isFirst=false`
+
+## Added in v4: regex operators
+- `"regex": "~ 'DATA'"` and `"iregex": "~* 'DATA'"` added to `filterOptions`
+- Two new options appended to the Pattern `` in `buildAdvancedSearchRow()`
+- No other changes needed — `getOpInputType()` returns `"single"` by default, `buildAdvancedWhereClause()` handles it unchanged
+
+## Bug fix (v3): advanced panel obscuring table rows
+- **Root cause**: `.with-pagination #output { top: 50px !important }` in `app.css` — the `!important` beat jQuery's inline style set by `adjustOutputTop()`
+- **Fix 1**: removed `!important` from that CSS rule so JS inline style wins
+- **Fix 2**: added `adjustOutputTop()` call immediately after `$("#body").prop("class", "with-pagination")` in `showTableContent()` — so offset is recalculated on every table load, not just on panel open/close
+
+## Key JS edits (app.js)
+- `showTableContent()` — advanced takes precedence over simple filter when `advancedSearchActive`; calls `adjustOutputTop()` after setting `with-pagination` class
+- `buildTableFilters()` — syncs columns into existing advanced rows; passes `isFirst=true`
+- Objects click handler — calls `resetAdvancedSearch()` on table switch
+- `reset-filters` button — calls `resetAdvancedSearch()`
+- `filter_by_value` context menu — appends pre-filled row when panel visible
+
+## No backend changes needed
+The existing `where` param on `GET /api/tables/:table/rows` accepts raw SQL.
+`TableRows()` in `pkg/client/client.go` appends `WHERE ` directly.
+
+## Build command (GOROOT is broken in this environment)
+```bash
+GOROOT=/opt/homebrew/Cellar/go/1.25.7_1/libexec \
+GOPROXY=https://proxy.golang.org,direct \
+GONOSUMDB='*' \
+GOOS=linux GOARCH=amd64 \
+go build -o ./bin/pgweb_linux_amd64
+```
+Output: `bin/pgweb_linux_amd64` (~28MB, ELF 64-bit, statically linked)
diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 73572edfe..7bf165bca 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -6,7 +6,8 @@
"Bash(git checkout:*)",
"Bash(go test:*)",
"Bash(go build:*)",
- "Bash(make:*)"
+ "Bash(make:*)",
+ "Bash(go env:*)"
]
}
}
diff --git a/pkg/command/version.go b/pkg/command/version.go
index 143a8194c..b83ca8bc0 100644
--- a/pkg/command/version.go
+++ b/pkg/command/version.go
@@ -8,7 +8,7 @@ import (
const (
// Version is the current Pgweb application version
- Version = "0.17.0"
+ Version = "0.17.0p1"
)
var (
diff --git a/static/css/app.css b/static/css/app.css
index b3aaebdfe..b5d8a205a 100644
--- a/static/css/app.css
+++ b/static/css/app.css
@@ -615,7 +615,7 @@
}
.with-pagination #output {
- top: 50px !important;
+ top: 50px;
}
.with-pagination #pagination {
@@ -914,3 +914,176 @@
.ace_autocomplete .ace_active-line {
background: #eee !important;
}
+
+/* ------------------------------------------------------------------ */
+/* Advanced Search Panel */
+/* ------------------------------------------------------------------ */
+
+#pagination.adv-search-open {
+ height: auto;
+ min-height: 50px;
+}
+
+#advanced_search_panel {
+ clear: both;
+ padding: 8px 10px 4px 10px;
+ border-top: 1px solid #eee;
+ background: #fafafa;
+}
+
+.adv-search-header {
+ font-size: 12px;
+ margin-bottom: 6px;
+ line-height: 26px;
+}
+
+.adv-match-label {
+ color: #777;
+ margin: 0 6px;
+}
+
+.adv-conjunction-toggle {
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.adv-search-row {
+ display: flex;
+ align-items: center;
+ margin-bottom: 4px;
+ gap: 6px;
+}
+
+.adv-search-row .adv-col {
+ width: 150px;
+ flex-shrink: 0;
+ height: 28px;
+ font-size: 12px;
+ padding: 2px 6px;
+}
+
+.adv-search-row .adv-op {
+ width: 110px;
+ flex-shrink: 0;
+ height: 28px;
+ font-size: 12px;
+ padding: 2px 6px;
+}
+
+.adv-search-row .adv-val {
+ flex: 1;
+ min-width: 80px;
+ max-width: 220px;
+ height: 28px;
+ font-size: 12px;
+ padding: 2px 6px;
+}
+
+.adv-search-row .adv-remove-row {
+ flex-shrink: 0;
+ padding: 2px 7px;
+ height: 28px;
+ line-height: 22px;
+}
+
+.adv-search-footer {
+ margin-top: 6px;
+ padding-bottom: 4px;
+}
+
+.adv-search-footer .btn {
+ margin-right: 6px;
+}
+
+#advanced-search-toggle.adv-open {
+ background: #f0e8ff;
+ border-color: #79589f;
+ color: #79589f;
+}
+
+#adv_search_active_badge {
+ font-size: 11px;
+ margin-right: 6px;
+ vertical-align: middle;
+ background-color: #79589f;
+}
+
+/* Per-row AND/OR connector pill */
+.adv-row-conj {
+ display: flex;
+ align-items: center;
+ flex-shrink: 0;
+ width: 72px;
+ gap: 2px;
+}
+
+.adv-row-conj-first {
+ justify-content: flex-end;
+}
+
+.adv-row-conj-first span {
+ font-size: 11px;
+ color: #999;
+ font-weight: bold;
+ padding-right: 4px;
+}
+
+.adv-conj-btn {
+ font-size: 10px;
+ padding: 1px 5px;
+ border: 1px solid #ccc;
+ background: #fff;
+ color: #777;
+ border-radius: 3px;
+ cursor: pointer;
+ line-height: 16px;
+}
+
+.adv-conj-btn.active {
+ background: #79589f;
+ border-color: #79589f;
+ color: #fff;
+}
+
+/* Range input pair */
+.adv-val-range {
+ display: flex;
+ align-items: center;
+ flex: 1;
+ min-width: 0;
+ gap: 4px;
+}
+
+.adv-val-range .adv-val-from,
+.adv-val-range .adv-val-to {
+ flex: 1;
+ min-width: 60px;
+ height: 28px;
+ font-size: 12px;
+ padding: 2px 6px;
+}
+
+.adv-range-sep {
+ flex-shrink: 0;
+ font-size: 11px;
+ color: #999;
+}
+
+/* Query preview box */
+#adv_query_display {
+ margin-top: 6px;
+ padding: 6px 8px;
+ background: #f5f5f5;
+ border: 1px solid #e0e0e0;
+ border-radius: 3px;
+}
+
+#adv_query_display pre {
+ margin: 0 0 6px 0;
+ font-size: 11px;
+ white-space: pre-wrap;
+ word-break: break-all;
+ color: #333;
+ max-height: 80px;
+ overflow-y: auto;
+}
diff --git a/static/index.html b/static/index.html
index 71ccaf7c0..6afce809c 100644
--- a/static/index.html
+++ b/static/index.html
@@ -133,13 +133,33 @@
Apply
+ Advanced
+
+ Advanced Filter Active
rows
diff --git a/static/js/app.js b/static/js/app.js
index d5b26d4ef..3336eb38c 100644
--- a/static/js/app.js
+++ b/static/js/app.js
@@ -8,20 +8,61 @@ var currentObject = null;
var autocompleteObjects = [];
var inputResizing = false;
var inputResizeOffset = null;
+var advancedSearchActive = false;
var filterOptions = {
- "equal": "= 'DATA'",
- "not_equal": "!= 'DATA'",
- "greater": "> 'DATA'" ,
- "greater_eq": ">= 'DATA'",
- "less": "< 'DATA'",
- "less_eq": "<= 'DATA'",
- "like": "LIKE 'DATA'",
- "ilike": "ILIKE 'DATA'",
- "null": "IS NULL",
- "not_null": "IS NOT NULL"
+ // Standard comparison (single value) — also used by simple filter
+ "equal": "= 'DATA'",
+ "not_equal": "!= 'DATA'",
+ "greater": "> 'DATA'",
+ "greater_eq": ">= 'DATA'",
+ "less": "< 'DATA'",
+ "less_eq": "<= 'DATA'",
+ "like": "LIKE 'DATA'",
+ "ilike": "ILIKE 'DATA'",
+
+ // NULL checks (no value)
+ "null": "IS NULL",
+ "not_null": "IS NOT NULL",
+
+ // List operators — comma-separated values → IN (...)
+ "in": "IN (DATA)",
+ "not_in": "NOT IN (DATA)",
+
+ // Range operators — two values
+ "between": "BETWEEN 'DATA1' AND 'DATA2'",
+ "not_between": "NOT BETWEEN 'DATA1' AND 'DATA2'",
+
+ // Pattern operators
+ "contains": "LIKE '%DATA%'",
+ "not_contains": "NOT LIKE '%DATA%'",
+ "icontains": "ILIKE '%DATA%'",
+ "not_icontains": "NOT ILIKE '%DATA%'",
+ "starts_with": "LIKE 'DATA%'",
+ "ends_with": "LIKE '%DATA'",
+ "istarts_with": "ILIKE 'DATA%'",
+ "iends_with": "ILIKE '%DATA'",
+
+ // Regex operators
+ "regex": "~ 'DATA'",
+ "iregex": "~* 'DATA'"
};
+// Returns the input type required for an operator: "none" | "single" | "list" | "range"
+function getOpInputType(op) {
+ if (!op || !filterOptions[op]) return "single";
+ if (op === "null" || op === "not_null") return "none";
+ if (op === "in" || op === "not_in") return "list";
+ if (op === "between" || op === "not_between") return "range";
+ return "single";
+}
+
+// Escape a filter value for inline SQL string literals.
+// Doubles single-quotes to prevent basic SQL injection via filter values.
+function escapeSqlLiteral(val) {
+ return String(val).replace(/'/g, "''");
+}
+
function getSessionId() {
var id = sessionStorage.getItem("session_id");
@@ -623,25 +664,32 @@ function showTableContent(sortColumn, sortOrder) {
sort_order: sortOrder
};
- var filter = {
- column: $(".filters select.column").val(),
- op: $(".filters select.filter").val(),
- input: $(".filters input").val()
- };
+ // Advanced search takes precedence over the simple filter
+ if (advancedSearchActive) {
+ var advWhere = $("#advanced_search_panel").data("where");
+ if (advWhere) opts["where"] = advWhere;
+ } else {
+ var filter = {
+ column: $(".filters select.column").val(),
+ op: $(".filters select.filter").val(),
+ input: $(".filters input").val()
+ };
- // Apply filtering only if column is selected
- if (filter.column && filter.op) {
- var where = [
- '"' + filter.column + '"',
- filterOptions[filter.op].replace("DATA", filter.input)
- ].join(" ");
+ // Apply filtering only if column is selected
+ if (filter.column && filter.op) {
+ var where = [
+ '"' + filter.column + '"',
+ filterOptions[filter.op].replace("DATA", escapeSqlLiteral(filter.input))
+ ].join(" ");
- opts["where"] = where;
+ opts["where"] = where;
+ }
}
getTableRows(name, opts, function(data) {
$("#input").hide();
$("#body").prop("class", "with-pagination");
+ adjustOutputTop();
buildTable(data, sortColumn, sortOrder);
setCurrentTab("table_content");
@@ -1023,6 +1071,218 @@ function buildTableFilters(name, type) {
var el = $(" ").attr("value", row[0]).text(row[0]);
$("#pagination select.column").append(el);
}
+
+ // Sync columns into any existing advanced search rows
+ var colHtml = $("#pagination select.column").html();
+ $("#adv_search_rows .adv-col").each(function() {
+ var prev = $(this).val();
+ $(this).html(colHtml);
+ $(this).val(prev);
+ });
+
+ // Ensure at least one empty row exists in the panel
+ if ($("#adv_search_rows .adv-search-row").length === 0) {
+ $("#adv_search_rows").append(buildAdvancedSearchRow(true));
+ bindAdvancedOpHandlers();
+ }
+ });
+}
+
+// Build a new advanced search condition row element.
+// isFirst=true omits the AND/OR connector pill and shows a "WHERE" label instead.
+function buildAdvancedSearchRow(isFirst) {
+ var colHtml = $("#pagination select.column").html() || "Select column ";
+
+ var opHtml = [
+ 'Select filter ',
+ '',
+ '= ',
+ '<> ',
+ '< ',
+ '> ',
+ '<= ',
+ '>= ',
+ ' ',
+ '',
+ 'IN ',
+ 'NOT IN ',
+ ' ',
+ '',
+ 'IS NULL ',
+ 'IS NOT NULL ',
+ ' ',
+ '',
+ 'BETWEEN ',
+ 'NOT BETWEEN ',
+ ' ',
+ '',
+ 'Contains ',
+ 'Not contains ',
+ 'Contains (case insensitive) ',
+ 'Not contains (case insensitive) ',
+ 'Has prefix ',
+ 'Has suffix ',
+ 'Has prefix (case insensitive) ',
+ 'Has suffix (case insensitive) ',
+ 'Matches regex ',
+ 'Matches regex (case insensitive) ',
+ ' '
+ ].join("");
+
+ var row = $('
');
+
+ if (isFirst) {
+ row.append('WHERE
');
+ } else {
+ row.append(
+ '' +
+ 'AND ' +
+ 'OR ' +
+ '
'
+ );
+ }
+
+ row.append('' + colHtml + ' ');
+ row.append('' + opHtml + ' ');
+ row.append(' ');
+ row.append(' ');
+ row.append(
+ '' +
+ ' ' +
+ 'and ' +
+ ' ' +
+ ' '
+ );
+ if (!isFirst) {
+ row.append(' ');
+ }
+
+ return row;
+}
+
+// Build a combined SQL WHERE clause from all advanced search condition rows.
+function buildAdvancedWhereClause() {
+ var parts = []; // array of {expr, conj}
+
+ $("#adv_search_rows .adv-search-row").each(function() {
+ var col = $(this).find(".adv-col").val();
+ var op = $(this).find(".adv-op").val();
+ var rowConj = $(this).data("row-conj") || "AND";
+ var inputType = getOpInputType(op);
+
+ if (!col || !op) return;
+
+ var template = filterOptions[op];
+ if (!template) return;
+
+ var fragment;
+
+ if (inputType === "none") {
+ fragment = '"' + col + '" ' + template;
+
+ } else if (inputType === "list") {
+ var raw = $.trim($(this).find(".adv-val-list").val());
+ if (!raw) return;
+ var items = raw.split(",").map(function(s) {
+ return "'" + escapeSqlLiteral($.trim(s)) + "'";
+ });
+ fragment = '"' + col + '" ' + template.replace("DATA", items.join(", "));
+
+ } else if (inputType === "range") {
+ var from = $.trim($(this).find(".adv-val-from").val());
+ var to = $.trim($(this).find(".adv-val-to").val());
+ if (!from || !to) return;
+ fragment = '"' + col + '" ' + template
+ .replace("DATA1", escapeSqlLiteral(from))
+ .replace("DATA2", escapeSqlLiteral(to));
+
+ } else {
+ var val = $.trim($(this).find(".adv-val").val());
+ if (!val) return;
+ fragment = '"' + col + '" ' + template.replace("DATA", escapeSqlLiteral(val));
+ }
+
+ parts.push({ expr: "(" + fragment + ")", conj: rowConj });
+ });
+
+ if (parts.length === 0) return null;
+
+ var sql = parts[0].expr;
+ for (var i = 1; i < parts.length; i++) {
+ sql += " " + parts[i].conj + " " + parts[i].expr;
+ }
+ return sql;
+}
+
+// Apply the advanced search conditions and reload table rows.
+function applyAdvancedSearch() {
+ var where = buildAdvancedWhereClause();
+
+ if (where === null) {
+ alert("Please complete at least one filter condition (column and operator are required).");
+ return;
+ }
+
+ $("#advanced_search_panel").data("where", where);
+ advancedSearchActive = true;
+
+ $("#adv_search_active_badge").show();
+ $("#advanced-search-toggle").addClass("adv-open");
+
+ $(".current-page").data("page", 1);
+ showTableContent();
+}
+
+// Clear the advanced search state and reset the panel to one empty row.
+function resetAdvancedSearch() {
+ advancedSearchActive = false;
+ $("#advanced_search_panel").data("where", null);
+ $("#adv_search_active_badge").hide();
+ $("#advanced-search-toggle").removeClass("adv-open");
+
+ $("#adv_search_rows").empty();
+ $("#adv_search_rows").append(buildAdvancedSearchRow(true));
+}
+
+// Build the full SELECT query string for the current advanced search conditions.
+function buildFullQuery() {
+ var where = buildAdvancedWhereClause();
+ if (!where) return null;
+
+ var table = getCurrentObject().name;
+ var nameParts = table.split(".");
+ var sql;
+ if (nameParts.length === 2) {
+ sql = 'SELECT * FROM "' + nameParts[0] + '"."' + nameParts[1] + '"';
+ } else {
+ sql = 'SELECT * FROM "' + table + '"';
+ }
+ return sql + " WHERE " + where + ";";
+}
+
+// Recalculate #output top offset to account for the advanced panel height.
+function adjustOutputTop() {
+ if ($("#pagination").is(":visible")) {
+ var h = $("#pagination").outerHeight(true);
+ $("#output").css("top", h + "px");
+ }
+}
+
+// Bind operator change handlers for advanced rows (delegated, called once).
+function bindAdvancedOpHandlers() {
+ if ($("#adv_search_rows").data("op-handler-bound")) return;
+ $("#adv_search_rows").data("op-handler-bound", true);
+
+ $("#adv_search_rows").on("change", ".adv-op", function() {
+ var row = $(this).closest(".adv-search-row");
+ var inputType = getOpInputType($(this).val());
+ row.find(".adv-val").toggle(inputType === "single");
+ row.find(".adv-val-list").toggle(inputType === "list");
+ row.find(".adv-val-range").toggle(inputType === "range");
+ if (inputType === "none") {
+ row.find(".adv-val, .adv-val-list").val("");
+ row.find(".adv-val-from, .adv-val-to").val("");
+ }
});
}
@@ -1270,10 +1530,19 @@ function bindTableHeaderMenu() {
var colValue = $(context).text();
var colName = $("#results_header th").eq(colIdx).data("name");
- $("select.column").val(colName);
- $("select.filter").val("equal");
- $("#table_filter_value").val(colValue);
- $("#rows_filter").submit();
+ if ($("#advanced_search_panel").is(":visible")) {
+ var newRow = buildAdvancedSearchRow();
+ newRow.find(".adv-col").val(colName);
+ newRow.find(".adv-op").val("equal");
+ newRow.find(".adv-val").val(colValue);
+ $("#adv_search_rows").append(newRow);
+ } else {
+ $("select.column").val(colName);
+ $("select.filter").val("equal");
+ $("#table_filter_value").val(colValue);
+ $("#rows_filter").submit();
+ }
+ break;
}
}
});
@@ -1594,6 +1863,7 @@ $(document).ready(function() {
$(this).addClass("active");
$(".current-page").data("page", 1);
$(".filters select, .filters input").val("");
+ resetAdvancedSearch();
if (currentObject.type == "function") {
sessionStorage.setItem("tab", "table_structure");
@@ -1681,9 +1951,96 @@ $(document).ready(function() {
$("button.reset-filters").on("click", function() {
$(".filters select, .filters input").val("");
+ resetAdvancedSearch();
+ showTableContent();
+ });
+
+ // ---- Advanced Search ------------------------------------------------
+
+ // Toggle the advanced search panel open/closed
+ $("#advanced-search-toggle").on("click", function() {
+ var panel = $("#advanced_search_panel");
+ if (panel.is(":visible")) {
+ panel.slideUp(150, function() {
+ $("#pagination").removeClass("adv-search-open");
+ adjustOutputTop();
+ });
+ $(this).find("i").removeClass("fa-caret-up").addClass("fa-caret-down");
+ } else {
+ if ($("#adv_search_rows .adv-search-row").length === 0) {
+ $("#adv_search_rows").append(buildAdvancedSearchRow(true));
+ bindAdvancedOpHandlers();
+ }
+ panel.slideDown(150, function() {
+ $("#pagination").addClass("adv-search-open");
+ adjustOutputTop();
+ });
+ $(this).find("i").removeClass("fa-caret-down").addClass("fa-caret-up");
+ }
+ });
+
+ // Add a new condition row
+ $("#adv-add-condition").on("click", function() {
+ var newRow = buildAdvancedSearchRow(false);
+ $("#adv_search_rows").append(newRow);
+ newRow.find(".adv-col").focus();
+ adjustOutputTop();
+ });
+
+ // Remove a condition row (clear instead of remove if it's the last row)
+ $("#adv_search_rows").on("click", ".adv-remove-row", function() {
+ var rows = $("#adv_search_rows .adv-search-row");
+ if (rows.length <= 1) {
+ $(this).closest(".adv-search-row").find("select").val("");
+ $(this).closest(".adv-search-row").find("input").val("").show();
+ } else {
+ $(this).closest(".adv-search-row").remove();
+ }
+ adjustOutputTop();
+ });
+
+ // Toggle per-row AND/OR connector
+ $("#adv_search_rows").on("click", ".adv-conj-btn", function() {
+ $(this).closest(".adv-row-conj").find(".adv-conj-btn").removeClass("active");
+ $(this).addClass("active");
+ $(this).closest(".adv-search-row").data("row-conj", $(this).data("conj"));
+ });
+
+ // Apply advanced search
+ $("#adv-apply").on("click", function() {
+ applyAdvancedSearch();
+ });
+
+ // Clear advanced search (keep panel open)
+ $("#adv-reset").on("click", function() {
+ resetAdvancedSearch();
+ $("#adv_query_display").hide();
showTableContent();
});
+ // Show/hide the raw SQL query preview
+ $("#adv-show-query").on("click", function() {
+ var display = $("#adv_query_display");
+ if (display.is(":visible")) {
+ display.slideUp(100, adjustOutputTop);
+ $(this).find("i").removeClass("fa-chevron-up").addClass("fa-code");
+ } else {
+ var sql = buildFullQuery();
+ if (!sql) {
+ alert("No valid conditions to preview. Fill in at least one complete condition.");
+ return;
+ }
+ $("#adv_query_text").text(sql);
+ display.slideDown(100, adjustOutputTop);
+ $(this).find("i").removeClass("fa-code").addClass("fa-chevron-up");
+ }
+ });
+
+ // Copy raw query to clipboard
+ $("#adv-copy-query").on("click", function() {
+ copyToClipboard($("#adv_query_text").text());
+ });
+
// Automatically prefill the filter if it's not set yet
$("select.column").on("change", function() {
if ($("select.filter").val() == "") {