Noam_15

Queries Executer

Dec 4th, 2019
176
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 7.90 KB | None | 0 0
  1. # queries_executer_test.py
  2.  
  3. # The Task:
  4. # ● Write a suitable script.
  5. # ● Allow your script to run with a “mongodb” like query format. (Note: Don’t use
  6. #   mongodb. You only need to implement the query format.)
  7. #   The script should support:
  8. #       ○ $or, $and conditions
  9. #       ○ $eq, $ne, $in, $nin, $gt, $gte, $lt and $lte
  10. #   https://docs.mongodb.com/manual/reference/operator
  11. #
  12. # ● The script should get the query as an argument and print the documents found.
  13. # ● You are allowed to add as many functions as you need.
  14. # ● All combination of queries should be supported - not only those in the examples below.
  15. # ● Your solution should be generic so that if the inline DB is replaced with different
  16. #   information and a relevant query is sent, the script will still work.
  17. #
  18. # Note: For the sake of this task, you are NOT required to handle performance optimizations;
  19. # you can assume that we always do a full table scan
  20. #
  21. # Examples:
  22. # Print information of product “cd” using “$eq”:
  23. # python main.py "{\"name\": {\"\$eq\": \"cd\"}}"
  24. # Result: { "name": "cd", "qty": 5, "price": 3.99 }
  25. #
  26. # Print products with low quantity (below 10) and price above $2:
  27. # python main.py "{\"\$and\": [{\"price\": {\"\$gt\": 1}}, {\"qty\": {\"\$lt\": 10}}]}"
  28. # Result: { "name": "cd", "qty": 5, "price": 3.99 } { "name": "ij", "qty": 3, "price": 1.99 }
  29. #
  30. # An example showing $and and $or together
  31. # python main.py "{\"\$and\": [{\"name\": {\"\$eq\": \"ij\"}}, {\"\$or\": [{\"price\": {\"\$lt\": 2}}, {\"qty\": {\"\$gte\": 15}}]}]}"
  32. # Result: { "name": "ij", "qty": 3, "price": 1.99 }
  33.  
  34.  
  35. import sys
  36. import errno
  37. import unittest
  38. from unittest.case import expectedFailure
  39.  
  40. IS_DEBUG_MODE = True
  41.  
  42. inventory = [
  43.     { "name": "ab", "qty": 15, "price": 2.99 },
  44.     { "name": "cd", "qty": 5, "price": 3.99 },
  45.     { "name": "ij", "qty": 3, "price": 1.99 },
  46.     { "name": "xy", "qty": 20, "price": 1.99 }
  47.     ]
  48.  
  49.  
  50. def main():
  51.     if len(sys.argv) == 2:
  52.         input_query = parse_cmd_query_string(sys.argv[1])
  53.     else:
  54.         if IS_DEBUG_MODE:
  55.             input_query = {"name": {"$eq": "cd"}}
  56.         else:
  57.             print("invalid number of arguments")
  58.             sys.exit(errno.EINVAL)
  59.  
  60.     results = find(inventory, input_query)
  61.     for record in results:
  62.         print(record)
  63.     print("End.")
  64.  
  65.  
  66. def find(db, query):
  67.     executer = QueriesExecuter(db)
  68.     return executer.execute(query)
  69.  
  70.  
  71. def parse_cmd_query_string(query_string):
  72.     # TODO: Parse string
  73.     return {}
  74.  
  75.  
  76. class QueriesExecuter:
  77.     def __init__(self, data_source):
  78.         self.data_source = data_source
  79.  
  80.     def execute(self, query):
  81.         if not query or not isinstance(query, dict):
  82.             raise Exception(f"Illegal query: {query}")
  83.         results = []
  84.         for row in self.data_source:
  85.             if self._check_condition(row, query):
  86.                 results.append(row.copy())
  87.         return results
  88.  
  89.     def _check_condition(self, row, condition):
  90.         if not condition or not isinstance(condition, dict):
  91.             raise Exception(f"Illegal condition: {condition}")
  92.         if len(condition) != 1:
  93.             raise Exception(f"Illegal condition length: {condition}")
  94.  
  95.         column_name_or_saved_word, value = list(condition.items())[0]
  96.         if not column_name_or_saved_word:
  97.             raise Exception(f"Illegal name: {column_name_or_saved_word}")
  98.         if column_name_or_saved_word == "$and":
  99.             return self._check_and_conditions(row, value)
  100.         elif column_name_or_saved_word == "$or":
  101.             return self._check_or_conditions(row, value)
  102.         else:
  103.             return self._check_column_conditions(row, column_name_or_saved_word, value)
  104.  
  105.     def _check_and_conditions(self, row, conditions):
  106.         if not conditions or not isinstance(conditions, list):
  107.             raise Exception(f"Illegal conditions: {conditions}")
  108.         for condition in conditions:
  109.             if not self._check_condition(row, condition):
  110.                 return False
  111.         return True
  112.  
  113.     def _check_or_conditions(self, row, conditions):
  114.         if not conditions or not isinstance(conditions, list):
  115.             raise Exception(f"Illegal conditions: {conditions}")
  116.         for condition in conditions:
  117.             if self._check_condition(row, condition):
  118.                 return True
  119.         return False
  120.  
  121.     def _check_column_conditions(self, row, col_name, conditions):
  122.         if not row or not isinstance(row, dict):
  123.             raise Exception("Illegal row")
  124.         if not conditions or not isinstance(conditions, dict):
  125.             raise Exception("Illegal conditions")
  126.         if col_name not in row:
  127.             raise Exception(f"Column name not found in table: {col_name}")
  128.  
  129.         operators_switcher = {
  130.             "eq": self.operator_eq,
  131.             "ne": self.operator_ne,
  132.             "in": self.operator_in,
  133.             "nin": self.operator_nin,
  134.             "gt": self.operator_gt,
  135.             "gte": self.operator_gte,
  136.             "lt": self.operator_lt,
  137.             "lte": self.operator_lte
  138.         }
  139.  
  140.         for operator_name, second_val in conditions.items():
  141.             clean_operator_name = operator_name[1:]
  142.             if clean_operator_name not in operators_switcher:
  143.                 raise Exception(f"Illegal operator: {operator_name}")
  144.             first_val = row[col_name]
  145.             if not operators_switcher[clean_operator_name](first_val, second_val):
  146.                 return False
  147.         return True
  148.  
  149.     @classmethod
  150.     def operator_eq(cls, first, second):
  151.         return first == second
  152.  
  153.     @classmethod
  154.     def operator_ne(cls, first, second):
  155.         return not cls.operator_eq(first, second)
  156.  
  157.     @classmethod
  158.     def operator_in(cls, first, second):
  159.         return first in second
  160.  
  161.     @classmethod
  162.     def operator_nin(cls, first, second):
  163.         return not cls.operator_in(first, second)
  164.  
  165.     @classmethod
  166.     def operator_gt(cls, first, second):
  167.         return first > second
  168.  
  169.     @classmethod
  170.     def operator_gte(cls, first, second):
  171.         return first >= second
  172.  
  173.     @classmethod
  174.     def operator_lt(cls, first, second):
  175.         return first < second
  176.  
  177.     @classmethod
  178.     def operator_lte(cls, first, second):
  179.         return first <= second
  180.  
  181.  
  182. class Test_QueriesExecuter(unittest.TestCase):
  183.     db = [
  184.         { "name": "ab", "qty": 15, "price": 2.99 },
  185.         { "name": "cd", "qty": 5, "price": 3.99 },
  186.         { "name": "ij", "qty": 3, "price": 1.99 },
  187.         { "name": "xy", "qty": 20, "price": 1.99 }
  188.         ]
  189.  
  190.     def test_simple_query(self):
  191.         query = {"name": {"$eq": "cd"}}
  192.         result = QueriesExecuter(self.db).execute(query)
  193.         self.assertEqual(result, [
  194.             { "name": "cd", "qty": 5, "price": 3.99 },
  195.             ])
  196.  
  197.     def test_AND_query(self):
  198.         query = {"$and": [
  199.             {"price": {"$gt": 1}},
  200.             {"qty": {"$lt": 10}},
  201.             ]}
  202.         result = QueriesExecuter(self.db).execute(query)
  203.         self.assertEqual(result, [
  204.             { "name": "cd", "qty": 5, "price": 3.99 },
  205.             { "name": "ij", "qty": 3, "price": 1.99 },
  206.             ])
  207.  
  208.     def test_AND_with_inner_OR_query(self):
  209.         query = {
  210.             "$and": [
  211.                 {"name": {"$eq": "ij"}},
  212.                 {"$or": [
  213.                     {"price": {"$lt": 2}},
  214.                     {"qty": {"$gte": 15}},
  215.                     ]
  216.                 }
  217.             ]
  218.         }
  219.         result = QueriesExecuter(self.db).execute(query)
  220.         self.assertEqual(result, [
  221.             { "name": "ij", "qty": 3, "price": 1.99 },
  222.             ])
  223.  
  224.     def test_AND_with_inner_dict_instead_list(self):
  225.         query = {
  226.             "$and": {"name": {"$eq": "ij"}}
  227.             }
  228.         with self.assertRaises(Exception):
  229.             result = QueriesExecuter(self.db).execute(query)
  230.  
  231.  
  232. if __name__ == "__main__":
  233.     main()
  234.     # unittest.main()
Add Comment
Please, Sign In to add comment