Skip to content
This repository was archived by the owner on Jun 7, 2023. It is now read-only.

Commit 26c67a8

Browse files
committed
✨ micro Parsons: Allow executing hidden prefix and sufix for SQL
1 parent 7ad7e0a commit 26c67a8

File tree

4 files changed

+136
-63
lines changed

4 files changed

+136
-63
lines changed

runestone/hparsons/hparsons.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,14 @@ class HParsonsDirective(RunestoneIdDirective):
102102
Here is the problem description. It must ends with the tildes.
103103
Make sure you use the correct delimitier for each section below.
104104
~~~~
105+
--hiddenprefix--
106+
// code that is for scaffolding the execution (e.g. initializing database)
105107
--blocks--
106108
block 1
107109
block 2
108110
block 3
111+
--hiddensuffix--
112+
// code that is for scaffolding unittest/execution (e.g. adding query for database)
109113
--unittest--
110114
assert 1,1 == world
111115
assert 0,1 == hello

runestone/hparsons/js/SQLFeedback.js

Lines changed: 85 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,21 @@ export default class SQLFeedback extends HParsonsFeedback {
4545
// fnprefix sets the path to load the sql-wasm.wasm file
4646
var bookprefix;
4747
var fnprefix;
48-
if (eBookConfig.useRunestoneServices) {
48+
if (
49+
eBookConfig.useRunestoneServices ||
50+
window.location.search.includes("mode=browsing")
51+
) {
4952
bookprefix = `${eBookConfig.app}/books/published/${eBookConfig.basecourse}`;
5053
fnprefix = bookprefix + "/_static";
5154
} else {
55+
// The else clause handles the case where you are building for a static web browser
5256
bookprefix = "";
5357
fnprefix = "/_static";
5458
}
5559
let SQLconfig = {
5660
locateFile: (filename) => `${fnprefix}/${filename}`,
5761
};
62+
// this.showLast = $(this.origElem).data("showlastsql");
5863
var self = this.hparsons;
5964
initSqlJs(SQLconfig).then(function (SQL) {
6065
// set up call to load database asynchronously if given
@@ -134,64 +139,18 @@ export default class SQLFeedback extends HParsonsFeedback {
134139
let query = await this.buildProg();
135140
if (!this.hparsons.db) {
136141
$(this.output).text(
137-
`Error: Database not initialized! DBURL: ${this.dburl}`
142+
`Error: Database not initialized! DBURL: ${this.hparsons.dburl}`
138143
);
139144
return;
140145
}
141146

142-
let it = this.hparsons.db.iterateStatements(query);
143-
this.results = [];
144-
try {
145-
for (let statement of it) {
146-
let columns = statement.getColumnNames();
147-
if (columns.length > 0) {
148-
// data! probably a SELECT
149-
let data = [];
150-
while (statement.step()) {
151-
data.push(statement.get());
152-
}
153-
this.results.push({
154-
status: "success",
155-
columns: columns,
156-
values: data,
157-
rowcount: data.length,
158-
});
159-
} else {
160-
let nsql = statement.getNormalizedSQL();
161-
let prefix = nsql.substr(0, 6).toLowerCase();
162-
statement.step(); // execute the query
163-
// Try to detect INSERT/UPDATE/DELETE to give friendly feedback
164-
// on rows modified - unfortunately, this won't catch such queries
165-
// if they use CTEs. There seems to be no reliable way of knowing
166-
// when a SQLite query actually modified data.
167-
if (
168-
prefix === "insert" ||
169-
prefix === "update" ||
170-
prefix === "delete"
171-
) {
172-
this.results.push({
173-
status: "success",
174-
operation: prefix,
175-
rowcount: this.db.getRowsModified(),
176-
});
177-
} else {
178-
this.results.push({ status: "success" });
179-
}
180-
}
181-
}
182-
} catch (e) {
183-
this.results.push({
184-
status: "failure",
185-
message: e.toString(),
186-
sql: it.getRemainingSQL(),
187-
});
147+
// TODO: cancel the execution if previous steps have errors
148+
if (query.prefix) {
149+
this.prefixresults = this.executeIteratedStatements(this.hparsons.db.iterateStatements(query.prefix));
188150
}
189-
190-
if (this.results.length === 0) {
191-
this.results.push({
192-
status: "failure",
193-
message: "No queries submitted.",
194-
});
151+
this.results = this.executeIteratedStatements(this.hparsons.db.iterateStatements(query.input));
152+
if (query.suffix) {
153+
this.suffixresults = this.executeIteratedStatements(this.hparsons.db.iterateStatements(query.suffix));
195154
}
196155

197156
respDiv = document.createElement("div");
@@ -248,25 +207,88 @@ export default class SQLFeedback extends HParsonsFeedback {
248207

249208
// Now handle autograding
250209
if (this.hparsons.unittest) {
251-
this.testResult = this.autograde(
252-
this.results[this.results.length - 1]
253-
);
210+
if (this.suffixresults) {
211+
this.testResult = this.autograde(
212+
this.suffixresults[this.suffixresults.length - 1]
213+
);
214+
} else {
215+
this.testResult = this.autograde(
216+
this.results[this.results.length - 1]
217+
);
218+
}
254219
} else {
255220
$(this.output).css("visibility", "hidden");
256221
}
257222

258223
return Promise.resolve("done");
259224
}
260225

226+
executeIteratedStatements(it) {
227+
let results = [];
228+
try {
229+
for (let statement of it) {
230+
let columns = statement.getColumnNames();
231+
if (columns.length > 0) {
232+
// data! probably a SELECT
233+
let data = [];
234+
while (statement.step()) {
235+
data.push(statement.get());
236+
}
237+
results.push({
238+
status: "success",
239+
columns: columns,
240+
values: data,
241+
rowcount: data.length,
242+
});
243+
} else {
244+
let nsql = statement.getNormalizedSQL();
245+
let prefix = nsql.substr(0, 6).toLowerCase();
246+
statement.step(); // execute the query
247+
// Try to detect INSERT/UPDATE/DELETE to give friendly feedback
248+
// on rows modified - unfortunately, this won't catch such queries
249+
// if they use CTEs. There seems to be no reliable way of knowing
250+
// when a SQLite query actually modified data.
251+
if (
252+
prefix === "insert" ||
253+
prefix === "update" ||
254+
prefix === "delete"
255+
) {
256+
results.push({
257+
status: "success",
258+
operation: prefix,
259+
rowcount: this.hparsons.db.getRowsModified(),
260+
});
261+
} else {
262+
results.push({ status: "success" });
263+
}
264+
}
265+
}
266+
} catch (e) {
267+
results.push({
268+
status: "failure",
269+
message: e.toString(),
270+
sql: it.getRemainingSQL(),
271+
});
272+
}
273+
if (results.length === 0) {
274+
results.push({
275+
status: "failure",
276+
message: "No queries submitted.",
277+
});
278+
}
279+
return results;
280+
}
281+
261282
// adapted from activecode
262283
async buildProg() {
263284
// assemble code from prefix, suffix, and editor for running.
264-
// TODO: fix or remove text entry
265-
var prog;
266-
if (this.hparsons.textentry) {
267-
prog = this.hparsons.hparsonsInput.getCurrentInput();
268-
} else {
269-
prog = this.hparsons.hparsonsInput.getParsonsTextArray().join(' ') + "\n";
285+
let prog = {};
286+
if (this.hparsons.hiddenPrefix) {
287+
prog.prefix = this.hparsons.hiddenPrefix;
288+
}
289+
prog.input = this.hparsons.hparsonsInput.getParsonsTextArray().join(' ') + "\n";
290+
if (this.hparsons.hiddenSuffix) {
291+
prog.suffix = this.hparsons.hiddenSuffix;
270292
}
271293
return Promise.resolve(prog);
272294
}

runestone/hparsons/js/hparsons.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,16 @@ export default class HParsons extends RunestoneBase {
6464

6565
processContent(code) {
6666
// todo: add errors when blocks are nonexistent (maybe in python)?
67+
this.hiddenPrefix = this.processSingleContent(code, '--hiddenprefix--');
6768
this.originalBlocks = this.processSingleContent(code, '--blocks--').split('\n').slice(1,-1);
69+
this.hiddenSuffix = this.processSingleContent(code, '--hiddensuffix--');
6870
this.unittest = this.processSingleContent(code, '--unittest--');
71+
console.log({
72+
'pre': this.hiddenPrefix,
73+
'blocks': this.originalBlocks,
74+
'suf': this.hiddenSuffix,
75+
'test': this.unittest
76+
})
6977
}
7078

7179
processSingleContent(code, delimitier) {

runestone/hparsons/test/_sources/index.rst

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,42 @@ Reusable Block with Execution Based Feedback
101101
assert 1,1 == world
102102
assert 0,1 == hello
103103
assert 2,1 == 42
104+
105+
106+
Randomized Block with Execution Based Feedback and Hidden Code
107+
---------------------------------------------------------------
108+
.. hparsons:: hparsons_lg_sql_practice_A_2_pb
109+
:language: sql
110+
:randomize:
111+
112+
In the ``grades`` table:
113+
114+
.. image:: https://i.ibb.co/r6qShy5/practice-grade.png
115+
116+
A student completed an extra assignment and got some additional points.
117+
118+
Please write an UPDATE statement to change the entry whose ``student_id`` is 1, and set their math score for ``final`` ``test_name`` to 90.
119+
~~~~
120+
--hiddenprefix--
121+
DROP TABLE IF EXISTS grades;
122+
create table "grades" ("student_id" INTEGER, "test_name" TEXT, "english" INTEGER, "math" INTEGER);
123+
INSERT INTO grades (student_id,test_name,english,math) VALUES
124+
('1', 'midterm', 62, 84),
125+
('1', 'final', 70, 86),
126+
('2', 'midterm', 50, 95),
127+
('2', 'final', 80, 99),
128+
('3', 'midterm', 55, 91);
129+
--blocks--
130+
UPDATE grades
131+
SET
132+
math = 90
133+
WHERE
134+
student_id = 1 AND test_name = "final"
135+
LET
136+
student_id = 1 AND test_name = final
137+
--hiddensuffix--
138+
;SELECT * FROM grades
139+
--unittest--
140+
assert 1,1 == final
141+
assert 1,3 == 90
142+
assert 3,3 == 99

0 commit comments

Comments
 (0)