From 91e48a5b46d7cec9d3e0ca580a13e66667381758 Mon Sep 17 00:00:00 2001 From: Ronaldo Ferreira de Lima Date: Sun, 26 Oct 2025 19:06:52 -0300 Subject: [PATCH 01/16] * proof of concept --- .../concept/intro-select/create_fixture.sql | 10 +++ .../intro-select/create_test_table.sql | 23 +++++ exercises/concept/intro-select/data.csv | 6 ++ .../concept/intro-select/intro-select.sql | 14 +++ .../intro-select/intro-select_test.sql | 85 +++++++++++++++++++ exercises/concept/intro-select/outputs.txt | 27 ++++++ 6 files changed, 165 insertions(+) create mode 100644 exercises/concept/intro-select/create_fixture.sql create mode 100644 exercises/concept/intro-select/create_test_table.sql create mode 100644 exercises/concept/intro-select/data.csv create mode 100644 exercises/concept/intro-select/intro-select.sql create mode 100644 exercises/concept/intro-select/intro-select_test.sql create mode 100644 exercises/concept/intro-select/outputs.txt diff --git a/exercises/concept/intro-select/create_fixture.sql b/exercises/concept/intro-select/create_fixture.sql new file mode 100644 index 00000000..37d4b63f --- /dev/null +++ b/exercises/concept/intro-select/create_fixture.sql @@ -0,0 +1,10 @@ +DROP TABLE IF EXISTS weather_readings; +CREATE TABLE weather_readings ( + date TEXT NOT NULL, + location TEXT NOT NULL, + temperature REAL NOT NULL, + humidity INTEGER NOT NULL +); + +.mode csv +.import ./data.csv weather_readings diff --git a/exercises/concept/intro-select/create_test_table.sql b/exercises/concept/intro-select/create_test_table.sql new file mode 100644 index 00000000..7f4bd05f --- /dev/null +++ b/exercises/concept/intro-select/create_test_table.sql @@ -0,0 +1,23 @@ +DROP TABLE IF EXISTS tests; +CREATE TABLE IF NOT EXISTS tests ( + description TEXT NOT NULL, + -- The following section is needed by the online test-runner + status TEXT DEFAULT 'fail', + message TEXT, + output TEXT, + test_code TEXT, + task_id INTEGER DEFAULT NULL, + -- Here are columns for the actual tests + expected TEXT NOT NULL +); + +INSERT INTO tests (description, expected) +VALUES + ('ALL records => SELECT * FROM weather_readings', '[{"date":"2025-10-22","location":"Portland","temperature":53.1,"humidity":72},{"date":"2025-10-22","location":"Seattle","temperature":56.2,"humidity":66},{"date":"2025-10-22","location":"Boise","temperature":60.4,"humidity":55},{"date":"2025-10-23","location":"Portland","temperature":54.6,"humidity":70},{"date":"2025-10-23","location":"Seattle","temperature":57.8,"humidity":68},{"date":"2025-10-23","location":"Boise","temperature":62.0,"humidity":58}]'), + ('Just location and temperature columns => SELECT location, temperature FROM weather_readings', '[{"location":"Portland","temperature":53.1},{"location":"Seattle","temperature":56.2},{"location":"Boise","temperature":60.4},{"location":"Portland","temperature":54.6},{"location":"Seattle","temperature":57.8},{"location":"Boise","temperature":62.0}]'), + ('Without "FROM" => SELECT ''Hello, world.''', '[{"''Hello, world.''":"Hello, world."}]'), + ('All records from Seatle location => SELECT * FROM weather_readings WHERE location = ''Seattle''', '[{"date":"2025-10-22","location":"Seattle","temperature":56.2,"humidity":66},{"date":"2025-10-23","location":"Seattle","temperature":57.8,"humidity":68}]'), + ('All records where humity in range => SELECT * FROM weather_readings WHERE humidity BETWEEN 60 AND 70', '[{"date":"2025-10-22","location":"Seattle","temperature":56.2,"humidity":66},{"date":"2025-10-23","location":"Portland","temperature":54.6,"humidity":70},{"date":"2025-10-23","location":"Seattle","temperature":57.8,"humidity":68}]'), + ('Just location column => SELECT location FROM weather_readings', '[{"location":"Portland"},{"location":"Seattle"},{"location":"Boise"},{"location":"Portland"},{"location":"Seattle"},{"location":"Boise"}]'), + ('Only unique locations => SELECT DISTINCT location FROM weather_readings', '[{"location":"Portland"},{"location":"Seattle"},{"location":"Boise"}]'); +; diff --git a/exercises/concept/intro-select/data.csv b/exercises/concept/intro-select/data.csv new file mode 100644 index 00000000..808ccddf --- /dev/null +++ b/exercises/concept/intro-select/data.csv @@ -0,0 +1,6 @@ +"2025-10-22","Portland",53.1,72 +"2025-10-22","Seattle",56.2,66 +"2025-10-22","Boise",60.4,55 +"2025-10-23","Portland",54.6,70 +"2025-10-23","Seattle",57.8,68 +"2025-10-23","Boise",62.0,58 diff --git a/exercises/concept/intro-select/intro-select.sql b/exercises/concept/intro-select/intro-select.sql new file mode 100644 index 00000000..265301d8 --- /dev/null +++ b/exercises/concept/intro-select/intro-select.sql @@ -0,0 +1,14 @@ +SELECT * FROM weather_readings; + +SELECT location, temperature FROM weather_readings; + +-- This one will fail on purpose +SELECT 'Hello, world.' AS say_hi; + +SELECT * FROM weather_readings WHERE location = 'Seattle'; + +SELECT * FROM weather_readings WHERE humidity BETWEEN 60 AND 70; + +SELECT location FROM weather_readings; + +SELECT DISTINCT location FROM weather_readings; diff --git a/exercises/concept/intro-select/intro-select_test.sql b/exercises/concept/intro-select/intro-select_test.sql new file mode 100644 index 00000000..6010d111 --- /dev/null +++ b/exercises/concept/intro-select/intro-select_test.sql @@ -0,0 +1,85 @@ +-- Create database: +.read ./create_fixture.sql +-- Read user student solution and save any output as markdown in user_output.md: +.mode markdown +.output user_output.md +.echo on +.read ./intro-select.sql +.echo off +.output +.shell sed -i 1d user_output.md + +-- Re-run stub file to collect the results as json arrays +.mode json +.output outputs.txt +.read ./intro-select.sql +.output +-- Creating the results table from the outputs.txt file +DROP TABLE IF EXISTS outputs; +CREATE TEMPORARY TABLE outputs (line TEXT NOT NULL); +.mode tabs +.import ./outputs.txt outputs +DROP TABLE IF EXISTS results; +CREATE TABLE results (result TEXT NOT NULL); +WITH inputs (input) AS ( + SELECT JSON(PRINTF('[%s]', GROUP_CONCAT(RTRIM(TRIM(line), ','), ','))) + FROM outputs +) +INSERT INTO results (result) +SELECT j.value + FROM inputs, JSON_EACH(input) j +; + +-- Create a clean testing environment: +.read ./create_test_table.sql +-- Comparison of user input and the tests updates the status for each test: +UPDATE tests + SET status = 'pass' + FROM (SELECT result FROM results) AS actual + WHERE NOT EXISTS ( + SELECT key, value, type, path + FROM JSON_TREE(result) + WHERE type NOT IN ('array', 'object') + EXCEPT + SELECT key, value, type, path + FROM JSON_TREE(expected) + WHERE type NOT IN ('array', 'object') + ) + AND NOT EXISTS ( + SELECT key, value, type, path + FROM JSON_TREE(expected) + WHERE type NOT IN ('array', 'object') + EXCEPT + SELECT key, value, type, path + FROM JSON_TREE(result) + WHERE type NOT IN ('array', 'object') + ) +; + +-- Update message for failed tests to give helpful information: +UPDATE tests + -- SET message = 'Result for "' || tests.description || '"' || ' is <' || COALESCE(actual.result, 'NULL') || '> but should be <' || tests.expected || '>' + SET message = 'Result for <"' || tests.description || '">' || ' NOT FOUND' -- need improvements +WHERE tests.status = 'fail'; + +-- Save results to ./output.json (needed by the online test-runner) +.mode json +.once './output.json' +SELECT + description, + status, + message, + output, + test_code, + task_id +FROM + tests; + +-- Display test results in readable form for the student: +.mode table +SELECT + description, + status, + message +FROM + tests; diff --git a/exercises/concept/intro-select/outputs.txt b/exercises/concept/intro-select/outputs.txt new file mode 100644 index 00000000..9e837a4a --- /dev/null +++ b/exercises/concept/intro-select/outputs.txt @@ -0,0 +1,27 @@ +[{"date":"2025-10-22","location":"Portland","temperature":53.10000000000000142,"humidity":72}, +{"date":"2025-10-22","location":"Seattle","temperature":56.20000000000000284,"humidity":66}, +{"date":"2025-10-22","location":"Boise","temperature":60.39999999999999858,"humidity":55}, +{"date":"2025-10-23","location":"Portland","temperature":54.60000000000000142,"humidity":70}, +{"date":"2025-10-23","location":"Seattle","temperature":57.79999999999999716,"humidity":68}, +{"date":"2025-10-23","location":"Boise","temperature":62.0,"humidity":58}] +[{"location":"Portland","temperature":53.10000000000000142}, +{"location":"Seattle","temperature":56.20000000000000284}, +{"location":"Boise","temperature":60.39999999999999858}, +{"location":"Portland","temperature":54.60000000000000142}, +{"location":"Seattle","temperature":57.79999999999999716}, +{"location":"Boise","temperature":62.0}] +[{"say_hi":"Hello, world."}] +[{"date":"2025-10-22","location":"Seattle","temperature":56.20000000000000284,"humidity":66}, +{"date":"2025-10-23","location":"Seattle","temperature":57.79999999999999716,"humidity":68}] +[{"date":"2025-10-22","location":"Seattle","temperature":56.20000000000000284,"humidity":66}, +{"date":"2025-10-23","location":"Portland","temperature":54.60000000000000142,"humidity":70}, +{"date":"2025-10-23","location":"Seattle","temperature":57.79999999999999716,"humidity":68}] +[{"location":"Portland"}, +{"location":"Seattle"}, +{"location":"Boise"}, +{"location":"Portland"}, +{"location":"Seattle"}, +{"location":"Boise"}] +[{"location":"Portland"}, +{"location":"Seattle"}, +{"location":"Boise"}] From 1c3d67fb5c41510d72d64bb55fb74df9c68af8ee Mon Sep 17 00:00:00 2001 From: Todd Schwartz Date: Sat, 1 Nov 2025 07:33:34 -0700 Subject: [PATCH 02/16] Generate textual results --- .../intro-select/create_test_table.sql | 23 ----- .../concept/intro-select/intro-select.sql | 28 ++++++ .../intro-select/intro-select_exemplar.sql | 41 +++++++++ .../intro-select/intro-select_test.sql | 87 ++----------------- exercises/concept/intro-select/outputs.txt | 27 ------ 5 files changed, 78 insertions(+), 128 deletions(-) delete mode 100644 exercises/concept/intro-select/create_test_table.sql create mode 100644 exercises/concept/intro-select/intro-select_exemplar.sql delete mode 100644 exercises/concept/intro-select/outputs.txt diff --git a/exercises/concept/intro-select/create_test_table.sql b/exercises/concept/intro-select/create_test_table.sql deleted file mode 100644 index 7f4bd05f..00000000 --- a/exercises/concept/intro-select/create_test_table.sql +++ /dev/null @@ -1,23 +0,0 @@ -DROP TABLE IF EXISTS tests; -CREATE TABLE IF NOT EXISTS tests ( - description TEXT NOT NULL, - -- The following section is needed by the online test-runner - status TEXT DEFAULT 'fail', - message TEXT, - output TEXT, - test_code TEXT, - task_id INTEGER DEFAULT NULL, - -- Here are columns for the actual tests - expected TEXT NOT NULL -); - -INSERT INTO tests (description, expected) -VALUES - ('ALL records => SELECT * FROM weather_readings', '[{"date":"2025-10-22","location":"Portland","temperature":53.1,"humidity":72},{"date":"2025-10-22","location":"Seattle","temperature":56.2,"humidity":66},{"date":"2025-10-22","location":"Boise","temperature":60.4,"humidity":55},{"date":"2025-10-23","location":"Portland","temperature":54.6,"humidity":70},{"date":"2025-10-23","location":"Seattle","temperature":57.8,"humidity":68},{"date":"2025-10-23","location":"Boise","temperature":62.0,"humidity":58}]'), - ('Just location and temperature columns => SELECT location, temperature FROM weather_readings', '[{"location":"Portland","temperature":53.1},{"location":"Seattle","temperature":56.2},{"location":"Boise","temperature":60.4},{"location":"Portland","temperature":54.6},{"location":"Seattle","temperature":57.8},{"location":"Boise","temperature":62.0}]'), - ('Without "FROM" => SELECT ''Hello, world.''', '[{"''Hello, world.''":"Hello, world."}]'), - ('All records from Seatle location => SELECT * FROM weather_readings WHERE location = ''Seattle''', '[{"date":"2025-10-22","location":"Seattle","temperature":56.2,"humidity":66},{"date":"2025-10-23","location":"Seattle","temperature":57.8,"humidity":68}]'), - ('All records where humity in range => SELECT * FROM weather_readings WHERE humidity BETWEEN 60 AND 70', '[{"date":"2025-10-22","location":"Seattle","temperature":56.2,"humidity":66},{"date":"2025-10-23","location":"Portland","temperature":54.6,"humidity":70},{"date":"2025-10-23","location":"Seattle","temperature":57.8,"humidity":68}]'), - ('Just location column => SELECT location FROM weather_readings', '[{"location":"Portland"},{"location":"Seattle"},{"location":"Boise"},{"location":"Portland"},{"location":"Seattle"},{"location":"Boise"}]'), - ('Only unique locations => SELECT DISTINCT location FROM weather_readings', '[{"location":"Portland"},{"location":"Seattle"},{"location":"Boise"}]'); -; diff --git a/exercises/concept/intro-select/intro-select.sql b/exercises/concept/intro-select/intro-select.sql index 265301d8..c3634b46 100644 --- a/exercises/concept/intro-select/intro-select.sql +++ b/exercises/concept/intro-select/intro-select.sql @@ -1,14 +1,42 @@ +.print +.print "All data" +.print "========" +.print SELECT * FROM weather_readings; +.print +.print "Location and temperature only" +.print "=============================" +.print SELECT location, temperature FROM weather_readings; +.print +.print "Greeting" +.print "========" +.print -- This one will fail on purpose SELECT 'Hello, world.' AS say_hi; +.print +.print "Data for Seattle" +.print "================" +.print SELECT * FROM weather_readings WHERE location = 'Seattle'; +.print +.print "Data with humidity constraints" +.print "==============================" +.print SELECT * FROM weather_readings WHERE humidity BETWEEN 60 AND 70; +.print +.print "Locations" +.print "=========" +.print SELECT location FROM weather_readings; +.print +.print "Unique locations" +.print "================" +.print SELECT DISTINCT location FROM weather_readings; diff --git a/exercises/concept/intro-select/intro-select_exemplar.sql b/exercises/concept/intro-select/intro-select_exemplar.sql new file mode 100644 index 00000000..d5859e11 --- /dev/null +++ b/exercises/concept/intro-select/intro-select_exemplar.sql @@ -0,0 +1,41 @@ +.print +.print "All data" +.print "========" +.print +SELECT * FROM weather_readings; + +.print +.print "Location and temperature only" +.print "=============================" +.print +SELECT location, temperature FROM weather_readings; + +.print +.print "Greeting" +.print "========" +.print +SELECT 'Hello, world.'; + +.print +.print "Data for Seattle" +.print "================" +.print +SELECT * FROM weather_readings WHERE location = 'Seattle'; + +.print +.print "Data with humidity constraints" +.print "==============================" +.print +SELECT * FROM weather_readings WHERE humidity BETWEEN 60 AND 70; + +.print +.print "Locations" +.print "=========" +.print +SELECT location FROM weather_readings; + +.print +.print "Unique locations" +.print "================" +.print +SELECT DISTINCT location FROM weather_readings; diff --git a/exercises/concept/intro-select/intro-select_test.sql b/exercises/concept/intro-select/intro-select_test.sql index 6010d111..8a479edb 100644 --- a/exercises/concept/intro-select/intro-select_test.sql +++ b/exercises/concept/intro-select/intro-select_test.sql @@ -1,85 +1,16 @@ -- Create database: .read ./create_fixture.sql --- Read user student solution and save any output as markdown in user_output.md: -.mode markdown -.output user_output.md -.echo on -.read ./intro-select.sql -.echo off -.output -.shell sed -i 1d user_output.md --- Re-run stub file to collect the results as json arrays -.mode json -.output outputs.txt -.read ./intro-select.sql -.output --- Creating the results table from the outputs.txt file -DROP TABLE IF EXISTS outputs; -CREATE TEMPORARY TABLE outputs (line TEXT NOT NULL); -.mode tabs -.import ./outputs.txt outputs -DROP TABLE IF EXISTS results; -CREATE TABLE results (result TEXT NOT NULL); -WITH inputs (input) AS ( - SELECT JSON(PRINTF('[%s]', GROUP_CONCAT(RTRIM(TRIM(line), ','), ','))) - FROM outputs -) -INSERT INTO results (result) -SELECT j.value - FROM inputs, JSON_EACH(input) j -; +.mode columns --- Create a clean testing environment: -.read ./create_test_table.sql --- Comparison of user input and the tests updates the status for each test: -UPDATE tests - SET status = 'pass' - FROM (SELECT result FROM results) AS actual - WHERE NOT EXISTS ( - SELECT key, value, type, path - FROM JSON_TREE(result) - WHERE type NOT IN ('array', 'object') - EXCEPT - SELECT key, value, type, path - FROM JSON_TREE(expected) - WHERE type NOT IN ('array', 'object') - ) - AND NOT EXISTS ( - SELECT key, value, type, path - FROM JSON_TREE(expected) - WHERE type NOT IN ('array', 'object') - EXCEPT - SELECT key, value, type, path - FROM JSON_TREE(result) - WHERE type NOT IN ('array', 'object') - ) -; +-- Generate expected output +.output expected_output.txt +.read ./intro-select_exemplar.sql --- Update message for failed tests to give helpful information: -UPDATE tests - -- SET message = 'Result for "' || tests.description || '"' || ' is <' || COALESCE(actual.result, 'NULL') || '> but should be <' || tests.expected || '>' - SET message = 'Result for <"' || tests.description || '">' || ' NOT FOUND' -- need improvements -WHERE tests.status = 'fail'; +-- Run user solution +.output user_output.txt +.read ./intro-select.sql --- Save results to ./output.json (needed by the online test-runner) -.mode json -.once './output.json' -SELECT - description, - status, - message, - output, - test_code, - task_id -FROM - tests; +-- Compare expected vs actual +.shell diff expected_output.txt user_output.txt --- Display test results in readable form for the student: -.mode table -SELECT - description, - status, - message -FROM - tests; diff --git a/exercises/concept/intro-select/outputs.txt b/exercises/concept/intro-select/outputs.txt deleted file mode 100644 index 9e837a4a..00000000 --- a/exercises/concept/intro-select/outputs.txt +++ /dev/null @@ -1,27 +0,0 @@ -[{"date":"2025-10-22","location":"Portland","temperature":53.10000000000000142,"humidity":72}, -{"date":"2025-10-22","location":"Seattle","temperature":56.20000000000000284,"humidity":66}, -{"date":"2025-10-22","location":"Boise","temperature":60.39999999999999858,"humidity":55}, -{"date":"2025-10-23","location":"Portland","temperature":54.60000000000000142,"humidity":70}, -{"date":"2025-10-23","location":"Seattle","temperature":57.79999999999999716,"humidity":68}, -{"date":"2025-10-23","location":"Boise","temperature":62.0,"humidity":58}] -[{"location":"Portland","temperature":53.10000000000000142}, -{"location":"Seattle","temperature":56.20000000000000284}, -{"location":"Boise","temperature":60.39999999999999858}, -{"location":"Portland","temperature":54.60000000000000142}, -{"location":"Seattle","temperature":57.79999999999999716}, -{"location":"Boise","temperature":62.0}] -[{"say_hi":"Hello, world."}] -[{"date":"2025-10-22","location":"Seattle","temperature":56.20000000000000284,"humidity":66}, -{"date":"2025-10-23","location":"Seattle","temperature":57.79999999999999716,"humidity":68}] -[{"date":"2025-10-22","location":"Seattle","temperature":56.20000000000000284,"humidity":66}, -{"date":"2025-10-23","location":"Portland","temperature":54.60000000000000142,"humidity":70}, -{"date":"2025-10-23","location":"Seattle","temperature":57.79999999999999716,"humidity":68}] -[{"location":"Portland"}, -{"location":"Seattle"}, -{"location":"Boise"}, -{"location":"Portland"}, -{"location":"Seattle"}, -{"location":"Boise"}] -[{"location":"Portland"}, -{"location":"Seattle"}, -{"location":"Boise"}] From 93bc0008c5f1348089f466593f886d3b7d4925b8 Mon Sep 17 00:00:00 2001 From: Todd Schwartz Date: Sat, 1 Nov 2025 08:08:07 -0700 Subject: [PATCH 03/16] Move each SELECT to separate file --- .../intro-select/intro-select-task1.sql | 1 + .../intro-select-task1_exemplar.sql | 1 + .../intro-select/intro-select-task2.sql | 1 + .../intro-select-task2_exemplar.sql | 1 + .../intro-select/intro-select-task3.sql | 2 + .../intro-select-task3_exemplar.sql | 1 + .../intro-select/intro-select-task4.sql | 1 + .../intro-select-task4_exemplar.sql | 1 + .../intro-select/intro-select-task5.sql | 1 + .../intro-select-task5_exemplar.sql | 1 + .../intro-select/intro-select-task6.sql | 1 + .../intro-select-task6_exemplar.sql | 1 + .../concept/intro-select/intro-select.sql | 42 ---------- .../intro-select/intro-select_exemplar.sql | 41 ---------- .../intro-select/intro-select_test.sql | 77 ++++++++++++++++++- 15 files changed, 87 insertions(+), 86 deletions(-) create mode 100644 exercises/concept/intro-select/intro-select-task1.sql create mode 100644 exercises/concept/intro-select/intro-select-task1_exemplar.sql create mode 100644 exercises/concept/intro-select/intro-select-task2.sql create mode 100644 exercises/concept/intro-select/intro-select-task2_exemplar.sql create mode 100644 exercises/concept/intro-select/intro-select-task3.sql create mode 100644 exercises/concept/intro-select/intro-select-task3_exemplar.sql create mode 100644 exercises/concept/intro-select/intro-select-task4.sql create mode 100644 exercises/concept/intro-select/intro-select-task4_exemplar.sql create mode 100644 exercises/concept/intro-select/intro-select-task5.sql create mode 100644 exercises/concept/intro-select/intro-select-task5_exemplar.sql create mode 100644 exercises/concept/intro-select/intro-select-task6.sql create mode 100644 exercises/concept/intro-select/intro-select-task6_exemplar.sql delete mode 100644 exercises/concept/intro-select/intro-select.sql delete mode 100644 exercises/concept/intro-select/intro-select_exemplar.sql diff --git a/exercises/concept/intro-select/intro-select-task1.sql b/exercises/concept/intro-select/intro-select-task1.sql new file mode 100644 index 00000000..ca7640a1 --- /dev/null +++ b/exercises/concept/intro-select/intro-select-task1.sql @@ -0,0 +1 @@ +SELECT * FROM weather_readings; diff --git a/exercises/concept/intro-select/intro-select-task1_exemplar.sql b/exercises/concept/intro-select/intro-select-task1_exemplar.sql new file mode 100644 index 00000000..ca7640a1 --- /dev/null +++ b/exercises/concept/intro-select/intro-select-task1_exemplar.sql @@ -0,0 +1 @@ +SELECT * FROM weather_readings; diff --git a/exercises/concept/intro-select/intro-select-task2.sql b/exercises/concept/intro-select/intro-select-task2.sql new file mode 100644 index 00000000..93b757f2 --- /dev/null +++ b/exercises/concept/intro-select/intro-select-task2.sql @@ -0,0 +1 @@ +SELECT location, temperature FROM weather_readings; diff --git a/exercises/concept/intro-select/intro-select-task2_exemplar.sql b/exercises/concept/intro-select/intro-select-task2_exemplar.sql new file mode 100644 index 00000000..93b757f2 --- /dev/null +++ b/exercises/concept/intro-select/intro-select-task2_exemplar.sql @@ -0,0 +1 @@ +SELECT location, temperature FROM weather_readings; diff --git a/exercises/concept/intro-select/intro-select-task3.sql b/exercises/concept/intro-select/intro-select-task3.sql new file mode 100644 index 00000000..c60b7b89 --- /dev/null +++ b/exercises/concept/intro-select/intro-select-task3.sql @@ -0,0 +1,2 @@ +-- Expected to fail +SELECT 'Hello, world.' AS say_hi; diff --git a/exercises/concept/intro-select/intro-select-task3_exemplar.sql b/exercises/concept/intro-select/intro-select-task3_exemplar.sql new file mode 100644 index 00000000..215327a6 --- /dev/null +++ b/exercises/concept/intro-select/intro-select-task3_exemplar.sql @@ -0,0 +1 @@ +SELECT 'Hello, world.'; diff --git a/exercises/concept/intro-select/intro-select-task4.sql b/exercises/concept/intro-select/intro-select-task4.sql new file mode 100644 index 00000000..9383fe0d --- /dev/null +++ b/exercises/concept/intro-select/intro-select-task4.sql @@ -0,0 +1 @@ +SELECT * FROM weather_readings WHERE location = 'Seattle'; diff --git a/exercises/concept/intro-select/intro-select-task4_exemplar.sql b/exercises/concept/intro-select/intro-select-task4_exemplar.sql new file mode 100644 index 00000000..9383fe0d --- /dev/null +++ b/exercises/concept/intro-select/intro-select-task4_exemplar.sql @@ -0,0 +1 @@ +SELECT * FROM weather_readings WHERE location = 'Seattle'; diff --git a/exercises/concept/intro-select/intro-select-task5.sql b/exercises/concept/intro-select/intro-select-task5.sql new file mode 100644 index 00000000..82da3538 --- /dev/null +++ b/exercises/concept/intro-select/intro-select-task5.sql @@ -0,0 +1 @@ +SELECT * FROM weather_readings WHERE humidity BETWEEN 60 AND 70; diff --git a/exercises/concept/intro-select/intro-select-task5_exemplar.sql b/exercises/concept/intro-select/intro-select-task5_exemplar.sql new file mode 100644 index 00000000..82da3538 --- /dev/null +++ b/exercises/concept/intro-select/intro-select-task5_exemplar.sql @@ -0,0 +1 @@ +SELECT * FROM weather_readings WHERE humidity BETWEEN 60 AND 70; diff --git a/exercises/concept/intro-select/intro-select-task6.sql b/exercises/concept/intro-select/intro-select-task6.sql new file mode 100644 index 00000000..442c8046 --- /dev/null +++ b/exercises/concept/intro-select/intro-select-task6.sql @@ -0,0 +1 @@ +SELECT DISTINCT location FROM weather_readings; diff --git a/exercises/concept/intro-select/intro-select-task6_exemplar.sql b/exercises/concept/intro-select/intro-select-task6_exemplar.sql new file mode 100644 index 00000000..442c8046 --- /dev/null +++ b/exercises/concept/intro-select/intro-select-task6_exemplar.sql @@ -0,0 +1 @@ +SELECT DISTINCT location FROM weather_readings; diff --git a/exercises/concept/intro-select/intro-select.sql b/exercises/concept/intro-select/intro-select.sql deleted file mode 100644 index c3634b46..00000000 --- a/exercises/concept/intro-select/intro-select.sql +++ /dev/null @@ -1,42 +0,0 @@ -.print -.print "All data" -.print "========" -.print -SELECT * FROM weather_readings; - -.print -.print "Location and temperature only" -.print "=============================" -.print -SELECT location, temperature FROM weather_readings; - -.print -.print "Greeting" -.print "========" -.print --- This one will fail on purpose -SELECT 'Hello, world.' AS say_hi; - -.print -.print "Data for Seattle" -.print "================" -.print -SELECT * FROM weather_readings WHERE location = 'Seattle'; - -.print -.print "Data with humidity constraints" -.print "==============================" -.print -SELECT * FROM weather_readings WHERE humidity BETWEEN 60 AND 70; - -.print -.print "Locations" -.print "=========" -.print -SELECT location FROM weather_readings; - -.print -.print "Unique locations" -.print "================" -.print -SELECT DISTINCT location FROM weather_readings; diff --git a/exercises/concept/intro-select/intro-select_exemplar.sql b/exercises/concept/intro-select/intro-select_exemplar.sql deleted file mode 100644 index d5859e11..00000000 --- a/exercises/concept/intro-select/intro-select_exemplar.sql +++ /dev/null @@ -1,41 +0,0 @@ -.print -.print "All data" -.print "========" -.print -SELECT * FROM weather_readings; - -.print -.print "Location and temperature only" -.print "=============================" -.print -SELECT location, temperature FROM weather_readings; - -.print -.print "Greeting" -.print "========" -.print -SELECT 'Hello, world.'; - -.print -.print "Data for Seattle" -.print "================" -.print -SELECT * FROM weather_readings WHERE location = 'Seattle'; - -.print -.print "Data with humidity constraints" -.print "==============================" -.print -SELECT * FROM weather_readings WHERE humidity BETWEEN 60 AND 70; - -.print -.print "Locations" -.print "=========" -.print -SELECT location FROM weather_readings; - -.print -.print "Unique locations" -.print "================" -.print -SELECT DISTINCT location FROM weather_readings; diff --git a/exercises/concept/intro-select/intro-select_test.sql b/exercises/concept/intro-select/intro-select_test.sql index 8a479edb..a108fbb1 100644 --- a/exercises/concept/intro-select/intro-select_test.sql +++ b/exercises/concept/intro-select/intro-select_test.sql @@ -5,11 +5,82 @@ -- Generate expected output .output expected_output.txt -.read ./intro-select_exemplar.sql --- Run user solution +.print +.print "All data" +.print "========" +.print +.read intro-select-task1_exemplar.sql + +.print +.print "Location and temperature only" +.print "=============================" +.print +.read intro-select-task2_exemplar.sql + +.print +.print "Greeting" +.print "========" +.print +.read intro-select-task3_exemplar.sql + +.print +.print "Data for Seattle" +.print "================" +.print +.read intro-select-task4_exemplar.sql + +.print +.print "Data with humidity constraints" +.print "==============================" +.print +.read intro-select-task5_exemplar.sql + +.print +.print "Locations" +.print "=========" +.print +.read intro-select-task6_exemplar.sql + + +-- Run user solutions .output user_output.txt -.read ./intro-select.sql +.print +.print "All data" +.print "========" +.print +.read intro-select-task1.sql + +.print +.print "Location and temperature only" +.print "=============================" +.print +.read intro-select-task2.sql + +.print +.print "Greeting" +.print "========" +.print +.read intro-select-task3.sql + +.print +.print "Data for Seattle" +.print "================" +.print +.read intro-select-task4.sql + +.print +.print "Data with humidity constraints" +.print "==============================" +.print +.read intro-select-task5.sql + +.print +.print "Locations" +.print "=========" +.print +.read intro-select-task6.sql + -- Compare expected vs actual .shell diff expected_output.txt user_output.txt From ac316d11dfd59d918ecad554e2e4b504ae408eb8 Mon Sep 17 00:00:00 2001 From: Todd Schwartz Date: Mon, 3 Nov 2025 16:05:36 -0800 Subject: [PATCH 04/16] Revert "Move each SELECT to separate file" This reverts commit 93bc0008c5f1348089f466593f886d3b7d4925b8. --- .../intro-select/intro-select-task1.sql | 1 - .../intro-select-task1_exemplar.sql | 1 - .../intro-select/intro-select-task2.sql | 1 - .../intro-select-task2_exemplar.sql | 1 - .../intro-select/intro-select-task3.sql | 2 - .../intro-select-task3_exemplar.sql | 1 - .../intro-select/intro-select-task4.sql | 1 - .../intro-select-task4_exemplar.sql | 1 - .../intro-select/intro-select-task5.sql | 1 - .../intro-select-task5_exemplar.sql | 1 - .../intro-select/intro-select-task6.sql | 1 - .../intro-select-task6_exemplar.sql | 1 - .../concept/intro-select/intro-select.sql | 42 ++++++++++ .../intro-select/intro-select_exemplar.sql | 41 ++++++++++ .../intro-select/intro-select_test.sql | 77 +------------------ 15 files changed, 86 insertions(+), 87 deletions(-) delete mode 100644 exercises/concept/intro-select/intro-select-task1.sql delete mode 100644 exercises/concept/intro-select/intro-select-task1_exemplar.sql delete mode 100644 exercises/concept/intro-select/intro-select-task2.sql delete mode 100644 exercises/concept/intro-select/intro-select-task2_exemplar.sql delete mode 100644 exercises/concept/intro-select/intro-select-task3.sql delete mode 100644 exercises/concept/intro-select/intro-select-task3_exemplar.sql delete mode 100644 exercises/concept/intro-select/intro-select-task4.sql delete mode 100644 exercises/concept/intro-select/intro-select-task4_exemplar.sql delete mode 100644 exercises/concept/intro-select/intro-select-task5.sql delete mode 100644 exercises/concept/intro-select/intro-select-task5_exemplar.sql delete mode 100644 exercises/concept/intro-select/intro-select-task6.sql delete mode 100644 exercises/concept/intro-select/intro-select-task6_exemplar.sql create mode 100644 exercises/concept/intro-select/intro-select.sql create mode 100644 exercises/concept/intro-select/intro-select_exemplar.sql diff --git a/exercises/concept/intro-select/intro-select-task1.sql b/exercises/concept/intro-select/intro-select-task1.sql deleted file mode 100644 index ca7640a1..00000000 --- a/exercises/concept/intro-select/intro-select-task1.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM weather_readings; diff --git a/exercises/concept/intro-select/intro-select-task1_exemplar.sql b/exercises/concept/intro-select/intro-select-task1_exemplar.sql deleted file mode 100644 index ca7640a1..00000000 --- a/exercises/concept/intro-select/intro-select-task1_exemplar.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM weather_readings; diff --git a/exercises/concept/intro-select/intro-select-task2.sql b/exercises/concept/intro-select/intro-select-task2.sql deleted file mode 100644 index 93b757f2..00000000 --- a/exercises/concept/intro-select/intro-select-task2.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT location, temperature FROM weather_readings; diff --git a/exercises/concept/intro-select/intro-select-task2_exemplar.sql b/exercises/concept/intro-select/intro-select-task2_exemplar.sql deleted file mode 100644 index 93b757f2..00000000 --- a/exercises/concept/intro-select/intro-select-task2_exemplar.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT location, temperature FROM weather_readings; diff --git a/exercises/concept/intro-select/intro-select-task3.sql b/exercises/concept/intro-select/intro-select-task3.sql deleted file mode 100644 index c60b7b89..00000000 --- a/exercises/concept/intro-select/intro-select-task3.sql +++ /dev/null @@ -1,2 +0,0 @@ --- Expected to fail -SELECT 'Hello, world.' AS say_hi; diff --git a/exercises/concept/intro-select/intro-select-task3_exemplar.sql b/exercises/concept/intro-select/intro-select-task3_exemplar.sql deleted file mode 100644 index 215327a6..00000000 --- a/exercises/concept/intro-select/intro-select-task3_exemplar.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT 'Hello, world.'; diff --git a/exercises/concept/intro-select/intro-select-task4.sql b/exercises/concept/intro-select/intro-select-task4.sql deleted file mode 100644 index 9383fe0d..00000000 --- a/exercises/concept/intro-select/intro-select-task4.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM weather_readings WHERE location = 'Seattle'; diff --git a/exercises/concept/intro-select/intro-select-task4_exemplar.sql b/exercises/concept/intro-select/intro-select-task4_exemplar.sql deleted file mode 100644 index 9383fe0d..00000000 --- a/exercises/concept/intro-select/intro-select-task4_exemplar.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM weather_readings WHERE location = 'Seattle'; diff --git a/exercises/concept/intro-select/intro-select-task5.sql b/exercises/concept/intro-select/intro-select-task5.sql deleted file mode 100644 index 82da3538..00000000 --- a/exercises/concept/intro-select/intro-select-task5.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM weather_readings WHERE humidity BETWEEN 60 AND 70; diff --git a/exercises/concept/intro-select/intro-select-task5_exemplar.sql b/exercises/concept/intro-select/intro-select-task5_exemplar.sql deleted file mode 100644 index 82da3538..00000000 --- a/exercises/concept/intro-select/intro-select-task5_exemplar.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM weather_readings WHERE humidity BETWEEN 60 AND 70; diff --git a/exercises/concept/intro-select/intro-select-task6.sql b/exercises/concept/intro-select/intro-select-task6.sql deleted file mode 100644 index 442c8046..00000000 --- a/exercises/concept/intro-select/intro-select-task6.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT DISTINCT location FROM weather_readings; diff --git a/exercises/concept/intro-select/intro-select-task6_exemplar.sql b/exercises/concept/intro-select/intro-select-task6_exemplar.sql deleted file mode 100644 index 442c8046..00000000 --- a/exercises/concept/intro-select/intro-select-task6_exemplar.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT DISTINCT location FROM weather_readings; diff --git a/exercises/concept/intro-select/intro-select.sql b/exercises/concept/intro-select/intro-select.sql new file mode 100644 index 00000000..c3634b46 --- /dev/null +++ b/exercises/concept/intro-select/intro-select.sql @@ -0,0 +1,42 @@ +.print +.print "All data" +.print "========" +.print +SELECT * FROM weather_readings; + +.print +.print "Location and temperature only" +.print "=============================" +.print +SELECT location, temperature FROM weather_readings; + +.print +.print "Greeting" +.print "========" +.print +-- This one will fail on purpose +SELECT 'Hello, world.' AS say_hi; + +.print +.print "Data for Seattle" +.print "================" +.print +SELECT * FROM weather_readings WHERE location = 'Seattle'; + +.print +.print "Data with humidity constraints" +.print "==============================" +.print +SELECT * FROM weather_readings WHERE humidity BETWEEN 60 AND 70; + +.print +.print "Locations" +.print "=========" +.print +SELECT location FROM weather_readings; + +.print +.print "Unique locations" +.print "================" +.print +SELECT DISTINCT location FROM weather_readings; diff --git a/exercises/concept/intro-select/intro-select_exemplar.sql b/exercises/concept/intro-select/intro-select_exemplar.sql new file mode 100644 index 00000000..d5859e11 --- /dev/null +++ b/exercises/concept/intro-select/intro-select_exemplar.sql @@ -0,0 +1,41 @@ +.print +.print "All data" +.print "========" +.print +SELECT * FROM weather_readings; + +.print +.print "Location and temperature only" +.print "=============================" +.print +SELECT location, temperature FROM weather_readings; + +.print +.print "Greeting" +.print "========" +.print +SELECT 'Hello, world.'; + +.print +.print "Data for Seattle" +.print "================" +.print +SELECT * FROM weather_readings WHERE location = 'Seattle'; + +.print +.print "Data with humidity constraints" +.print "==============================" +.print +SELECT * FROM weather_readings WHERE humidity BETWEEN 60 AND 70; + +.print +.print "Locations" +.print "=========" +.print +SELECT location FROM weather_readings; + +.print +.print "Unique locations" +.print "================" +.print +SELECT DISTINCT location FROM weather_readings; diff --git a/exercises/concept/intro-select/intro-select_test.sql b/exercises/concept/intro-select/intro-select_test.sql index a108fbb1..8a479edb 100644 --- a/exercises/concept/intro-select/intro-select_test.sql +++ b/exercises/concept/intro-select/intro-select_test.sql @@ -5,82 +5,11 @@ -- Generate expected output .output expected_output.txt +.read ./intro-select_exemplar.sql -.print -.print "All data" -.print "========" -.print -.read intro-select-task1_exemplar.sql - -.print -.print "Location and temperature only" -.print "=============================" -.print -.read intro-select-task2_exemplar.sql - -.print -.print "Greeting" -.print "========" -.print -.read intro-select-task3_exemplar.sql - -.print -.print "Data for Seattle" -.print "================" -.print -.read intro-select-task4_exemplar.sql - -.print -.print "Data with humidity constraints" -.print "==============================" -.print -.read intro-select-task5_exemplar.sql - -.print -.print "Locations" -.print "=========" -.print -.read intro-select-task6_exemplar.sql - - --- Run user solutions +-- Run user solution .output user_output.txt -.print -.print "All data" -.print "========" -.print -.read intro-select-task1.sql - -.print -.print "Location and temperature only" -.print "=============================" -.print -.read intro-select-task2.sql - -.print -.print "Greeting" -.print "========" -.print -.read intro-select-task3.sql - -.print -.print "Data for Seattle" -.print "================" -.print -.read intro-select-task4.sql - -.print -.print "Data with humidity constraints" -.print "==============================" -.print -.read intro-select-task5.sql - -.print -.print "Locations" -.print "=========" -.print -.read intro-select-task6.sql - +.read ./intro-select.sql -- Compare expected vs actual .shell diff expected_output.txt user_output.txt From 581bda6f8e41cc794aa9690fb4d9bc2495221b57 Mon Sep 17 00:00:00 2001 From: Todd Schwartz Date: Tue, 4 Nov 2025 18:17:44 -0800 Subject: [PATCH 05/16] Evaluate results in python --- exercises/concept/intro-select/evaluate.py | 60 +++++++++++++++++ .../concept/intro-select/intro-select.sql | 28 -------- .../intro-select/intro-select_exemplar.sql | 41 ------------ .../intro-select/intro-select_test.sql | 9 +-- exercises/concept/intro-select/test_data.json | 64 +++++++++++++++++++ 5 files changed, 126 insertions(+), 76 deletions(-) create mode 100644 exercises/concept/intro-select/evaluate.py delete mode 100644 exercises/concept/intro-select/intro-select_exemplar.sql create mode 100644 exercises/concept/intro-select/test_data.json diff --git a/exercises/concept/intro-select/evaluate.py b/exercises/concept/intro-select/evaluate.py new file mode 100644 index 00000000..0cedc7b7 --- /dev/null +++ b/exercises/concept/intro-select/evaluate.py @@ -0,0 +1,60 @@ +from pathlib import Path +import json +import sys + + +def gather_results(filename): + text = Path(filename).read_text() + list_start_indices = find_all_occurrences('[', text) + list_end_indices = find_all_occurrences(']', text) + return [ + json.loads(text[begin:end+1]) + for begin, end in zip(list_start_indices, list_end_indices) + ] + + +def find_all_occurrences(char, string): + return [i for i, c, in enumerate(string) if c == char] + + +def format_diff(expected, actual): + return ( + f"\tExpected:\n" + f"\t\t{expected}\n" + f"\tActual:\n" + f"\t\t{actual}" + ) + +def report_results(all_test_data, actual_results): + results = [] + for test_data, actual in zip(all_test_data, actual_results): + result = {'description': test_data['description']} + if test_data['expected'] == actual: + result['status'] = 'pass' + else: + result['status'] = 'fail' + result['message'] = "Expected: " + json.dumps(test_data['expected']) + result['output'] = "Actual: " + json.dumps(actual) + results.append(result) + return make_result_report(results) + + +def make_result_report(results): + status = 'pass' if all(r['status'] == 'pass' for r in results) else 'fail' + return { + "version": 3, + "status": status, + "tests": results + } + + +def main(): + test_data_filename, user_output_filename = sys.argv[1:3] + test_data = json.loads(Path(test_data_filename).read_text()) + actual_results = gather_results(user_output_filename) + results = report_results(test_data, actual_results) + Path('results.json').write_text(json.dumps(results, indent=2)) + + +if __name__ == '__main__': + main() diff --git a/exercises/concept/intro-select/intro-select.sql b/exercises/concept/intro-select/intro-select.sql index c3634b46..265301d8 100644 --- a/exercises/concept/intro-select/intro-select.sql +++ b/exercises/concept/intro-select/intro-select.sql @@ -1,42 +1,14 @@ -.print -.print "All data" -.print "========" -.print SELECT * FROM weather_readings; -.print -.print "Location and temperature only" -.print "=============================" -.print SELECT location, temperature FROM weather_readings; -.print -.print "Greeting" -.print "========" -.print -- This one will fail on purpose SELECT 'Hello, world.' AS say_hi; -.print -.print "Data for Seattle" -.print "================" -.print SELECT * FROM weather_readings WHERE location = 'Seattle'; -.print -.print "Data with humidity constraints" -.print "==============================" -.print SELECT * FROM weather_readings WHERE humidity BETWEEN 60 AND 70; -.print -.print "Locations" -.print "=========" -.print SELECT location FROM weather_readings; -.print -.print "Unique locations" -.print "================" -.print SELECT DISTINCT location FROM weather_readings; diff --git a/exercises/concept/intro-select/intro-select_exemplar.sql b/exercises/concept/intro-select/intro-select_exemplar.sql deleted file mode 100644 index d5859e11..00000000 --- a/exercises/concept/intro-select/intro-select_exemplar.sql +++ /dev/null @@ -1,41 +0,0 @@ -.print -.print "All data" -.print "========" -.print -SELECT * FROM weather_readings; - -.print -.print "Location and temperature only" -.print "=============================" -.print -SELECT location, temperature FROM weather_readings; - -.print -.print "Greeting" -.print "========" -.print -SELECT 'Hello, world.'; - -.print -.print "Data for Seattle" -.print "================" -.print -SELECT * FROM weather_readings WHERE location = 'Seattle'; - -.print -.print "Data with humidity constraints" -.print "==============================" -.print -SELECT * FROM weather_readings WHERE humidity BETWEEN 60 AND 70; - -.print -.print "Locations" -.print "=========" -.print -SELECT location FROM weather_readings; - -.print -.print "Unique locations" -.print "================" -.print -SELECT DISTINCT location FROM weather_readings; diff --git a/exercises/concept/intro-select/intro-select_test.sql b/exercises/concept/intro-select/intro-select_test.sql index 8a479edb..dc1fde83 100644 --- a/exercises/concept/intro-select/intro-select_test.sql +++ b/exercises/concept/intro-select/intro-select_test.sql @@ -1,16 +1,11 @@ -- Create database: .read ./create_fixture.sql -.mode columns - --- Generate expected output -.output expected_output.txt -.read ./intro-select_exemplar.sql +.mode json -- Run user solution .output user_output.txt .read ./intro-select.sql -- Compare expected vs actual -.shell diff expected_output.txt user_output.txt - +.shell python evaluate.py test_data.json user_output.txt diff --git a/exercises/concept/intro-select/test_data.json b/exercises/concept/intro-select/test_data.json new file mode 100644 index 00000000..e150eeaa --- /dev/null +++ b/exercises/concept/intro-select/test_data.json @@ -0,0 +1,64 @@ +[ + { + "description": "ALL records => SELECT * FROM weather_readings", + "expected": [ + {"date":"2025-10-22","location":"Portland","temperature":53.1,"humidity":72}, + {"date":"2025-10-22","location":"Seattle","temperature":56.2,"humidity":66}, + {"date":"2025-10-22","location":"Boise","temperature":60.4,"humidity":55}, + {"date":"2025-10-23","location":"Portland","temperature":54.6,"humidity":70}, + {"date":"2025-10-23","location":"Seattle","temperature":57.79999999999999,"humidity":68}, + {"date":"2025-10-23","location":"Boise","temperature":62.0,"humidity":58} + ] + }, + { + "description": "Just location and temperature columns => SELECT location, temperature FROM weather_readings", + "expected": [ + {"location":"Portland","temperature":53.1}, + {"location":"Seattle","temperature":56.2}, + {"location":"Boise","temperature":60.4}, + {"location":"Portland","temperature":54.6}, + {"location":"Seattle","temperature":57.79999999999999}, + {"location":"Boise","temperature":62.0} + ] + }, + { + "description": "Without \"FROM\" => SELECT 'Hello, world.'", + "expected": [ + {"'Hello, world.'":"Hello, world."} + ] + }, + { + "description": "All records from Seatle location => SELECT * FROM weather_readings WHERE location = 'Seattle'", + "expected": [ + {"date":"2025-10-22","location":"Seattle","temperature":56.2,"humidity":66}, + {"date":"2025-10-23","location":"Seattle","temperature":57.79999999999999,"humidity":68} + ] + }, + { + "description": "All records where humidity in range => SELECT * FROM weather_readings WHERE humidity BETWEEN 60 AND 70", + "expected": [ + {"date":"2025-10-22","location":"Seattle","temperature":56.2,"humidity":66}, + {"date":"2025-10-23","location":"Portland","temperature":54.6,"humidity":70}, + {"date":"2025-10-23","location":"Seattle","temperature":57.79999999999999,"humidity":68} + ] + }, + { + "description": "Just location column => SELECT location FROM weather_readings", + "expected": [ + {"location":"Portland"}, + {"location":"Seattle"}, + {"location":"Boise"}, + {"location":"Portland"}, + {"location":"Seattle"}, + {"location":"Boise"} + ] + }, + { + "description": "Only unique locations => SELECT DISTINCT location FROM weather_readings", + "expected": [ + {"location":"Portland"}, + {"location":"Seattle"}, + {"location":"Boise"} + ] + } +] \ No newline at end of file From 20a91eced68e363876fd5220a4d459720cee87fb Mon Sep 17 00:00:00 2001 From: Todd Schwartz Date: Tue, 18 Nov 2025 13:44:58 -0800 Subject: [PATCH 06/16] Use less-challenging floating point values --- exercises/concept/intro-select/data.csv | 2 +- exercises/concept/intro-select/test_data.json | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/exercises/concept/intro-select/data.csv b/exercises/concept/intro-select/data.csv index 808ccddf..50696b1f 100644 --- a/exercises/concept/intro-select/data.csv +++ b/exercises/concept/intro-select/data.csv @@ -2,5 +2,5 @@ "2025-10-22","Seattle",56.2,66 "2025-10-22","Boise",60.4,55 "2025-10-23","Portland",54.6,70 -"2025-10-23","Seattle",57.8,68 +"2025-10-23","Seattle",57.5,68 "2025-10-23","Boise",62.0,58 diff --git a/exercises/concept/intro-select/test_data.json b/exercises/concept/intro-select/test_data.json index e150eeaa..e6d3df48 100644 --- a/exercises/concept/intro-select/test_data.json +++ b/exercises/concept/intro-select/test_data.json @@ -6,7 +6,7 @@ {"date":"2025-10-22","location":"Seattle","temperature":56.2,"humidity":66}, {"date":"2025-10-22","location":"Boise","temperature":60.4,"humidity":55}, {"date":"2025-10-23","location":"Portland","temperature":54.6,"humidity":70}, - {"date":"2025-10-23","location":"Seattle","temperature":57.79999999999999,"humidity":68}, + {"date":"2025-10-23","location":"Seattle","temperature":57.5,"humidity":68}, {"date":"2025-10-23","location":"Boise","temperature":62.0,"humidity":58} ] }, @@ -17,7 +17,7 @@ {"location":"Seattle","temperature":56.2}, {"location":"Boise","temperature":60.4}, {"location":"Portland","temperature":54.6}, - {"location":"Seattle","temperature":57.79999999999999}, + {"location":"Seattle","temperature":57.5}, {"location":"Boise","temperature":62.0} ] }, @@ -31,7 +31,7 @@ "description": "All records from Seatle location => SELECT * FROM weather_readings WHERE location = 'Seattle'", "expected": [ {"date":"2025-10-22","location":"Seattle","temperature":56.2,"humidity":66}, - {"date":"2025-10-23","location":"Seattle","temperature":57.79999999999999,"humidity":68} + {"date":"2025-10-23","location":"Seattle","temperature":57.5,"humidity":68} ] }, { @@ -39,7 +39,7 @@ "expected": [ {"date":"2025-10-22","location":"Seattle","temperature":56.2,"humidity":66}, {"date":"2025-10-23","location":"Portland","temperature":54.6,"humidity":70}, - {"date":"2025-10-23","location":"Seattle","temperature":57.79999999999999,"humidity":68} + {"date":"2025-10-23","location":"Seattle","temperature":57.5,"humidity":68} ] }, { From eac01958b4a23a989bbed46e5844552f25493943 Mon Sep 17 00:00:00 2001 From: Todd Schwartz Date: Tue, 18 Nov 2025 13:49:48 -0800 Subject: [PATCH 07/16] Rework test structure - Split exercise into separate tasks - Capture actual output of each task in a separate table - Store expected output for each task in a separate table --- .../concept/intro-select/intro-select.sql | 40 +++++++++--- .../intro-select/intro-select_test.sql | 10 +-- .../concept/intro-select/store_test_data.sql | 52 +++++++++++++++ exercises/concept/intro-select/test_data.json | 64 ------------------- 4 files changed, 90 insertions(+), 76 deletions(-) create mode 100644 exercises/concept/intro-select/store_test_data.sql delete mode 100644 exercises/concept/intro-select/test_data.json diff --git a/exercises/concept/intro-select/intro-select.sql b/exercises/concept/intro-select/intro-select.sql index 265301d8..15aa4ce3 100644 --- a/exercises/concept/intro-select/intro-select.sql +++ b/exercises/concept/intro-select/intro-select.sql @@ -1,14 +1,38 @@ -SELECT * FROM weather_readings; +-- TODO: Surround each task in a CREATE TABLE -SELECT location, temperature FROM weather_readings; +CREATE TABLE all_data AS + -- Task 1. Select all records. + SELECT * FROM weather_readings +; --- This one will fail on purpose -SELECT 'Hello, world.' AS say_hi; +CREATE TABLE location_and_temperature AS + -- Task 2. Select only location and temperature data. + -- Expect failure + SELECT temperature FROM weather_readings +; -SELECT * FROM weather_readings WHERE location = 'Seattle'; +CREATE TABLE hello AS + -- Task 3. Say hello! + -- Expect failure + SELECT 'Goodbye, Mars.' +; -SELECT * FROM weather_readings WHERE humidity BETWEEN 60 AND 70; +CREATE TABLE seattle AS + -- Task 4. Select all data for Seattle. + SELECT * FROM weather_readings WHERE location = 'Seattle' +; -SELECT location FROM weather_readings; +CREATE TABLE limited_humidity AS + -- Task 5. Select all data where the humidity is between 60% and 70%. + SELECT * FROM weather_readings WHERE humidity BETWEEN 60 AND 70 +; -SELECT DISTINCT location FROM weather_readings; +CREATE TABLE location AS + -- Task 6. Select only location data. + SELECT location FROM weather_readings +; + +CREATE TABLE unique_location AS + -- Task 7. Select only unique location data. + SELECT DISTINCT location FROM weather_readings +; diff --git a/exercises/concept/intro-select/intro-select_test.sql b/exercises/concept/intro-select/intro-select_test.sql index dc1fde83..fbce6cd2 100644 --- a/exercises/concept/intro-select/intro-select_test.sql +++ b/exercises/concept/intro-select/intro-select_test.sql @@ -1,11 +1,13 @@ -- Create database: .read ./create_fixture.sql -.mode json +-- ASK: How can we correlate user output with specific tests? + +-- Store test data +.read ./store_test_data.sql -- Run user solution -.output user_output.txt .read ./intro-select.sql --- Compare expected vs actual -.shell python evaluate.py test_data.json user_output.txt +-- TODO: Compare expected vs actual among each of the slug values in test_data +-- Compare columns first; if columns are equal, then compare rows diff --git a/exercises/concept/intro-select/store_test_data.sql b/exercises/concept/intro-select/store_test_data.sql new file mode 100644 index 00000000..03d3d94f --- /dev/null +++ b/exercises/concept/intro-select/store_test_data.sql @@ -0,0 +1,52 @@ +DROP TABLE IF EXISTS test_data; +CREATE TABLE test_data ( + task_number INTEGER, + slug, + description +); + +INSERT INTO test_data (slug, description, task_number) VALUES + ('all_data', 'All data', 1), + ('location_and_temp', 'Just location and temperature', 2), + ('hello', 'Hello, world', 3), + ('seattle', 'Just Seattle data', 4), + ('limited_humidity', 'Data with humidity within range', 5), + ('location', 'Just location', 6), + ('unique_location', 'Just unique locaations', 7); + + +DROP TABLE IF EXISTS all_data_expected; +CREATE TABLE all_data_expected AS + SELECT * FROM weather_readings +; + +DROP TABLE IF EXISTS location_and_temperature_expected; +CREATE TABLE location_and_temperature_expected AS + SELECT location, temperature FROM weather_readings +; + +DROP TABLE IF EXISTS hello_expected; +CREATE TABLE hello_expected AS + SELECT 'Hello, world.' +; + +DROP TABLE IF EXISTS seattle_expected; +CREATE TABLE seattle_expected AS + SELECT * FROM weather_readings WHERE location = 'Seattle' +; + +DROP TABLE IF EXISTS limited_humidity_expected; +CREATE TABLE limited_humidity_expected AS + SELECT * FROM weather_readings WHERE humidity BETWEEN 60 AND 70 +; + +DROP TABLE IF EXISTS location_expected; +CREATE TABLE location_expected AS + SELECT location FROM weather_readings +; + +DROP TABLE IF EXISTS unique_location_expected; +CREATE TABLE unique_location_expected AS + SELECT DISTINCT location FROM weather_readings +; + diff --git a/exercises/concept/intro-select/test_data.json b/exercises/concept/intro-select/test_data.json deleted file mode 100644 index e6d3df48..00000000 --- a/exercises/concept/intro-select/test_data.json +++ /dev/null @@ -1,64 +0,0 @@ -[ - { - "description": "ALL records => SELECT * FROM weather_readings", - "expected": [ - {"date":"2025-10-22","location":"Portland","temperature":53.1,"humidity":72}, - {"date":"2025-10-22","location":"Seattle","temperature":56.2,"humidity":66}, - {"date":"2025-10-22","location":"Boise","temperature":60.4,"humidity":55}, - {"date":"2025-10-23","location":"Portland","temperature":54.6,"humidity":70}, - {"date":"2025-10-23","location":"Seattle","temperature":57.5,"humidity":68}, - {"date":"2025-10-23","location":"Boise","temperature":62.0,"humidity":58} - ] - }, - { - "description": "Just location and temperature columns => SELECT location, temperature FROM weather_readings", - "expected": [ - {"location":"Portland","temperature":53.1}, - {"location":"Seattle","temperature":56.2}, - {"location":"Boise","temperature":60.4}, - {"location":"Portland","temperature":54.6}, - {"location":"Seattle","temperature":57.5}, - {"location":"Boise","temperature":62.0} - ] - }, - { - "description": "Without \"FROM\" => SELECT 'Hello, world.'", - "expected": [ - {"'Hello, world.'":"Hello, world."} - ] - }, - { - "description": "All records from Seatle location => SELECT * FROM weather_readings WHERE location = 'Seattle'", - "expected": [ - {"date":"2025-10-22","location":"Seattle","temperature":56.2,"humidity":66}, - {"date":"2025-10-23","location":"Seattle","temperature":57.5,"humidity":68} - ] - }, - { - "description": "All records where humidity in range => SELECT * FROM weather_readings WHERE humidity BETWEEN 60 AND 70", - "expected": [ - {"date":"2025-10-22","location":"Seattle","temperature":56.2,"humidity":66}, - {"date":"2025-10-23","location":"Portland","temperature":54.6,"humidity":70}, - {"date":"2025-10-23","location":"Seattle","temperature":57.5,"humidity":68} - ] - }, - { - "description": "Just location column => SELECT location FROM weather_readings", - "expected": [ - {"location":"Portland"}, - {"location":"Seattle"}, - {"location":"Boise"}, - {"location":"Portland"}, - {"location":"Seattle"}, - {"location":"Boise"} - ] - }, - { - "description": "Only unique locations => SELECT DISTINCT location FROM weather_readings", - "expected": [ - {"location":"Portland"}, - {"location":"Seattle"}, - {"location":"Boise"} - ] - } -] \ No newline at end of file From 24d11d9e98faaa2516b03081de63a5f7f6517893 Mon Sep 17 00:00:00 2001 From: Todd Schwartz Date: Sat, 29 Nov 2025 09:22:42 -0800 Subject: [PATCH 08/16] Remove unused file --- exercises/concept/intro-select/evaluate.py | 60 ---------------------- 1 file changed, 60 deletions(-) delete mode 100644 exercises/concept/intro-select/evaluate.py diff --git a/exercises/concept/intro-select/evaluate.py b/exercises/concept/intro-select/evaluate.py deleted file mode 100644 index 0cedc7b7..00000000 --- a/exercises/concept/intro-select/evaluate.py +++ /dev/null @@ -1,60 +0,0 @@ -from pathlib import Path -import json -import sys - - -def gather_results(filename): - text = Path(filename).read_text() - list_start_indices = find_all_occurrences('[', text) - list_end_indices = find_all_occurrences(']', text) - return [ - json.loads(text[begin:end+1]) - for begin, end in zip(list_start_indices, list_end_indices) - ] - - -def find_all_occurrences(char, string): - return [i for i, c, in enumerate(string) if c == char] - - -def format_diff(expected, actual): - return ( - f"\tExpected:\n" - f"\t\t{expected}\n" - f"\tActual:\n" - f"\t\t{actual}" - ) - -def report_results(all_test_data, actual_results): - results = [] - for test_data, actual in zip(all_test_data, actual_results): - result = {'description': test_data['description']} - if test_data['expected'] == actual: - result['status'] = 'pass' - else: - result['status'] = 'fail' - result['message'] = "Expected: " + json.dumps(test_data['expected']) - result['output'] = "Actual: " + json.dumps(actual) - results.append(result) - return make_result_report(results) - - -def make_result_report(results): - status = 'pass' if all(r['status'] == 'pass' for r in results) else 'fail' - return { - "version": 3, - "status": status, - "tests": results - } - - -def main(): - test_data_filename, user_output_filename = sys.argv[1:3] - test_data = json.loads(Path(test_data_filename).read_text()) - actual_results = gather_results(user_output_filename) - results = report_results(test_data, actual_results) - Path('results.json').write_text(json.dumps(results, indent=2)) - - -if __name__ == '__main__': - main() From 2c68fb2d8d1a904c8f0ceb016706ed0cefb2e907 Mon Sep 17 00:00:00 2001 From: Todd Schwartz Date: Sat, 29 Nov 2025 09:32:58 -0800 Subject: [PATCH 09/16] Rework test report logic - Use only INTEGER data, for simplicity - Add shell script to generate report - Add jq filters to aid in report generation - Store test data in json file, not in db --- .../concept/intro-select/create_fixture.sql | 2 +- exercises/concept/intro-select/data.csv | 12 ++-- .../concept/intro-select/generate-report.sh | 12 ++++ .../intro-select/intro-select_test.sql | 15 ++-- .../concept/intro-select/store_test_data.sql | 52 -------------- exercises/concept/intro-select/test-report.jq | 7 ++ exercises/concept/intro-select/test-result.jq | 13 ++++ exercises/concept/intro-select/test_data.json | 70 +++++++++++++++++++ 8 files changed, 117 insertions(+), 66 deletions(-) create mode 100644 exercises/concept/intro-select/generate-report.sh delete mode 100644 exercises/concept/intro-select/store_test_data.sql create mode 100644 exercises/concept/intro-select/test-report.jq create mode 100644 exercises/concept/intro-select/test-result.jq create mode 100644 exercises/concept/intro-select/test_data.json diff --git a/exercises/concept/intro-select/create_fixture.sql b/exercises/concept/intro-select/create_fixture.sql index 37d4b63f..c96de796 100644 --- a/exercises/concept/intro-select/create_fixture.sql +++ b/exercises/concept/intro-select/create_fixture.sql @@ -2,7 +2,7 @@ DROP TABLE IF EXISTS weather_readings; CREATE TABLE weather_readings ( date TEXT NOT NULL, location TEXT NOT NULL, - temperature REAL NOT NULL, + temperature INTEGER NOT NULL, humidity INTEGER NOT NULL ); diff --git a/exercises/concept/intro-select/data.csv b/exercises/concept/intro-select/data.csv index 50696b1f..cbe5e09a 100644 --- a/exercises/concept/intro-select/data.csv +++ b/exercises/concept/intro-select/data.csv @@ -1,6 +1,6 @@ -"2025-10-22","Portland",53.1,72 -"2025-10-22","Seattle",56.2,66 -"2025-10-22","Boise",60.4,55 -"2025-10-23","Portland",54.6,70 -"2025-10-23","Seattle",57.5,68 -"2025-10-23","Boise",62.0,58 +"2025-10-22","Portland",53,72 +"2025-10-22","Seattle",56,66 +"2025-10-22","Boise",60,55 +"2025-10-23","Portland",54,70 +"2025-10-23","Seattle",57,68 +"2025-10-23","Boise",62,58 diff --git a/exercises/concept/intro-select/generate-report.sh b/exercises/concept/intro-select/generate-report.sh new file mode 100644 index 00000000..9070bc6b --- /dev/null +++ b/exercises/concept/intro-select/generate-report.sh @@ -0,0 +1,12 @@ +DB_FILE=$1 +SLUGS=$(jq -n --slurpfile test_data test_data.json '$test_data[0] | keys' | sed 's/[][",]//g') + +# Generate result for each test +rm -f results.txt +for SLUG in $SLUGS; do + ACTUAL=$(sqlite3 -json $DB_FILE "SELECT * FROM ${SLUG};" | tr -d '[:space:]') + jq -n --slurpfile test_data test_data.json --argjson got ''${ACTUAL}'' --arg slug ${SLUG} -f test-result.jq >> results.txt +done + +# Generate top-level report +jq -n --slurpfile results results.txt -f test-report.jq > results.json diff --git a/exercises/concept/intro-select/intro-select_test.sql b/exercises/concept/intro-select/intro-select_test.sql index fbce6cd2..853a2dd6 100644 --- a/exercises/concept/intro-select/intro-select_test.sql +++ b/exercises/concept/intro-select/intro-select_test.sql @@ -1,13 +1,14 @@ -- Create database: .read ./create_fixture.sql --- ASK: How can we correlate user output with specific tests? +-- ASK: How can we correlate user output with specific tests? One way is to add .output statements +-- in the stub file. But that introduces more noise into the stub file. --- Store test data -.read ./store_test_data.sql - --- Run user solution +-- Run user solution and store results .read ./intro-select.sql +.shell rm -f ./results.db +.save ./results.db --- TODO: Compare expected vs actual among each of the slug values in test_data --- Compare columns first; if columns are equal, then compare rows +-- Generate report +-- TODO: Make sure exit code reflects test result +.shell sh ./generate-report.sh results.db diff --git a/exercises/concept/intro-select/store_test_data.sql b/exercises/concept/intro-select/store_test_data.sql deleted file mode 100644 index 03d3d94f..00000000 --- a/exercises/concept/intro-select/store_test_data.sql +++ /dev/null @@ -1,52 +0,0 @@ -DROP TABLE IF EXISTS test_data; -CREATE TABLE test_data ( - task_number INTEGER, - slug, - description -); - -INSERT INTO test_data (slug, description, task_number) VALUES - ('all_data', 'All data', 1), - ('location_and_temp', 'Just location and temperature', 2), - ('hello', 'Hello, world', 3), - ('seattle', 'Just Seattle data', 4), - ('limited_humidity', 'Data with humidity within range', 5), - ('location', 'Just location', 6), - ('unique_location', 'Just unique locaations', 7); - - -DROP TABLE IF EXISTS all_data_expected; -CREATE TABLE all_data_expected AS - SELECT * FROM weather_readings -; - -DROP TABLE IF EXISTS location_and_temperature_expected; -CREATE TABLE location_and_temperature_expected AS - SELECT location, temperature FROM weather_readings -; - -DROP TABLE IF EXISTS hello_expected; -CREATE TABLE hello_expected AS - SELECT 'Hello, world.' -; - -DROP TABLE IF EXISTS seattle_expected; -CREATE TABLE seattle_expected AS - SELECT * FROM weather_readings WHERE location = 'Seattle' -; - -DROP TABLE IF EXISTS limited_humidity_expected; -CREATE TABLE limited_humidity_expected AS - SELECT * FROM weather_readings WHERE humidity BETWEEN 60 AND 70 -; - -DROP TABLE IF EXISTS location_expected; -CREATE TABLE location_expected AS - SELECT location FROM weather_readings -; - -DROP TABLE IF EXISTS unique_location_expected; -CREATE TABLE unique_location_expected AS - SELECT DISTINCT location FROM weather_readings -; - diff --git a/exercises/concept/intro-select/test-report.jq b/exercises/concept/intro-select/test-report.jq new file mode 100644 index 00000000..6f7ae012 --- /dev/null +++ b/exercises/concept/intro-select/test-report.jq @@ -0,0 +1,7 @@ +($results | map(.status) | if all(. == "pass") then "pass" else "fail" end) as $status + +| { + "version": 3, + "status": $status, + "tests": $results +} diff --git a/exercises/concept/intro-select/test-result.jq b/exercises/concept/intro-select/test-result.jq new file mode 100644 index 00000000..df946a03 --- /dev/null +++ b/exercises/concept/intro-select/test-result.jq @@ -0,0 +1,13 @@ +$test_data[0][$slug] as $single_test_data +| $single_test_data.description as $description +| $single_test_data.expected as $expected +| $single_test_data.task_id as $task_id +| if $task_id then {$task_id} else {} end as $entry +| $entry + {$description} as $entry +| if $got != $expected then + # TODO: Make the message more human-readable + "Expected " + ($expected | tostring) + ", but got " + ($got | tostring) as $message + | $entry + {"status": "fail", $message} + else + $entry + {"status": "pass"} + end diff --git a/exercises/concept/intro-select/test_data.json b/exercises/concept/intro-select/test_data.json new file mode 100644 index 00000000..3cb80b8c --- /dev/null +++ b/exercises/concept/intro-select/test_data.json @@ -0,0 +1,70 @@ +{ + "all_data": { + "task_id": 1, + "description": "All data", + "expected": [ + {"date":"2025-10-22","location":"Portland","temperature":53,"humidity":72}, + {"date":"2025-10-22","location":"Seattle","temperature":56,"humidity":66}, + {"date":"2025-10-22","location":"Boise","temperature":60,"humidity":55}, + {"date":"2025-10-23","location":"Portland","temperature":54,"humidity":70}, + {"date":"2025-10-23","location":"Seattle","temperature":57,"humidity":68}, + {"date":"2025-10-23","location":"Boise","temperature":62,"humidity":58} + ] + }, + "location_and_temperature": { + "task_id": 2, + "description": "Just location and temperature", + "expected": [ + {"location":"Portland","temperature":53}, + {"location":"Seattle","temperature":56}, + {"location":"Boise","temperature":60}, + {"location":"Portland","temperature":54}, + {"location":"Seattle","temperature":57}, + {"location":"Boise","temperature":62} + ] + }, + "hello": { + "description": "Hello, world.", + "expected": [ + {"'Hello, world.'":"Hello, world."} + ] + }, + "seattle": { + "task_id": 3, + "description": "Seattle only", + "expected": [ + {"date":"2025-10-22","location":"Seattle","temperature":56,"humidity":66}, + {"date":"2025-10-23","location":"Seattle","temperature":57,"humidity":68} + ] + }, + "limited_humidity": { + "task_id": 4, + "description": "Humidity within range", + "expected": [ + {"date":"2025-10-22","location":"Seattle","temperature":56,"humidity":66}, + {"date":"2025-10-23","location":"Portland","temperature":54,"humidity":70}, + {"date":"2025-10-23","location":"Seattle","temperature":57,"humidity":68} + ] + }, + "location": { + "task_id": 5, + "description": "Just locations", + "expected": [ + {"location":"Portland"}, + {"location":"Seattle"}, + {"location":"Boise"}, + {"location":"Portland"}, + {"location":"Seattle"}, + {"location":"Boise"} + ] + }, + "unique_location": { + "task_id": 5, + "description": "Only unique locations", + "expected": [ + {"location":"Portland"}, + {"location":"Seattle"}, + {"location":"Boise"} + ] + } +} \ No newline at end of file From 878a96430c46e27ed874dda2538c92979510f735 Mon Sep 17 00:00:00 2001 From: Todd Schwartz Date: Tue, 2 Dec 2025 17:26:03 -0800 Subject: [PATCH 10/16] Make failure message more human-readable Also: - Conform to test runner interface, filenames - Capture user output - Cause SQL test script to exit with error on test failure - Handle empty output from test - Remove "Hello, world" test --- .../concept/intro-select/generate-report.sh | 5 +++- .../concept/intro-select/intro-select.sql | 21 +++++++---------- .../intro-select/intro-select_test.sql | 12 +++++++++- exercises/concept/intro-select/test-result.jq | 23 ++++++++++++++++--- exercises/concept/intro-select/test_data.json | 6 ----- 5 files changed, 43 insertions(+), 24 deletions(-) diff --git a/exercises/concept/intro-select/generate-report.sh b/exercises/concept/intro-select/generate-report.sh index 9070bc6b..3af2cbf8 100644 --- a/exercises/concept/intro-select/generate-report.sh +++ b/exercises/concept/intro-select/generate-report.sh @@ -5,8 +5,11 @@ SLUGS=$(jq -n --slurpfile test_data test_data.json '$test_data[0] | keys' | sed rm -f results.txt for SLUG in $SLUGS; do ACTUAL=$(sqlite3 -json $DB_FILE "SELECT * FROM ${SLUG};" | tr -d '[:space:]') + if [ -z "$ACTUAL" ]; then + ACTUAL="[]" + fi jq -n --slurpfile test_data test_data.json --argjson got ''${ACTUAL}'' --arg slug ${SLUG} -f test-result.jq >> results.txt done # Generate top-level report -jq -n --slurpfile results results.txt -f test-report.jq > results.json +jq -n --slurpfile results results.txt -f test-report.jq > output.json diff --git a/exercises/concept/intro-select/intro-select.sql b/exercises/concept/intro-select/intro-select.sql index 15aa4ce3..4c512429 100644 --- a/exercises/concept/intro-select/intro-select.sql +++ b/exercises/concept/intro-select/intro-select.sql @@ -1,5 +1,3 @@ --- TODO: Surround each task in a CREATE TABLE - CREATE TABLE all_data AS -- Task 1. Select all records. SELECT * FROM weather_readings @@ -11,28 +9,25 @@ CREATE TABLE location_and_temperature AS SELECT temperature FROM weather_readings ; -CREATE TABLE hello AS - -- Task 3. Say hello! - -- Expect failure - SELECT 'Goodbye, Mars.' -; - CREATE TABLE seattle AS - -- Task 4. Select all data for Seattle. + -- Task 3. Select all data for Seattle. SELECT * FROM weather_readings WHERE location = 'Seattle' ; CREATE TABLE limited_humidity AS - -- Task 5. Select all data where the humidity is between 60% and 70%. - SELECT * FROM weather_readings WHERE humidity BETWEEN 60 AND 70 + -- Task 4. Select all data where the humidity is between 60% and 70%. + -- Expect failure + SELECT * FROM weather_readings WHERE humidity BETWEEN 50 AND 62 ; +.print "My debugging output:" +SELECT humidity from weather_readings; CREATE TABLE location AS - -- Task 6. Select only location data. + -- Task 5. Select only location data. SELECT location FROM weather_readings ; CREATE TABLE unique_location AS - -- Task 7. Select only unique location data. + -- Task 5. Select only unique location data. SELECT DISTINCT location FROM weather_readings ; diff --git a/exercises/concept/intro-select/intro-select_test.sql b/exercises/concept/intro-select/intro-select_test.sql index 853a2dd6..82d274c5 100644 --- a/exercises/concept/intro-select/intro-select_test.sql +++ b/exercises/concept/intro-select/intro-select_test.sql @@ -5,10 +5,20 @@ -- in the stub file. But that introduces more noise into the stub file. -- Run user solution and store results +.mode markdown +.output user_output.md .read ./intro-select.sql .shell rm -f ./results.db .save ./results.db +.output + -- Generate report --- TODO: Make sure exit code reflects test result .shell sh ./generate-report.sh results.db + +-- Exit normally if all tests passed; otherwise abort with error code +CREATE TABLE result ( + status TEXT CHECK(status = "true") +); +.mode line +.import "|jq -c .status output.json" result diff --git a/exercises/concept/intro-select/test-result.jq b/exercises/concept/intro-select/test-result.jq index df946a03..606e710b 100644 --- a/exercises/concept/intro-select/test-result.jq +++ b/exercises/concept/intro-select/test-result.jq @@ -1,3 +1,22 @@ +def columns: + if (. | length) == 0 then [] + else .[0] | keys + end; + +def rows: + [map(to_entries)[] | map(.value)]; + +def failure_message(got; expected): + (got | columns | tostring) as $got_columns + | (expected | columns | tostring) as $expected_columns + | if $got_columns != $expected_columns then + "Expected columns " + $expected_columns + "; but got " + $got_columns + else + (got | rows | tostring) as $got_rows + | (expected | rows | tostring) as $expected_rows + | "With columns " + $got_columns + ", expected " + $expected_rows + "; but got " + $got_rows + end; + $test_data[0][$slug] as $single_test_data | $single_test_data.description as $description | $single_test_data.expected as $expected @@ -5,9 +24,7 @@ $test_data[0][$slug] as $single_test_data | if $task_id then {$task_id} else {} end as $entry | $entry + {$description} as $entry | if $got != $expected then - # TODO: Make the message more human-readable - "Expected " + ($expected | tostring) + ", but got " + ($got | tostring) as $message - | $entry + {"status": "fail", $message} + $entry + {"status": "fail", "message": failure_message($got; $expected)} else $entry + {"status": "pass"} end diff --git a/exercises/concept/intro-select/test_data.json b/exercises/concept/intro-select/test_data.json index 3cb80b8c..295e3f66 100644 --- a/exercises/concept/intro-select/test_data.json +++ b/exercises/concept/intro-select/test_data.json @@ -23,12 +23,6 @@ {"location":"Boise","temperature":62} ] }, - "hello": { - "description": "Hello, world.", - "expected": [ - {"'Hello, world.'":"Hello, world."} - ] - }, "seattle": { "task_id": 3, "description": "Seattle only", From d1b773e8752866ea090506dd2d26b557a5578086 Mon Sep 17 00:00:00 2001 From: Todd Schwartz Date: Wed, 3 Dec 2025 10:34:28 -0800 Subject: [PATCH 11/16] Really integrate with test runner - Aggregate only individual test results in output.json - Don't change exit code based on test result --- exercises/concept/intro-select/intro-select_test.sql | 11 ++--------- .../{generate-report.sh => report-results.sh} | 4 ++-- exercises/concept/intro-select/test-report.jq | 7 ------- 3 files changed, 4 insertions(+), 18 deletions(-) rename exercises/concept/intro-select/{generate-report.sh => report-results.sh} (82%) delete mode 100644 exercises/concept/intro-select/test-report.jq diff --git a/exercises/concept/intro-select/intro-select_test.sql b/exercises/concept/intro-select/intro-select_test.sql index 82d274c5..d844e726 100644 --- a/exercises/concept/intro-select/intro-select_test.sql +++ b/exercises/concept/intro-select/intro-select_test.sql @@ -13,12 +13,5 @@ .output --- Generate report -.shell sh ./generate-report.sh results.db - --- Exit normally if all tests passed; otherwise abort with error code -CREATE TABLE result ( - status TEXT CHECK(status = "true") -); -.mode line -.import "|jq -c .status output.json" result +-- Report results +.shell sh ./report-results.sh results.db diff --git a/exercises/concept/intro-select/generate-report.sh b/exercises/concept/intro-select/report-results.sh similarity index 82% rename from exercises/concept/intro-select/generate-report.sh rename to exercises/concept/intro-select/report-results.sh index 3af2cbf8..323980d3 100644 --- a/exercises/concept/intro-select/generate-report.sh +++ b/exercises/concept/intro-select/report-results.sh @@ -11,5 +11,5 @@ for SLUG in $SLUGS; do jq -n --slurpfile test_data test_data.json --argjson got ''${ACTUAL}'' --arg slug ${SLUG} -f test-result.jq >> results.txt done -# Generate top-level report -jq -n --slurpfile results results.txt -f test-report.jq > output.json +# Aggregate results +jq -n --slurpfile results results.txt '$results' > output.json diff --git a/exercises/concept/intro-select/test-report.jq b/exercises/concept/intro-select/test-report.jq deleted file mode 100644 index 6f7ae012..00000000 --- a/exercises/concept/intro-select/test-report.jq +++ /dev/null @@ -1,7 +0,0 @@ -($results | map(.status) | if all(. == "pass") then "pass" else "fail" end) as $status - -| { - "version": 3, - "status": $status, - "tests": $results -} From 6303fbe5abff19580cd1f4d9b79e53fb2faf273b Mon Sep 17 00:00:00 2001 From: Todd Schwartz Date: Fri, 12 Dec 2025 08:08:18 -0800 Subject: [PATCH 12/16] Apply suggestions from code review Co-authored-by: Glenn Jackman Co-authored-by: Isaac Good --- exercises/concept/intro-select/intro-select_test.sql | 2 +- exercises/concept/intro-select/report-results.sh | 7 ++++--- exercises/concept/intro-select/test-result.jq | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/exercises/concept/intro-select/intro-select_test.sql b/exercises/concept/intro-select/intro-select_test.sql index d844e726..478311cb 100644 --- a/exercises/concept/intro-select/intro-select_test.sql +++ b/exercises/concept/intro-select/intro-select_test.sql @@ -14,4 +14,4 @@ .output -- Report results -.shell sh ./report-results.sh results.db +.shell bash ./report-results.sh results.db diff --git a/exercises/concept/intro-select/report-results.sh b/exercises/concept/intro-select/report-results.sh index 323980d3..dc062053 100644 --- a/exercises/concept/intro-select/report-results.sh +++ b/exercises/concept/intro-select/report-results.sh @@ -1,14 +1,15 @@ +#!/usr/bin/env bash DB_FILE=$1 -SLUGS=$(jq -n --slurpfile test_data test_data.json '$test_data[0] | keys' | sed 's/[][",]//g') +mapfile -t SLUGS < <(jq -r 'keys[]' test_data.json) # Generate result for each test rm -f results.txt -for SLUG in $SLUGS; do +for SLUG in "${SLUGS[@]}"; do ACTUAL=$(sqlite3 -json $DB_FILE "SELECT * FROM ${SLUG};" | tr -d '[:space:]') if [ -z "$ACTUAL" ]; then ACTUAL="[]" fi - jq -n --slurpfile test_data test_data.json --argjson got ''${ACTUAL}'' --arg slug ${SLUG} -f test-result.jq >> results.txt + jq -n --slurpfile test_data test_data.json --argjson got "${ACTUAL}" --arg slug "${SLUG}" -f test-result.jq >> results.txt done # Aggregate results diff --git a/exercises/concept/intro-select/test-result.jq b/exercises/concept/intro-select/test-result.jq index 606e710b..dba779f3 100644 --- a/exercises/concept/intro-select/test-result.jq +++ b/exercises/concept/intro-select/test-result.jq @@ -4,7 +4,7 @@ def columns: end; def rows: - [map(to_entries)[] | map(.value)]; + map(to_entries | map(.value)); def failure_message(got; expected): (got | columns | tostring) as $got_columns @@ -14,7 +14,7 @@ def failure_message(got; expected): else (got | rows | tostring) as $got_rows | (expected | rows | tostring) as $expected_rows - | "With columns " + $got_columns + ", expected " + $expected_rows + "; but got " + $got_rows + | "With columns " + $got_columns + ", \nbexpected " + $expected_rows + ";\nbut got " + $got_rows end; $test_data[0][$slug] as $single_test_data @@ -22,7 +22,7 @@ $test_data[0][$slug] as $single_test_data | $single_test_data.expected as $expected | $single_test_data.task_id as $task_id | if $task_id then {$task_id} else {} end as $entry -| $entry + {$description} as $entry +| $entry += {$description} | if $got != $expected then $entry + {"status": "fail", "message": failure_message($got; $expected)} else From 284fe3a1aee793c077ec7c94bce703bf2772c71a Mon Sep 17 00:00:00 2001 From: Todd Schwartz Date: Fri, 12 Dec 2025 08:36:05 -0800 Subject: [PATCH 13/16] Bug fix: rebind updated entry --- exercises/concept/intro-select/test-result.jq | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/intro-select/test-result.jq b/exercises/concept/intro-select/test-result.jq index dba779f3..c7ddd913 100644 --- a/exercises/concept/intro-select/test-result.jq +++ b/exercises/concept/intro-select/test-result.jq @@ -22,7 +22,7 @@ $test_data[0][$slug] as $single_test_data | $single_test_data.expected as $expected | $single_test_data.task_id as $task_id | if $task_id then {$task_id} else {} end as $entry -| $entry += {$description} +| $entry + {$description} as $entry | if $got != $expected then $entry + {"status": "fail", "message": failure_message($got; $expected)} else From 54a1ddedcd0ab0645da6b9bb443232a1001fede0 Mon Sep 17 00:00:00 2001 From: Todd Schwartz Date: Fri, 12 Dec 2025 13:35:40 -0800 Subject: [PATCH 14/16] Apply suggestions from code review Co-authored-by: Isaac Good --- exercises/concept/intro-select/report-results.sh | 7 +++---- exercises/concept/intro-select/test-result.jq | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/exercises/concept/intro-select/report-results.sh b/exercises/concept/intro-select/report-results.sh index dc062053..e916ffc7 100644 --- a/exercises/concept/intro-select/report-results.sh +++ b/exercises/concept/intro-select/report-results.sh @@ -3,14 +3,13 @@ DB_FILE=$1 mapfile -t SLUGS < <(jq -r 'keys[]' test_data.json) # Generate result for each test -rm -f results.txt for SLUG in "${SLUGS[@]}"; do ACTUAL=$(sqlite3 -json $DB_FILE "SELECT * FROM ${SLUG};" | tr -d '[:space:]') - if [ -z "$ACTUAL" ]; then + if [[ -z "$ACTUAL" ]]; then ACTUAL="[]" fi - jq -n --slurpfile test_data test_data.json --argjson got "${ACTUAL}" --arg slug "${SLUG}" -f test-result.jq >> results.txt -done + jq -n --slurpfile test_data test_data.json --argjson got "${ACTUAL}" --arg slug "${SLUG}" -f test-result.jq +done > results.txt # Aggregate results jq -n --slurpfile results results.txt '$results' > output.json diff --git a/exercises/concept/intro-select/test-result.jq b/exercises/concept/intro-select/test-result.jq index c7ddd913..0a960b1e 100644 --- a/exercises/concept/intro-select/test-result.jq +++ b/exercises/concept/intro-select/test-result.jq @@ -10,11 +10,11 @@ def failure_message(got; expected): (got | columns | tostring) as $got_columns | (expected | columns | tostring) as $expected_columns | if $got_columns != $expected_columns then - "Expected columns " + $expected_columns + "; but got " + $got_columns + "Expected columns \($expected_columns); but got \($got_columns)" else (got | rows | tostring) as $got_rows | (expected | rows | tostring) as $expected_rows - | "With columns " + $got_columns + ", \nbexpected " + $expected_rows + ";\nbut got " + $got_rows + | "With columns \($got_columns)\nexpected \($expected_rows\)\nbut got \($got_rows)" end; $test_data[0][$slug] as $single_test_data From 4afcf77216523095281d643d6aa8be79918e4e4a Mon Sep 17 00:00:00 2001 From: Todd Schwartz Date: Fri, 12 Dec 2025 13:48:33 -0800 Subject: [PATCH 15/16] Appy review feedback --- exercises/concept/intro-select/report-results.sh | 14 +++++++------- exercises/concept/intro-select/test-result.jq | 14 +++++--------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/exercises/concept/intro-select/report-results.sh b/exercises/concept/intro-select/report-results.sh index e916ffc7..7fea77cb 100644 --- a/exercises/concept/intro-select/report-results.sh +++ b/exercises/concept/intro-select/report-results.sh @@ -1,14 +1,14 @@ #!/usr/bin/env bash -DB_FILE=$1 -mapfile -t SLUGS < <(jq -r 'keys[]' test_data.json) +db_file=$1 +mapfile -t slugs < <(jq -r 'keys[]' test_data.json) # Generate result for each test -for SLUG in "${SLUGS[@]}"; do - ACTUAL=$(sqlite3 -json $DB_FILE "SELECT * FROM ${SLUG};" | tr -d '[:space:]') - if [[ -z "$ACTUAL" ]]; then - ACTUAL="[]" +for slug in "${slugs[@]}"; do + actual=$(sqlite3 -json $db_file "SELECT * FROM ${slug};" | tr -d '[:space:]') + if [[ -z "$actual" ]]; then + actual="[]" fi - jq -n --slurpfile test_data test_data.json --argjson got "${ACTUAL}" --arg slug "${SLUG}" -f test-result.jq + jq -n --slurpfile test_data test_data.json --argjson got "${actual}" --arg slug "${slug}" -f test-result.jq done > results.txt # Aggregate results diff --git a/exercises/concept/intro-select/test-result.jq b/exercises/concept/intro-select/test-result.jq index 0a960b1e..4dbced99 100644 --- a/exercises/concept/intro-select/test-result.jq +++ b/exercises/concept/intro-select/test-result.jq @@ -14,17 +14,13 @@ def failure_message(got; expected): else (got | rows | tostring) as $got_rows | (expected | rows | tostring) as $expected_rows - | "With columns \($got_columns)\nexpected \($expected_rows\)\nbut got \($got_rows)" + | "With columns \($got_columns)\nexpected \($expected_rows)\nbut got \($got_rows)" end; -$test_data[0][$slug] as $single_test_data -| $single_test_data.description as $description -| $single_test_data.expected as $expected -| $single_test_data.task_id as $task_id -| if $task_id then {$task_id} else {} end as $entry -| $entry + {$description} as $entry -| if $got != $expected then - $entry + {"status": "fail", "message": failure_message($got; $expected)} +$test_data[0][$slug] +| del(.expected) as $entry +| if $got != .expected then + $entry + {"status": "fail", "message": failure_message($got; .expected)} else $entry + {"status": "pass"} end From b8c68505698b6039ff69875aefef50b498cc7702 Mon Sep 17 00:00:00 2001 From: Todd Schwartz Date: Sat, 13 Dec 2025 12:26:34 -0800 Subject: [PATCH 16/16] Apply review comments --- .../concept/intro-select/report-results.sh | 2 +- exercises/concept/intro-select/test_data.json | 54 +++++++++---------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/exercises/concept/intro-select/report-results.sh b/exercises/concept/intro-select/report-results.sh index 7fea77cb..371a5f46 100644 --- a/exercises/concept/intro-select/report-results.sh +++ b/exercises/concept/intro-select/report-results.sh @@ -4,7 +4,7 @@ mapfile -t slugs < <(jq -r 'keys[]' test_data.json) # Generate result for each test for slug in "${slugs[@]}"; do - actual=$(sqlite3 -json $db_file "SELECT * FROM ${slug};" | tr -d '[:space:]') + actual=$(sqlite3 -json $db_file "SELECT * FROM ${slug};") if [[ -z "$actual" ]]; then actual="[]" fi diff --git a/exercises/concept/intro-select/test_data.json b/exercises/concept/intro-select/test_data.json index 295e3f66..e90689ec 100644 --- a/exercises/concept/intro-select/test_data.json +++ b/exercises/concept/intro-select/test_data.json @@ -3,62 +3,62 @@ "task_id": 1, "description": "All data", "expected": [ - {"date":"2025-10-22","location":"Portland","temperature":53,"humidity":72}, - {"date":"2025-10-22","location":"Seattle","temperature":56,"humidity":66}, - {"date":"2025-10-22","location":"Boise","temperature":60,"humidity":55}, - {"date":"2025-10-23","location":"Portland","temperature":54,"humidity":70}, - {"date":"2025-10-23","location":"Seattle","temperature":57,"humidity":68}, - {"date":"2025-10-23","location":"Boise","temperature":62,"humidity":58} + {"date": "2025-10-22", "location": "Portland", "temperature": 53, "humidity": 72}, + {"date": "2025-10-22", "location": "Seattle", "temperature": 56, "humidity": 66}, + {"date": "2025-10-22", "location": "Boise", "temperature": 60, "humidity": 55}, + {"date": "2025-10-23", "location": "Portland", "temperature": 54, "humidity": 70}, + {"date": "2025-10-23", "location": "Seattle", "temperature": 57, "humidity": 68}, + {"date": "2025-10-23", "location": "Boise", "temperature": 62, "humidity": 58} ] }, "location_and_temperature": { "task_id": 2, "description": "Just location and temperature", "expected": [ - {"location":"Portland","temperature":53}, - {"location":"Seattle","temperature":56}, - {"location":"Boise","temperature":60}, - {"location":"Portland","temperature":54}, - {"location":"Seattle","temperature":57}, - {"location":"Boise","temperature":62} + {"location": "Portland", "temperature": 53}, + {"location": "Seattle", "temperature": 56}, + {"location": "Boise", "temperature": 60}, + {"location": "Portland", "temperature": 54}, + {"location": "Seattle", "temperature": 57}, + {"location": "Boise", "temperature": 62} ] }, "seattle": { "task_id": 3, "description": "Seattle only", "expected": [ - {"date":"2025-10-22","location":"Seattle","temperature":56,"humidity":66}, - {"date":"2025-10-23","location":"Seattle","temperature":57,"humidity":68} + {"date": "2025-10-22", "location": "Seattle", "temperature": 56, "humidity": 66}, + {"date": "2025-10-23", "location": "Seattle", "temperature": 57, "humidity": 68} ] }, "limited_humidity": { "task_id": 4, "description": "Humidity within range", "expected": [ - {"date":"2025-10-22","location":"Seattle","temperature":56,"humidity":66}, - {"date":"2025-10-23","location":"Portland","temperature":54,"humidity":70}, - {"date":"2025-10-23","location":"Seattle","temperature":57,"humidity":68} + {"date": "2025-10-22", "location": "Seattle", "temperature": 56, "humidity": 66}, + {"date": "2025-10-23", "location": "Portland", "temperature": 54, "humidity": 70}, + {"date": "2025-10-23", "location": "Seattle", "temperature": 57, "humidity": 68} ] }, "location": { "task_id": 5, "description": "Just locations", "expected": [ - {"location":"Portland"}, - {"location":"Seattle"}, - {"location":"Boise"}, - {"location":"Portland"}, - {"location":"Seattle"}, - {"location":"Boise"} + {"location": "Portland"}, + {"location": "Seattle"}, + {"location": "Boise"}, + {"location": "Portland"}, + {"location": "Seattle"}, + {"location": "Boise"} ] }, "unique_location": { "task_id": 5, "description": "Only unique locations", "expected": [ - {"location":"Portland"}, - {"location":"Seattle"}, - {"location":"Boise"} + {"location": "Portland"}, + {"location": "Seattle"}, + {"location": "Boise"} ] } -} \ No newline at end of file +}