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/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/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 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..dc1fde83 --- /dev/null +++ b/exercises/concept/intro-select/intro-select_test.sql @@ -0,0 +1,11 @@ +-- Create database: +.read ./create_fixture.sql + +.mode json + +-- 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 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