diff --git a/README.md b/README.md index 2ccf097..290e4b4 100644 --- a/README.md +++ b/README.md @@ -31,18 +31,18 @@ You will get output like so: ```bash $ invoke test python any -docker run ... - ⏱ ./src/python/sort_builtin.py on ./data/sort_input_0.txt ran for 0.5 seconds +docker run ... python ./src/python/sort_builtin.py + ⏱ ./src/python/sort_builtin.py on ./data/sort_input_0.txt ran for 0.52 seconds 🟢 ./src/python/sort_builtin.py on ./data/sort_input_0.txt succeeded -docker run ... - ⏱ ./src/python/sort_bubble_sort.py on ./data/sort_input_0.txt ran for 0.51 seconds - 🟢 ./src/python/sort_bubble_sort.py on ./data/sort_input_0.txt succeeded -docker run ... - ⏱ ./src/python/sort_selection_sort.py on ./data/sort_input_0.txt ran for 0.58 seconds - 🟢 ./src/python/sort_selection_sort.py on ./data/sort_input_0.txt succeeded -docker run ... - ⏱ ./src/python/sort_insertion_sort.py on ./data/sort_input_0.txt ran for 0.52 seconds - 🟢 ./src/python/sort_insertion_sort.py on ./data/sort_input_0.txt succeeded +docker run ... python ./src/python/sql_test.py + ⏱ ./src/python/sql_test.py on ./data/sql_input_2.sql ran for 0.75 seconds + 🟢 ./src/python/sql_test.py on ./data/sql_input_2.sql succeeded +docker run ... python ./src/python/sql_test.py + ⏱ ./src/python/sql_test.py on ./data/sql_input_3.sql ran for 0.75 seconds + 🟢 ./src/python/sql_test.py on ./data/sql_input_3.sql succeeded +docker run ... python ./src/python/sql_test.py + ⏱ ./src/python/sql_test.py on ./data/sql_input_1.sql ran for 0.74 seconds + 🟢 ./src/python/sql_test.py on ./data/sql_input_1.sql succeeded ✨ script run success ✨ ``` diff --git a/snippets/ruby/sql_test.rb b/snippets/ruby/sql_test.rb new file mode 100644 index 0000000..2a389b6 --- /dev/null +++ b/snippets/ruby/sql_test.rb @@ -0,0 +1,144 @@ +class SQLState + attr_reader :state + + def initialize(state = {}) + @state = state + end + + def read_table_meta(table_name) + @state[table_name] ? @state[table_name]['metadata'] || {} : {} + end + + def read_table_rows(table_name) + @state[table_name] ? @state[table_name]['rows'] || [] : [] + end + + def read_information_schema + @state.values.map { |data| data['metadata'] } + end + + def write_table_meta(table_name, data) + @state[table_name] ||= {} + @state[table_name]['metadata'] ||= {} + @state[table_name]['metadata'].merge!(data) + self.class.new(@state) + end + + def write_table_rows(table_name, data) + @state[table_name] ||= {} + @state[table_name]['rows'] ||= [] + @state[table_name]['rows'] << data + self.class.new(@state) + end +end + +module SQLType + def self.varchar(data) + data.to_s.strip.sub(/^['"]/, '').sub(/['"]$/, '') + end + + def self.int(data) + data.to_i + end +end + +module SQLFunctions + def self.create_table(state, *args, table_schema: 'public') + output = [] + table_name = args[2] + + columns = {} + columns_str = args[3..].join(' ').gsub(/[()]/, '').strip + unless columns_str.empty? + columns = Hash[columns_str.split(',').map { |column| column.strip.split(' ') }] + end + + if state.read_table_meta(table_name).empty? + metadata = { + 'table_name' => table_name, + 'table_schema' => table_schema, + 'columns' => columns + } + state = state.write_table_meta(table_name, metadata) + end + + [output, state] + end + + def self.insert_into(state, *args) + output = [] + table_name = args[2] + + sql_type_map = { + 'VARCHAR' => SQLType.method(:varchar), + 'INT' => SQLType.method(:int) + } + + values_index = args.index('VALUES') + raise 'VALUES not found' if values_index.nil? + + keys = args[3...values_index].join(' ').gsub(/[()]/, '').split(',').map(&:strip) + values = args[values_index + 1..].join(' ').gsub(/[()]/, '').split(',').map(&:strip) + key_value_map = Hash[keys.zip(values)] + + metadata = state.read_table_meta(table_name) + row = {} + key_value_map.each do |key, value| + row[key] = sql_type_map[metadata['columns'][key]].call(value) + end + state = state.write_table_rows(table_name, row) + + [output, state] + end + + def self.select(state, *args) + output = [] + + from_index = args.index('FROM') + # where_index = args.index('WHERE') + + raise 'FROM not found' if from_index.nil? + + select_keys = args[1...from_index].join(' ').split(',').map(&:strip) + from_value = args[from_index + 1] + + data = if from_value == 'information_schema.tables' + state.read_information_schema + else + state.read_table_rows(from_value) + end + + data.each do |datum| + output << select_keys.map { |key| [key, datum[key]] }.to_h + end + + [output, state] + end +end + +def run_sql(input_sql) + output = [] + state = SQLState.new + + input_sql = input_sql.map(&:strip).reject { |line| line.start_with?('--') } + input_sql = input_sql.join(' ').split(';') + + sql_function_map = { + 'CREATE TABLE' => SQLFunctions.method(:create_table), + 'SELECT' => SQLFunctions.method(:select), + 'INSERT INTO' => SQLFunctions.method(:insert_into) + } + + input_sql.each do |line| + words = line.split(' ') + words.each_index do |i| + key = words[0..i].join(' ').strip + if sql_function_map.key?(key) + output, state = sql_function_map[key].call(state, *words.reject(&:empty?)) + break + end + end + end + + [output.to_json] +end diff --git a/src/ruby/sort_bubble_sort.rb b/src/ruby/sort_bubble_sort.rb index 84f4e29..de36bd5 100644 --- a/src/ruby/sort_bubble_sort.rb +++ b/src/ruby/sort_bubble_sort.rb @@ -44,12 +44,12 @@ def do_sorting_round(input_list) # business logic end # ###################### -# 👇🏽 copy pasted helpers +if __FILE__ == $PROGRAM_NAME + input_file_path = ENV['INPUT_PATH'] + input_file = File.readlines(input_file_path) -input_file_path = ENV['INPUT_PATH'] -input_file = File.readlines(input_file_path) + sorted_data = do_sorting(input_file) -sorted_data = do_sorting(input_file) - -output_file_path = ENV['OUTPUT_PATH'] -File.open(output_file_path, 'wb') { |f| f.write(sorted_data.join('')) } + output_file_path = ENV['OUTPUT_PATH'] + File.open(output_file_path, 'wb') { |f| f.write(sorted_data.join('')) } +end diff --git a/src/ruby/sort_builtin.rb b/src/ruby/sort_builtin.rb index be6dabf..f3c3db8 100644 --- a/src/ruby/sort_builtin.rb +++ b/src/ruby/sort_builtin.rb @@ -14,12 +14,12 @@ def do_sorting(input_list) # business logic end # ###################### -# 👇🏽 copy pasted helpers +if __FILE__ == $PROGRAM_NAME + input_file_path = ENV['INPUT_PATH'] + input_file = File.readlines(input_file_path) -input_file_path = ENV['INPUT_PATH'] -input_file = File.readlines(input_file_path) + sorted_data = do_sorting(input_file) -sorted_data = do_sorting(input_file) - -output_file_path = ENV['OUTPUT_PATH'] -File.open(output_file_path, 'wb') { |f| f.write(sorted_data.join('')) } + output_file_path = ENV['OUTPUT_PATH'] + File.open(output_file_path, 'wb') { |f| f.write(sorted_data.join('')) } +end diff --git a/src/ruby/sort_insertion_sort.rb b/src/ruby/sort_insertion_sort.rb index 4154a01..9320645 100644 --- a/src/ruby/sort_insertion_sort.rb +++ b/src/ruby/sort_insertion_sort.rb @@ -55,12 +55,12 @@ def swap(idx) # business logic end # ###################### -# 👇🏽 copy pasted helpers +if __FILE__ == $PROGRAM_NAME + input_file_path = ENV['INPUT_PATH'] + input_file = File.readlines(input_file_path) -input_file_path = ENV['INPUT_PATH'] -input_file = File.readlines(input_file_path) + sorted_data = do_sorting(input_file) -sorted_data = do_sorting(input_file) - -output_file_path = ENV['OUTPUT_PATH'] -File.open(output_file_path, 'wb') { |f| f.write(sorted_data.join('')) } + output_file_path = ENV['OUTPUT_PATH'] + File.open(output_file_path, 'wb') { |f| f.write(sorted_data.join('')) } +end diff --git a/src/ruby/sort_selection_sort.rb b/src/ruby/sort_selection_sort.rb index 2fd2c3f..545cb12 100644 --- a/src/ruby/sort_selection_sort.rb +++ b/src/ruby/sort_selection_sort.rb @@ -56,12 +56,12 @@ def find_smallest_index(input_list) # business logic end # ###################### -# 👇🏽 copy pasted helpers +if __FILE__ == $PROGRAM_NAME + input_file_path = ENV['INPUT_PATH'] + input_file = File.readlines(input_file_path) -input_file_path = ENV['INPUT_PATH'] -input_file = File.readlines(input_file_path) + sorted_data = do_sorting(input_file) -sorted_data = do_sorting(input_file) - -output_file_path = ENV['OUTPUT_PATH'] -File.open(output_file_path, 'wb') { |f| f.write(sorted_data.join('')) } + output_file_path = ENV['OUTPUT_PATH'] + File.open(output_file_path, 'wb') { |f| f.write(sorted_data.join('')) } +end diff --git a/src/ruby/sql_test.rb b/src/ruby/sql_test.rb new file mode 100644 index 0000000..491bd38 --- /dev/null +++ b/src/ruby/sql_test.rb @@ -0,0 +1,166 @@ +# frozen_string_literal: true + +require 'json' + +######################## +# business logic start # +######################## + +class SQLState + attr_reader :state + + def initialize(state = {}) + @state = state + end + + def read_table_meta(table_name) + @state[table_name] ? @state[table_name]['metadata'] || {} : {} + end + + def read_table_rows(table_name) + @state[table_name] ? @state[table_name]['rows'] || [] : [] + end + + def read_information_schema + @state.values.map { |data| data['metadata'] } + end + + def write_table_meta(table_name, data) + @state[table_name] ||= {} + @state[table_name]['metadata'] ||= {} + @state[table_name]['metadata'].merge!(data) + self.class.new(@state) + end + + def write_table_rows(table_name, data) + @state[table_name] ||= {} + @state[table_name]['rows'] ||= [] + @state[table_name]['rows'] << data + self.class.new(@state) + end +end + +module SQLType + def self.varchar(data) + data.to_s.strip.sub(/^['"]/, '').sub(/['"]$/, '') + end + + def self.int(data) + data.to_i + end +end + +module SQLFunctions + def self.create_table(state, *args, table_schema: 'public') + output = [] + table_name = args[2] + + columns = {} + columns_str = args[3..].join(' ').gsub(/[()]/, '').strip + unless columns_str.empty? + columns = Hash[columns_str.split(',').map { |column| column.strip.split(' ') }] + end + + if state.read_table_meta(table_name).empty? + metadata = { + 'table_name' => table_name, + 'table_schema' => table_schema, + 'columns' => columns + } + state = state.write_table_meta(table_name, metadata) + end + + [output, state] + end + + def self.insert_into(state, *args) + output = [] + table_name = args[2] + + sql_type_map = { + 'VARCHAR' => SQLType.method(:varchar), + 'INT' => SQLType.method(:int) + } + + values_index = args.index('VALUES') + raise 'VALUES not found' if values_index.nil? + + keys = args[3...values_index].join(' ').gsub(/[()]/, '').split(',').map(&:strip) + values = args[values_index + 1..].join(' ').gsub(/[()]/, '').split(',').map(&:strip) + key_value_map = Hash[keys.zip(values)] + + metadata = state.read_table_meta(table_name) + row = {} + key_value_map.each do |key, value| + row[key] = sql_type_map[metadata['columns'][key]].call(value) + end + state = state.write_table_rows(table_name, row) + + [output, state] + end + + def self.select(state, *args) + output = [] + + from_index = args.index('FROM') + # where_index = args.index('WHERE') + + raise 'FROM not found' if from_index.nil? + + select_keys = args[1...from_index].join(' ').split(',').map(&:strip) + from_value = args[from_index + 1] + + data = if from_value == 'information_schema.tables' + state.read_information_schema + else + state.read_table_rows(from_value) + end + + data.each do |datum| + output << select_keys.map { |key| [key, datum[key]] }.to_h + end + + [output, state] + end +end + +def run_sql(input_sql) + output = [] + state = SQLState.new + + input_sql = input_sql.map(&:strip).reject { |line| line.start_with?('--') } + input_sql = input_sql.join(' ').split(';') + + sql_function_map = { + 'CREATE TABLE' => SQLFunctions.method(:create_table), + 'SELECT' => SQLFunctions.method(:select), + 'INSERT INTO' => SQLFunctions.method(:insert_into) + } + + input_sql.each do |line| + words = line.split(' ') + words.each_index do |i| + key = words[0..i].join(' ').strip + if sql_function_map.key?(key) + output, state = sql_function_map[key].call(state, *words.reject(&:empty?)) + break + end + end + end + + [output.to_json] +end + +###################### +# business logic end # +###################### + +if __FILE__ == $PROGRAM_NAME + input_file_path = ENV['INPUT_PATH'] + input_file = File.readlines(input_file_path) + + sorted_data = run_sql(input_file) + + output_file_path = ENV['OUTPUT_PATH'] + File.open(output_file_path, 'wb') { |f| f.write(sorted_data.join('')) } +end diff --git a/tasks.py b/tasks.py index 1012564..bfdf839 100644 --- a/tasks.py +++ b/tasks.py @@ -271,7 +271,8 @@ def run_tests(self, input_script): # run the script start_time = time.time() - output = self.invoke.run(ctx.docker_run_test, echo=True, pty=True) + print(f"docker run ... {ctx.language} {ctx.script_relative_path}") + output = self.invoke.run(ctx.docker_run_test, echo=False, pty=True) end_time = time.time() # report timing