Skip to content
This repository was archived by the owner on Mar 4, 2025. It is now read-only.

Commit dc6a4aa

Browse files
committed
api, common, cypress, webui: Execute SQL page can now do SELECT queries
The server logs show people are commonly trying to run SELECT queries from the Execute SQL page. This commit allows that to work, though there's no visualisation of the resulting data. We might want to merge the Visualise and Execute SQL pages at some point, as we can clearly do both from one page. And it'd reduce the backend code a little bit too. eg no need to save both exec and vis queries seperately, etc
1 parent 53c93d8 commit dc6a4aa

File tree

7 files changed

+127
-143
lines changed

7 files changed

+127
-143
lines changed

api/handlers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -931,7 +931,7 @@ func queryHandler(w http.ResponseWriter, r *http.Request) {
931931
}
932932
} else {
933933
// Send the query to the appropriate backend live node
934-
data, err = com.LiveQueryDB(com.AmqpChan, liveNode, loggedInUser, dbOwner, dbName, query)
934+
data, err = com.LiveQuery(liveNode, loggedInUser, dbOwner, dbName, query)
935935
if err != nil {
936936
log.Println(err)
937937
jsonErr(w, err.Error(), http.StatusInternalServerError)

common/live.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,11 +226,11 @@ func LiveExecute(liveNode, loggedInUser, dbOwner, dbName, sql string) (rowsChang
226226
return
227227
}
228228

229-
// LiveQueryDB sends a SQLite query to a live database on its hosting node
230-
func LiveQueryDB(channel *amqp.Channel, nodeName, requestingUser, dbOwner, dbName, query string) (rows SQLiteRecordSet, err error) {
229+
// LiveQuery sends a SQLite query to a live database on its hosting node
230+
func LiveQuery(nodeName, requestingUser, dbOwner, dbName, query string) (rows SQLiteRecordSet, err error) {
231231
// Send the query request to our AMQP backend
232232
var rawResponse []byte
233-
rawResponse, err = MQRequest(channel, nodeName, "query", requestingUser, dbOwner, dbName, query)
233+
rawResponse, err = MQRequest(AmqpChan, nodeName, "query", requestingUser, dbOwner, dbName, query)
234234
if err != nil {
235235
return
236236
}

cypress/e2e/1-webui/bugs/002-visualisation-base64url-padding-bug.cy.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ describe('002-visualisation-base64url-padding-bug', () => {
1616
'LIMIT 10')
1717
cy.get('[data-cy="runsqlbtn"]').click()
1818
cy.wait(150) // Needs a bit of a delay here, otherwise any error status message may be missed
19-
cy.get('[data-cy="statusmsg"]').should('contain.text', 'SQL run successful')
19+
cy.get('[data-cy="statusmsg"]').should('contain.text', 'SQL query ran successfully')
2020
})
2121
})

webui/execute.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"log"
77
"net/http"
8+
"strings"
89

910
"github.com/gorilla/sessions"
1011
com "github.com/sqlitebrowser/dbhub.io/common"
@@ -283,16 +284,29 @@ func execLiveSQL(w http.ResponseWriter, r *http.Request) {
283284

284285
// Send the SQL execution request to our AMQP backend
285286
var rowsChanged int
287+
var z interface{}
286288
rowsChanged, err = com.LiveExecute(liveNode, loggedInUser, dbOwner, dbName, sql)
287289
if err != nil {
288-
log.Println(err)
289-
w.WriteHeader(http.StatusInternalServerError)
290-
fmt.Fprint(w, err)
291-
return
290+
if !strings.HasPrefix(err.Error(), "don't use exec with") {
291+
log.Println(err)
292+
w.WriteHeader(http.StatusInternalServerError)
293+
fmt.Fprint(w, err)
294+
return
295+
}
296+
297+
// The user tried to run a SELECT query. Let's just run with it...
298+
z, err = com.LiveQuery(liveNode, loggedInUser, dbOwner, dbName, sql)
299+
if err != nil {
300+
w.WriteHeader(http.StatusInternalServerError)
301+
fmt.Fprint(w, err.Error())
302+
return
303+
}
304+
} else {
305+
// The SQL statement execution succeeded, so pass along the # of rows changed
306+
z = com.ExecuteResponseContainer{RowsChanged: rowsChanged, Status: "OK"}
292307
}
293308

294-
// The SQL statement execution succeeded, so pass along the # of rows changed
295-
z := com.ExecuteResponseContainer{RowsChanged: rowsChanged, Status: "OK"}
309+
// Return the success message
296310
jsonData, err := json.Marshal(z)
297311
if err != nil {
298312
log.Println(err)

webui/templates/execute.html

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,23 @@
3737
<uib-tab index="0" data-cy="sqltab">
3838
<uib-tab-heading><span style="color: #555;">SQL</span></uib-tab-heading>
3939
<textarea id="usersql" rows="8" onchange="updateSQL()" ng-attr-placeholder="Your SQL statement here..." data-cy="usersqltext">[[ .SQL ]]</textarea>
40+
41+
<button type="button" class="btn btn-default" ng-if="db.Records.length > 0" ng-click="toggleResultTable()" data-cy="resultsbtn">{{ toggleLabel }}</button>
42+
<div uib-collapse="isCollapsed">
43+
<div style="max-width: 100%; overflow: auto; border: 1px solid #DDD; border-radius: 7px 7px 0 0;">
44+
<table class="table table-bordered table-striped table-responsive" style="margin-bottom: 0; padding-bottom: 0;" data-cy="resultstbl">
45+
<thead>
46+
<tr>
47+
<th ng-repeat="col in db.ColNames" style="padding: 7px 0 6px 6px;">{{ col }}</th>
48+
</tr>
49+
</thead>
50+
<tbody>
51+
<tr ng-repeat="row in db.Records">
52+
<td ng-repeat="val in row" dir="auto"><pre style="background-color: transparent; border: none; padding: 0px; margin: 0px;" ng-bind-html="val.Value | fixSpaces"></pre></td>
53+
</tr>
54+
</table>
55+
</div>
56+
</div>
4057
</uib-tab>
4158
</uib-tabset>
4259
</div>
@@ -115,6 +132,24 @@ <h4 style="color: {{ statusMessageColour }};" data-cy="statusmsg">&nbsp;{{ statu
115132
// Initialise things with safe defaults (these are potentially filled out further down the page)
116133
$scope.selectedName = "[[ .SelectedName ]]";
117134
$scope.execNames = [[ .ExecNames ]];
135+
$scope.toggleLabel = "Show result table"
136+
$scope.isCollapsed = true;
137+
138+
// Create empty stub to hold returned query data
139+
$scope.db = {"ColCount":0,"ColNames":[],"Offset":0,"Records":[],"RowCount":0,"SortCol":"","SortDir":"","Tablename":"","TotalRows":0};
140+
141+
// Functions for toggling on/off various display elements
142+
$scope.toggleResultTable = function() {
143+
$scope.isCollapsed = !$scope.isCollapsed;
144+
if ($scope.isCollapsed) {
145+
$scope.toggleLabel = "Show result table"
146+
} else {
147+
$scope.toggleLabel = "Hide result table"
148+
}
149+
150+
// Clear any existing status message
151+
$scope.statusMessage = "";
152+
}
118153

119154
// Ensure the SQL statement name field isn't blank
120155
// Returns true if a name is present, false if it's missing
@@ -178,6 +213,12 @@ <h4 style="color: {{ statusMessageColour }};" data-cy="statusmsg">&nbsp;{{ statu
178213
)
179214
}
180215

216+
// Clear out any existing table data
217+
$scope.clearData = function() {
218+
$scope.db.ColNames = [];
219+
$scope.db.Records = [];
220+
}
221+
181222
// If there are saved SQL statements but none of them is named "default", then change the Saved SQL drop down
182223
// selector to use the first one
183224
if (($scope.execNames.length > 0) && (!$scope.execNames.includes("default"))) {
@@ -218,15 +259,24 @@ <h4 style="color: {{ statusMessageColour }};" data-cy="statusmsg">&nbsp;{{ statu
218259
// Display a success message
219260
$scope.statusMessageColour = "green";
220261
$scope.statusMessage = nowString() + "SQL statement '" + name + "' deleted";
262+
263+
// Clear out any existing table data
264+
$scope.clearData();
221265
}, function failure(response) {
222266
// The deletion failed, so display the returned error message
223267
$scope.statusMessageColour = "red";
224268
$scope.statusMessage = nowString() + "Deleting '" + name + "' failed: " + response.data;
269+
270+
// Clear out any existing table data
271+
$scope.clearData();
225272
}
226273
)
227274
} else {
228275
$scope.statusMessageColour = "red";
229276
$scope.statusMessage = nowString() + "Unknown SQL statement";
277+
278+
// Clear out any existing table data
279+
$scope.clearData();
230280
}
231281
};
232282

@@ -239,18 +289,38 @@ <h4 style="color: {{ statusMessageColour }};" data-cy="statusmsg">&nbsp;{{ statu
239289
let encoded = base64url(userSQL);
240290
$http.get("/x/execlivesql/[[ .DB.Info.Owner ]]/[[ .DB.Info.Database ]]?commit=[[ .DB.Info.CommitID ]]&sql="+encoded).then(
241291
function success(response) {
242-
$scope.statusMessageColour = "green";
243-
if (response.data["rows_changed"] === 0) {
244-
$scope.statusMessage = nowString() + "Execution succeeded";
245-
} else if (response.data["rows_changed"] === 1) {
246-
$scope.statusMessage = nowString() + "Execution success: " + response.data["rows_changed"] + " row changed";
292+
// We can potentially receive either "Execute SQL" or Query responses here
293+
if ("rows_changed" in response.data) {
294+
// Handle the response as for Execute SQL
295+
$scope.statusMessageColour = "green";
296+
if (response.data["rows_changed"] === 0) {
297+
$scope.statusMessage = nowString() + "Execution succeeded";
298+
} else if (response.data["rows_changed"] === 1) {
299+
$scope.statusMessage = nowString() + "Execution success: " + response.data["rows_changed"] + " row changed";
300+
} else {
301+
$scope.statusMessage = nowString() + "Execution success: " + response.data["rows_changed"] + " rows changed";
302+
}
303+
304+
// Clear out any existing table data
305+
$scope.clearData();
247306
} else {
248-
$scope.statusMessage = nowString() + "Execution success: " + response.data["rows_changed"] + " rows changed";
307+
// Give a useful "success" status message
308+
$scope.statusMessageColour = "green";
309+
if (response.data["RowCount"] === 0) {
310+
$scope.statusMessage = nowString() + "SQL query ran without error, but returned no records";
311+
$scope.clearData();
312+
} else {
313+
$scope.statusMessage = nowString() + "SQL query ran successfully";
314+
$scope.db = response.data;
315+
}
249316
}
250317
}, function failure(response) {
251318
// Retrieving data failed, so display the returned error message and hide the graph
252319
$scope.statusMessageColour = "red";
253320
$scope.statusMessage = nowString() + "Executing SQL statement failed: " + response.data;
321+
322+
// Clear out any existing table data
323+
$scope.clearData();
254324
}
255325
)
256326
};

0 commit comments

Comments
 (0)