Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ Change Log
This document records all notable changes to `SQL Explorer <https://github.com/explorerhq/sql-explorer>`_.
This project adheres to `Semantic Versioning <https://semver.org/>`_.

`5.3.1`_ (2026-09-24)
===========================
* `#710`_: **Security fix.** The AI Assistant endpoints (``/assistant/`` and ``/assistant/history/``) did not check
permissions, and table names passed to the assistant were not validated before being used to sample rows. The
endpoints now require ``EXPLORER_PERMISSION_CHANGE``, and only tables present in the connection's schema (respecting
``EXPLORER_SCHEMA_INCLUDE_TABLE_PREFIXES`` / ``EXPLORER_SCHEMA_EXCLUDE_TABLE_PREFIXES``) are sampled. All users of
4.1 and later are encouraged to upgrade.

* `#687`_: UI improvements:

- A download button in the preview pane exports the displayed results as CSV (`#683`_).
- Fixed the assistant controls floating incorrectly while a response is being generated (`#685`_).
- Show/Hide Schema is now a single toggle button.
- Table descriptions are no longer truncated in the table description list.

* `#703`_: The email CSV endpoint no longer requires the ``X-Requested-With`` header, and returns a 400 if no email
address is provided.

`5.3.0`_ (2024-09-24)
===========================
* `#664`_: Improvements to the AI SQL Assistant:
Expand Down Expand Up @@ -589,6 +607,8 @@ Initial Release
.. _5.1.1: https://github.com/explorerhq/sql-explorer/compare/5.1.0...5.1.1
.. _5.2.0: https://github.com/explorerhq/sql-explorer/compare/5.1.1...5.2.0
.. _5.3b1: https://github.com/explorerhq/sql-explorer/compare/5.2.0...5.3b1
.. _5.3.0: https://github.com/explorerhq/sql-explorer/compare/5.3b1...5.3.0
.. _5.3.1: https://github.com/explorerhq/sql-explorer/compare/5.3.0...5.3.1


.. _#254: https://github.com/explorerhq/sql-explorer/pull/254
Expand Down Expand Up @@ -681,6 +701,9 @@ Initial Release
.. _#662: https://github.com/explorerhq/sql-explorer/pull/662
.. _#660: https://github.com/explorerhq/sql-explorer/pull/660
.. _#664: https://github.com/explorerhq/sql-explorer/pull/664
.. _#687: https://github.com/explorerhq/sql-explorer/pull/687
.. _#703: https://github.com/explorerhq/sql-explorer/pull/703
.. _#710: https://github.com/explorerhq/sql-explorer/pull/710

.. _#269: https://github.com/explorerhq/sql-explorer/issues/269
.. _#288: https://github.com/explorerhq/sql-explorer/issues/288
Expand All @@ -698,6 +721,8 @@ Initial Release
.. _#490: https://github.com/explorerhq/sql-explorer/issues/490
.. _#492: https://github.com/explorerhq/sql-explorer/issues/492
.. _#592: https://github.com/explorerhq/sql-explorer/issues/592
.. _#683: https://github.com/explorerhq/sql-explorer/issues/683
.. _#685: https://github.com/explorerhq/sql-explorer/issues/685
.. _#609: https://github.com/explorerhq/sql-explorer/issues/609
.. _#610: https://github.com/explorerhq/sql-explorer/issues/610
.. _#612: https://github.com/explorerhq/sql-explorer/issues/612
Expand Down
2 changes: 1 addition & 1 deletion explorer/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
__version_info__ = {
"major": 5,
"minor": 3,
"patch": 0,
"patch": 1,
"releaselevel": "final",
"serial": 0
}
Expand Down
83 changes: 59 additions & 24 deletions explorer/src/js/explorer.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,40 @@ function selectConnection() {
}
}

