25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

105 lines
3.1 KiB

  1. from enum import Enum
  2. from importlib import import_module
  3. from typing import Any, Callable, Dict, Union, get_type_hints
  4. from pypika import Query
  5. from pypika.queries import Column
  6. from pypika.terms import PseudoColumn
  7. import frappe
  8. from frappe.query_builder.terms import NamedParameterWrapper
  9. from .builder import MariaDB, Postgres
  10. class db_type_is(Enum):
  11. MARIADB = "mariadb"
  12. POSTGRES = "postgres"
  13. class ImportMapper:
  14. def __init__(self, func_map: Dict[db_type_is, Callable]) -> None:
  15. self.func_map = func_map
  16. def __call__(self, *args: Any, **kwds: Any) -> Callable:
  17. db = db_type_is(frappe.conf.db_type or "mariadb")
  18. return self.func_map[db](*args, **kwds)
  19. class BuilderIdentificationFailed(Exception):
  20. def __init__(self):
  21. super().__init__("Couldn't guess builder")
  22. def get_query_builder(type_of_db: str) -> Union[Postgres, MariaDB]:
  23. """[return the query builder object]
  24. Args:
  25. type_of_db (str): [string value of the db used]
  26. Returns:
  27. Query: [Query object]
  28. """
  29. db = db_type_is(type_of_db)
  30. picks = {db_type_is.MARIADB: MariaDB, db_type_is.POSTGRES: Postgres}
  31. return picks[db]
  32. def get_attr(method_string):
  33. modulename = '.'.join(method_string.split('.')[:-1])
  34. methodname = method_string.split('.')[-1]
  35. return getattr(import_module(modulename), methodname)
  36. def DocType(*args, **kwargs):
  37. return frappe.qb.DocType(*args, **kwargs)
  38. def patch_query_execute():
  39. """Patch the Query Builder with helper execute method
  40. This excludes the use of `frappe.db.sql` method while
  41. executing the query object
  42. """
  43. def execute_query(query, *args, **kwargs):
  44. query, params = prepare_query(query)
  45. return frappe.db.sql(query, params, *args, **kwargs) # nosemgrep
  46. def prepare_query(query):
  47. import inspect
  48. param_collector = NamedParameterWrapper()
  49. query = query.get_sql(param_wrapper=param_collector)
  50. if frappe.flags.in_safe_exec and not query.lower().strip().startswith("select"):
  51. callstack = inspect.stack()
  52. if len(callstack) >= 3 and ".py" in callstack[2].filename:
  53. # ignore any query builder methods called from python files
  54. # assumption is that those functions are whitelisted already.
  55. # since query objects are patched everywhere any query.run()
  56. # will have callstack like this:
  57. # frame0: this function prepare_query()
  58. # frame1: execute_query()
  59. # frame2: frame that called `query.run()`
  60. #
  61. # if frame2 is server script it wont have a filename and hence
  62. # it shouldn't be allowed.
  63. # ps. stack() returns `"<unknown>"` as filename.
  64. pass
  65. else:
  66. raise frappe.PermissionError('Only SELECT SQL allowed in scripting')
  67. return query, param_collector.get_parameters()
  68. query_class = get_attr(str(frappe.qb).split("'")[1])
  69. builder_class = get_type_hints(query_class._builder).get('return')
  70. if not builder_class:
  71. raise BuilderIdentificationFailed
  72. builder_class.run = execute_query
  73. builder_class.walk = prepare_query
  74. def patch_query_aggregation():
  75. """Patch aggregation functions to frappe.qb
  76. """
  77. from frappe.query_builder.functions import _avg, _max, _min, _sum
  78. frappe.qb.max = _max
  79. frappe.qb.min = _min
  80. frappe.qb.avg = _avg
  81. frappe.qb.sum = _sum