// Quote like Python's csv.writer (used by the server-side CSV export): only when needed.
function csvEscape(value) {
if (/[",\r\n]/.test(value)) {
return '"' + value.replace(/"/g, '""') + '"';
}
return value;
}

function downloadCSVFromTable() {
var table = document.getElementById("preview");
// Skip the hidden row-number column, the optional stats row, and the stats tables nested in the headers.
var headers = table.querySelectorAll("thead > tr:first-child > th:not(.counter)");
var rows = [Array.from(headers)].concat(
Array.from(table.querySelectorAll("tbody > tr.data-row")).map(function (row) {
return Array.from(row.querySelectorAll(":scope > td:not(.counter)"));
})
);
var csv = rows.map(function (cells) {
return cells.map(function (cell) { return csvEscape(cell.innerText); }).join(",");
});

// BOM so Excel detects UTF-8, matching the server-side export.
var csvFile = new Blob(["\ufeff" + csv.join("\r\n")], { type: "text/csv;charset=utf-8" });
var url = URL.createObjectURL(csvFile);
var downloadLink = document.createElement("a");
downloadLink.href = url;
downloadLink.download = "preview.csv";

document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
URL.revokeObjectURL(url);
}

export class ExplorerEditor {
constructor(queryId) {

Expand Down Expand Up @@ -100,7 +134,7 @@ export class ExplorerEditor {
this.bind();

if (cookie.get("schema_sidebar_open") === 'true') {
this.showSchema(true);
this.toggleSchema(true, true);
}
}

Expand Down Expand Up @@ -189,28 +223,29 @@ export class ExplorerEditor {
form.submit();
}

showSchema(noAutofocus) {
if (noAutofocus === true) {
$("#schema_frame").addClass("no-autofocus");
}
$("#query_area").removeClass("col").addClass("col-9");
var schema$ = $("#schema");
schema$.addClass("col-md-3");
schema$.show();
$("#show_schema_button").hide();
$("#hide_schema_button").show();
cookie.set("schema_sidebar_open", 'true');
return false;
}
toggleSchema(noAutofocus, doShow) {
var schema = document.getElementById("schema");
var queryArea = document.getElementById("query_area");
var toggleBtn = document.getElementById("toggle_schema_button");

hideSchema() {
$("#query_area").removeClass("col-9").addClass("col");
var schema$ = $("#schema");
schema$.removeClass("col-3");
schema$.hide();
$("#hide_schema_button").hide();
$("#show_schema_button").show();
cookie.set("schema_sidebar_open", 'false');
if (doShow || schema.style.display === "none" || schema.style.display === "") { // show
if (noAutofocus === true) {
schema.classList.add("no-autofocus");
}
queryArea.classList.remove("col");
queryArea.classList.add("col-9");
schema.classList.add("col-md-3");
schema.style.display = "block";
toggleBtn.innerHTML = "Hide Schema";
cookie.set("schema_sidebar_open", 'true');
} else { // hide
queryArea.classList.remove("col-9");
queryArea.classList.add("col");
schema.classList.remove("col-md-3");
schema.style.display = "none";
toggleBtn.innerHTML = "Show Schema";
cookie.set("schema_sidebar_open", 'false');
}
return false;
}

Expand All @@ -237,9 +272,9 @@ export class ExplorerEditor {
element.addEventListener('click', toggleFavorite);
});

document.getElementById('show_schema_button')?.addEventListener('click', this.showSchema.bind(this));
document.getElementById('hide_schema_button')?.addEventListener('click', this.hideSchema.bind(this));
document.getElementById('toggle_schema_button')?.addEventListener('click', this.toggleSchema.bind(this));

document.getElementById('preview-download')?.addEventListener('click', downloadCSVFromTable)

$("#format_button").click(function(e) {
e.preventDefault();
Expand Down
2 changes: 1 addition & 1 deletion explorer/src/scss/assistant.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
.assistant-icons {
width: 1rem;
position: absolute;
right: .75rem;
right: 1rem;
}

#table-list {
Expand Down
2 changes: 1 addition & 1 deletion explorer/templates/assistant/table_description_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ <h3>Table Annotations</h3>
<tr>
<td>{{ table_description.database_connection }}</td>
<td>{{ table_description.table_name }}</td>
<td>{{ table_description.description|truncatewords:20 }}</td>
<td>{{ table_description.description }}</td>
<td>
<a href="{% url 'table_description_update' table_description.pk %}" class="px-2"><i class="bi-pencil-square"></i></a>
<a href="{% url 'table_description_delete' table_description.pk %}"><i class="bi-trash"></i></a>
Expand Down
36 changes: 18 additions & 18 deletions explorer/templates/explorer/assistant.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,24 @@
<div id="additional_table_container" class="col-3" style="width: 31% !important">
<div id="table-list"></div>
</div>
</div>
<div class="assistant-icons" style="">
<div>
<i class="bi-check-all" id="select_all_button" style="cursor: pointer;" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="Add all"></i>
</div>
<div>
<i class="bi-trash" id="deselect_all_button" style="cursor: pointer;" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="Remove all"></i>
</div>
<div>
<i class="bi-repeat" id="refresh_tables_button" style="cursor: pointer;" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="Refresh autodetect"></i>
</div>
<div>
<i class="bi-card-list" id="assistant_history" style="cursor: pointer;" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="History"></i>
</div>
<div>
<i class="bi-question-circle" style="cursor: pointer;"
data-bs-toggle="tooltip" data-bs-placement="top"
data-bs-title="SQL Assistant builds a prompt with your query, your request, and the tables (schema, sample data, and annotations) referenced here."></i>
<div class="assistant-icons" style="">
<div>
<i class="bi-check-all" id="select_all_button" style="cursor: pointer;" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="Add all"></i>
</div>
<div>
<i class="bi-trash" id="deselect_all_button" style="cursor: pointer;" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="Remove all"></i>
</div>
<div>
<i class="bi-repeat" id="refresh_tables_button" style="cursor: pointer;" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="Refresh autodetect"></i>
</div>
<div>
<i class="bi-card-list" id="assistant_history" style="cursor: pointer;" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="History"></i>
</div>
<div>
<i class="bi-question-circle" style="cursor: pointer;"
data-bs-toggle="tooltip" data-bs-placement="top"
data-bs-title="SQL Assistant builds a prompt with your query, your request, and the tables (schema, sample data, and annotations) referenced here."></i>
</div>
</div>
</div>
<div class="row">
Expand Down
6 changes: 1 addition & 5 deletions explorer/templates/explorer/play.html
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,9 @@ <h2>{% translate "Playground" %}</h2>
class="btn btn-outline-primary">{% translate 'Save As New' %}</button>
{% export_buttons query %}

<button type="button" class="btn btn-outline-primary" id="show_schema_button">
<button type="button" class="btn btn-outline-primary" id="toggle_schema_button">
{% translate "Show Schema" %}
</button>
<button type="button" class="btn btn-outline-primary" id="hide_schema_button"
style="display: none;">
{% translate "Hide Schema" %}
</button>
</div>
</div>
<input type="hidden" value="{% translate 'Playground Query' %}" name="title" />
Expand Down
1 change: 1 addition & 0 deletions explorer/templates/explorer/preview_pane.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
title="Fullscreen results">
<i class="bi-arrows-angle-expand"></i>
</a>
<i id="preview-download" class="bi-download ms-1" title="Download table as csv"></i>
</div>
</div>
</div>
Expand Down
30 changes: 20 additions & 10 deletions explorer/templates/explorer/query.html
Original file line number Diff line number Diff line change
Expand Up @@ -111,19 +111,29 @@ <h2>
{% endif %}
<div class="btn-group" role="group">
{% if can_change %}
<button id="save_button" type="submit" class="btn btn-primary">
{% translate "Save & Run" %}
</button>
<button class="btn btn-outline-primary" id="save_only_button">
{% translate "Save Only" %}
</button>
<div class="btn-group" role="group">
<button id="save_button"
type="submit"
class="btn btn-primary">
{% translate 'Save & Run' %}
</button>
<button type="button"
class="btn btn-primary dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li>
<button type="submit" class="dropdown-item" id="save_only_button">
{% translate 'Save Only' %}
</button>
</li>
</ul>
</div>
{% export_buttons query %}
<button type="button" class="btn btn-outline-primary" id="show_schema_button">
<button type="button" class="btn btn-outline-primary" id="toggle_schema_button">
{% translate "Show Schema" %}
</button>
<button type="button" class="btn btn-outline-primary" id="hide_schema_button" style="display: none;">
{% translate "Hide Schema" %}
</button>
{% else %}
<button id="refresh_button" type="button" class="btn btn-outline-primary">{% translate "Refresh" %}</button>
{% export_buttons query %}
Expand Down
Loading