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

1186 lines
35 KiB

  1. # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. # License: MIT. See LICENSE
  3. # Database Module
  4. # --------------------
  5. import datetime
  6. import random
  7. import re
  8. import string
  9. from contextlib import contextmanager
  10. from time import time
  11. from typing import Dict, List, Optional, Tuple, Union
  12. from pypika.terms import Criterion, NullValue, PseudoColumn
  13. import frappe
  14. import frappe.defaults
  15. import frappe.model.meta
  16. from frappe import _
  17. from frappe.model.utils.link_count import flush_local_link_count
  18. from frappe.query_builder.functions import Count
  19. from frappe.query_builder.utils import DocType
  20. from frappe.utils import cast, get_datetime, getdate, now, sbool
  21. from .query import Query
  22. class Database(object):
  23. """
  24. Open a database connection with the given parmeters, if use_default is True, use the
  25. login details from `conf.py`. This is called by the request handler and is accessible using
  26. the `db` global variable. the `sql` method is also global to run queries
  27. """
  28. VARCHAR_LEN = 140
  29. MAX_COLUMN_LENGTH = 64
  30. OPTIONAL_COLUMNS = ["_user_tags", "_comments", "_assign", "_liked_by"]
  31. DEFAULT_SHORTCUTS = ['_Login', '__user', '_Full Name', 'Today', '__today', "now", "Now"]
  32. STANDARD_VARCHAR_COLUMNS = ('name', 'owner', 'modified_by')
  33. DEFAULT_COLUMNS = ['name', 'creation', 'modified', 'modified_by', 'owner', 'docstatus', 'idx']
  34. CHILD_TABLE_COLUMNS = ('parent', 'parenttype', 'parentfield')
  35. MAX_WRITES_PER_TRANSACTION = 200_000
  36. class InvalidColumnName(frappe.ValidationError): pass
  37. def __init__(self, host=None, user=None, password=None, ac_name=None, use_default=0, port=None):
  38. self.setup_type_map()
  39. self.host = host or frappe.conf.db_host or '127.0.0.1'
  40. self.port = port or frappe.conf.db_port or ''
  41. self.user = user or frappe.conf.db_name
  42. self.db_name = frappe.conf.db_name
  43. self._conn = None
  44. if ac_name:
  45. self.user = ac_name or frappe.conf.db_name
  46. if use_default:
  47. self.user = frappe.conf.db_name
  48. self.transaction_writes = 0
  49. self.auto_commit_on_many_writes = 0
  50. self.password = password or frappe.conf.db_password
  51. self.value_cache = {}
  52. self.query = Query()
  53. def setup_type_map(self):
  54. pass
  55. def connect(self):
  56. """Connects to a database as set in `site_config.json`."""
  57. self.cur_db_name = self.user
  58. self._conn = self.get_connection()
  59. self._cursor = self._conn.cursor()
  60. frappe.local.rollback_observers = []
  61. def use(self, db_name):
  62. """`USE` db_name."""
  63. self._conn.select_db(db_name)
  64. def get_connection(self):
  65. pass
  66. def get_database_size(self):
  67. pass
  68. def sql(self, query, values=(), as_dict = 0, as_list = 0, formatted = 0,
  69. debug=0, ignore_ddl=0, as_utf8=0, auto_commit=0, update=None,
  70. explain=False, run=True, pluck=False):
  71. """Execute a SQL query and fetch all rows.
  72. :param query: SQL query.
  73. :param values: List / dict of values to be escaped and substituted in the query.
  74. :param as_dict: Return as a dictionary.
  75. :param as_list: Always return as a list.
  76. :param formatted: Format values like date etc.
  77. :param debug: Print query and `EXPLAIN` in debug log.
  78. :param ignore_ddl: Catch exception if table, column missing.
  79. :param as_utf8: Encode values as UTF 8.
  80. :param auto_commit: Commit after executing the query.
  81. :param update: Update this dict to all rows (if returned `as_dict`).
  82. :param run: Returns query without executing it if False.
  83. Examples:
  84. # return customer names as dicts
  85. frappe.db.sql("select name from tabCustomer", as_dict=True)
  86. # return names beginning with a
  87. frappe.db.sql("select name from tabCustomer where name like %s", "a%")
  88. # values as dict
  89. frappe.db.sql("select name from tabCustomer where name like %(name)s and owner=%(owner)s",
  90. {"name": "a%", "owner":"test@example.com"})
  91. """
  92. query = str(query)
  93. if not run:
  94. return query
  95. # remove whitespace / indentation from start and end of query
  96. query = query.strip()
  97. if re.search(r'ifnull\(', query, flags=re.IGNORECASE):
  98. # replaces ifnull in query with coalesce
  99. query = re.sub(r'ifnull\(', 'coalesce(', query, flags=re.IGNORECASE)
  100. if not self._conn:
  101. self.connect()
  102. # in transaction validations
  103. self.check_transaction_status(query)
  104. self.clear_db_table_cache(query)
  105. # autocommit
  106. if auto_commit: self.commit()
  107. # execute
  108. try:
  109. if debug:
  110. time_start = time()
  111. self.log_query(query, values, debug, explain)
  112. if values!=():
  113. # MySQL-python==1.2.5 hack!
  114. if not isinstance(values, (dict, tuple, list)):
  115. values = (values,)
  116. self._cursor.execute(query, values)
  117. if frappe.flags.in_migrate:
  118. self.log_touched_tables(query, values)
  119. else:
  120. self._cursor.execute(query)
  121. if frappe.flags.in_migrate:
  122. self.log_touched_tables(query)
  123. if debug:
  124. time_end = time()
  125. frappe.errprint(("Execution time: {0} sec").format(round(time_end - time_start, 2)))
  126. except Exception as e:
  127. if self.is_syntax_error(e):
  128. # only for mariadb
  129. frappe.errprint('Syntax error in query:')
  130. frappe.errprint(query)
  131. elif self.is_deadlocked(e):
  132. raise frappe.QueryDeadlockError(e)
  133. elif self.is_timedout(e):
  134. raise frappe.QueryTimeoutError(e)
  135. elif frappe.conf.db_type == 'postgres':
  136. # TODO: added temporarily
  137. print(e)
  138. raise
  139. if ignore_ddl and (self.is_missing_column(e) or self.is_table_missing(e) or self.cant_drop_field_or_key(e)):
  140. pass
  141. else:
  142. raise
  143. if auto_commit: self.commit()
  144. if not self._cursor.description:
  145. return ()
  146. if pluck:
  147. return [r[0] for r in self._cursor.fetchall()]
  148. # scrub output if required
  149. if as_dict:
  150. ret = self.fetch_as_dict(formatted, as_utf8)
  151. if update:
  152. for r in ret:
  153. r.update(update)
  154. return ret
  155. elif as_list:
  156. return self.convert_to_lists(self._cursor.fetchall(), formatted, as_utf8)
  157. elif as_utf8:
  158. return self.convert_to_lists(self._cursor.fetchall(), formatted, as_utf8)
  159. else:
  160. return self._cursor.fetchall()
  161. def log_query(self, query, values, debug, explain):
  162. # for debugging in tests
  163. if frappe.conf.get('allow_tests') and frappe.cache().get_value('flag_print_sql'):
  164. print(self.mogrify(query, values))
  165. # debug
  166. if debug:
  167. if explain and query.strip().lower().startswith('select'):
  168. self.explain_query(query, values)
  169. frappe.errprint(self.mogrify(query, values))
  170. # info
  171. if (frappe.conf.get("logging") or False)==2:
  172. frappe.log("<<<< query")
  173. frappe.log(self.mogrify(query, values))
  174. frappe.log(">>>>")
  175. def mogrify(self, query, values):
  176. '''build the query string with values'''
  177. if not values:
  178. return query
  179. else:
  180. try:
  181. return self._cursor.mogrify(query, values)
  182. except: # noqa: E722
  183. return (query, values)
  184. def explain_query(self, query, values=None):
  185. """Print `EXPLAIN` in error log."""
  186. try:
  187. frappe.errprint("--- query explain ---")
  188. if values is None:
  189. self._cursor.execute("explain " + query)
  190. else:
  191. self._cursor.execute("explain " + query, values)
  192. import json
  193. frappe.errprint(json.dumps(self.fetch_as_dict(), indent=1))
  194. frappe.errprint("--- query explain end ---")
  195. except Exception:
  196. frappe.errprint("error in query explain")
  197. def sql_list(self, query, values=(), debug=False, **kwargs):
  198. """Return data as list of single elements (first column).
  199. Example:
  200. # doctypes = ["DocType", "DocField", "User", ...]
  201. doctypes = frappe.db.sql_list("select name from DocType")
  202. """
  203. return [r[0] for r in self.sql(query, values, **kwargs, debug=debug)]
  204. def sql_ddl(self, query, values=(), debug=False):
  205. """Commit and execute a query. DDL (Data Definition Language) queries that alter schema
  206. autocommit in MariaDB."""
  207. self.commit()
  208. self.sql(query, debug=debug)
  209. def check_transaction_status(self, query):
  210. """Raises exception if more than 20,000 `INSERT`, `UPDATE` queries are
  211. executed in one transaction. This is to ensure that writes are always flushed otherwise this
  212. could cause the system to hang."""
  213. self.check_implicit_commit(query)
  214. if query and query.strip().lower() in ('commit', 'rollback'):
  215. self.transaction_writes = 0
  216. if query[:6].lower() in ('update', 'insert', 'delete'):
  217. self.transaction_writes += 1
  218. if self.transaction_writes > self.MAX_WRITES_PER_TRANSACTION:
  219. if self.auto_commit_on_many_writes:
  220. self.commit()
  221. else:
  222. msg = "<br><br>" + _("Too many changes to database in single action.") + "<br>"
  223. msg += _("The changes have been reverted.") + "<br>"
  224. raise frappe.TooManyWritesError(msg)
  225. def check_implicit_commit(self, query):
  226. if self.transaction_writes and \
  227. query and query.strip().split()[0].lower() in ['start', 'alter', 'drop', 'create', "begin", "truncate"]:
  228. raise Exception('This statement can cause implicit commit')
  229. def fetch_as_dict(self, formatted=0, as_utf8=0):
  230. """Internal. Converts results to dict."""
  231. result = self._cursor.fetchall()
  232. ret = []
  233. if result:
  234. keys = [column[0] for column in self._cursor.description]
  235. for r in result:
  236. values = []
  237. for value in r:
  238. if as_utf8 and isinstance(value, str):
  239. value = value.encode('utf-8')
  240. values.append(value)
  241. ret.append(frappe._dict(zip(keys, values)))
  242. return ret
  243. @staticmethod
  244. def clear_db_table_cache(query):
  245. if query and query.strip().split()[0].lower() in {'drop', 'create'}:
  246. frappe.cache().delete_key('db_tables')
  247. @staticmethod
  248. def needs_formatting(result, formatted):
  249. """Returns true if the first row in the result has a Date, Datetime, Long Int."""
  250. if result and result[0]:
  251. for v in result[0]:
  252. if isinstance(v, (datetime.date, datetime.timedelta, datetime.datetime, int)):
  253. return True
  254. if formatted and isinstance(v, (int, float)):
  255. return True
  256. return False
  257. def get_description(self):
  258. """Returns result metadata."""
  259. return self._cursor.description
  260. @staticmethod
  261. def convert_to_lists(res, formatted=0, as_utf8=0):
  262. """Convert tuple output to lists (internal)."""
  263. nres = []
  264. for r in res:
  265. nr = []
  266. for val in r:
  267. if as_utf8 and isinstance(val, str):
  268. val = val.encode('utf-8')
  269. nr.append(val)
  270. nres.append(nr)
  271. return nres
  272. def get(self, doctype, filters=None, as_dict=True, cache=False):
  273. """Returns `get_value` with fieldname='*'"""
  274. return self.get_value(doctype, filters, "*", as_dict=as_dict, cache=cache)
  275. def get_value(
  276. self,
  277. doctype,
  278. filters=None,
  279. fieldname="name",
  280. ignore=None,
  281. as_dict=False,
  282. debug=False,
  283. order_by="KEEP_DEFAULT_ORDERING",
  284. cache=False,
  285. for_update=False,
  286. *,
  287. run=True,
  288. pluck=False,
  289. distinct=False,
  290. ):
  291. """Returns a document property or list of properties.
  292. :param doctype: DocType name.
  293. :param filters: Filters like `{"x":"y"}` or name of the document. `None` if Single DocType.
  294. :param fieldname: Column name.
  295. :param ignore: Don't raise exception if table, column is missing.
  296. :param as_dict: Return values as dict.
  297. :param debug: Print query in error log.
  298. :param order_by: Column to order by
  299. Example:
  300. # return first customer starting with a
  301. frappe.db.get_value("Customer", {"name": ("like a%")})
  302. # return last login of **User** `test@example.com`
  303. frappe.db.get_value("User", "test@example.com", "last_login")
  304. last_login, last_ip = frappe.db.get_value("User", "test@example.com",
  305. ["last_login", "last_ip"])
  306. # returns default date_format
  307. frappe.db.get_value("System Settings", None, "date_format")
  308. """
  309. result = self.get_values(doctype, filters, fieldname, ignore, as_dict, debug,
  310. order_by, cache=cache, for_update=for_update, run=run, pluck=pluck, distinct=distinct, limit=1)
  311. if not run:
  312. return result
  313. if not result:
  314. return None
  315. row = result[0]
  316. if len(row) > 1 or as_dict:
  317. return row
  318. else:
  319. # single field is requested, send it without wrapping in containers
  320. return row[0]
  321. def get_values(self, doctype, filters=None, fieldname="name", ignore=None, as_dict=False,
  322. debug=False, order_by="KEEP_DEFAULT_ORDERING", update=None, cache=False, for_update=False,
  323. *, run=True, pluck=False, distinct=False, limit=None):
  324. """Returns multiple document properties.
  325. :param doctype: DocType name.
  326. :param filters: Filters like `{"x":"y"}` or name of the document.
  327. :param fieldname: Column name.
  328. :param ignore: Don't raise exception if table, column is missing.
  329. :param as_dict: Return values as dict.
  330. :param debug: Print query in error log.
  331. :param order_by: Column to order by,
  332. :param distinct: Get Distinct results.
  333. Example:
  334. # return first customer starting with a
  335. customers = frappe.db.get_values("Customer", {"name": ("like a%")})
  336. # return last login of **User** `test@example.com`
  337. user = frappe.db.get_values("User", "test@example.com", "*")[0]
  338. """
  339. out = None
  340. if cache and isinstance(filters, str) and \
  341. (doctype, filters, fieldname) in self.value_cache:
  342. return self.value_cache[(doctype, filters, fieldname)]
  343. if distinct:
  344. order_by = None
  345. if isinstance(filters, list):
  346. out = self._get_value_for_many_names(
  347. doctype=doctype,
  348. names=filters,
  349. field=fieldname,
  350. order_by=order_by,
  351. debug=debug,
  352. run=run,
  353. pluck=pluck,
  354. distinct=distinct,
  355. limit=limit,
  356. )
  357. else:
  358. fields = fieldname
  359. if fieldname != "*":
  360. if isinstance(fieldname, str):
  361. fields = [fieldname]
  362. if (filters is not None) and (filters!=doctype or doctype=="DocType"):
  363. try:
  364. if order_by:
  365. order_by = "modified" if order_by == "KEEP_DEFAULT_ORDERING" else order_by
  366. out = self._get_values_from_table(
  367. fields=fields,
  368. filters=filters,
  369. doctype=doctype,
  370. as_dict=as_dict,
  371. debug=debug,
  372. order_by=order_by,
  373. update=update,
  374. for_update=for_update,
  375. run=run,
  376. pluck=pluck,
  377. distinct=distinct,
  378. limit=limit,
  379. )
  380. except Exception as e:
  381. if ignore and (frappe.db.is_missing_column(e) or frappe.db.is_table_missing(e)):
  382. # table or column not found, return None
  383. out = None
  384. elif (not ignore) and frappe.db.is_table_missing(e):
  385. # table not found, look in singles
  386. out = self.get_values_from_single(fields, filters, doctype, as_dict, debug, update, run=run, distinct=distinct)
  387. else:
  388. raise
  389. else:
  390. out = self.get_values_from_single(fields, filters, doctype, as_dict, debug, update, run=run, pluck=pluck, distinct=distinct)
  391. if cache and isinstance(filters, str):
  392. self.value_cache[(doctype, filters, fieldname)] = out
  393. return out
  394. def get_values_from_single(
  395. self,
  396. fields,
  397. filters,
  398. doctype,
  399. as_dict=False,
  400. debug=False,
  401. update=None,
  402. *,
  403. run=True,
  404. pluck=False,
  405. distinct=False,
  406. ):
  407. """Get values from `tabSingles` (Single DocTypes) (internal).
  408. :param fields: List of fields,
  409. :param filters: Filters (dict).
  410. :param doctype: DocType name.
  411. """
  412. # TODO
  413. # if not frappe.model.meta.is_single(doctype):
  414. # raise frappe.DoesNotExistError("DocType", doctype)
  415. if fields=="*" or isinstance(filters, dict):
  416. # check if single doc matches with filters
  417. values = self.get_singles_dict(doctype)
  418. if isinstance(filters, dict):
  419. for key, value in filters.items():
  420. if values.get(key) != value:
  421. return []
  422. if as_dict:
  423. return values and [values] or []
  424. if isinstance(fields, list):
  425. return [map(values.get, fields)]
  426. else:
  427. r = self.query.get_sql(
  428. "Singles",
  429. filters={"field": ("in", tuple(fields)), "doctype": doctype},
  430. fields=["field", "value"],
  431. distinct=distinct,
  432. ).run(pluck=pluck, debug=debug, as_dict=False)
  433. if not run:
  434. return r
  435. if as_dict:
  436. if r:
  437. r = frappe._dict(r)
  438. if update:
  439. r.update(update)
  440. return [r]
  441. else:
  442. return []
  443. else:
  444. return r and [[i[1] for i in r]] or []
  445. def get_singles_dict(self, doctype, debug = False):
  446. """Get Single DocType as dict.
  447. :param doctype: DocType of the single object whose value is requested
  448. Example:
  449. # Get coulmn and value of the single doctype Accounts Settings
  450. account_settings = frappe.db.get_singles_dict("Accounts Settings")
  451. """
  452. result = self.query.get_sql(
  453. "Singles", filters={"doctype": doctype}, fields=["field", "value"]
  454. ).run()
  455. dict_ = frappe._dict(result)
  456. return dict_
  457. @staticmethod
  458. def get_all(*args, **kwargs):
  459. return frappe.get_all(*args, **kwargs)
  460. @staticmethod
  461. def get_list(*args, **kwargs):
  462. return frappe.get_list(*args, **kwargs)
  463. def set_single_value(self, doctype: str, fieldname: Union[str, Dict], value: Optional[Union[str, int]] = None, *args, **kwargs):
  464. """Set field value of Single DocType.
  465. :param doctype: DocType of the single object
  466. :param fieldname: `fieldname` of the property
  467. :param value: `value` of the property
  468. Example:
  469. # Update the `deny_multiple_sessions` field in System Settings DocType.
  470. company = frappe.db.set_single_value("System Settings", "deny_multiple_sessions", True)
  471. """
  472. return self.set_value(doctype, doctype, fieldname, value, *args, **kwargs)
  473. def get_single_value(self, doctype, fieldname, cache=True):
  474. """Get property of Single DocType. Cache locally by default
  475. :param doctype: DocType of the single object whose value is requested
  476. :param fieldname: `fieldname` of the property whose value is requested
  477. Example:
  478. # Get the default value of the company from the Global Defaults doctype.
  479. company = frappe.db.get_single_value('Global Defaults', 'default_company')
  480. """
  481. if doctype not in self.value_cache:
  482. self.value_cache[doctype] = {}
  483. if cache and fieldname in self.value_cache[doctype]:
  484. return self.value_cache[doctype][fieldname]
  485. val = self.query.get_sql(
  486. table="Singles",
  487. filters={"doctype": doctype, "field": fieldname},
  488. fields="value",
  489. ).run()
  490. val = val[0][0] if val else None
  491. df = frappe.get_meta(doctype).get_field(fieldname)
  492. if not df:
  493. frappe.throw(_('Invalid field name: {0}').format(frappe.bold(fieldname)), self.InvalidColumnName)
  494. val = cast(df.fieldtype, val)
  495. self.value_cache[doctype][fieldname] = val
  496. return val
  497. def get_singles_value(self, *args, **kwargs):
  498. """Alias for get_single_value"""
  499. return self.get_single_value(*args, **kwargs)
  500. def _get_values_from_table(
  501. self,
  502. fields,
  503. filters,
  504. doctype,
  505. as_dict,
  506. *,
  507. debug=False,
  508. order_by=None,
  509. update=None,
  510. for_update=False,
  511. run=True,
  512. pluck=False,
  513. distinct=False,
  514. limit=None,
  515. ):
  516. field_objects = []
  517. if not isinstance(fields, Criterion):
  518. for field in fields:
  519. if "(" in str(field) or " as " in str(field):
  520. field_objects.append(PseudoColumn(field))
  521. else:
  522. field_objects.append(field)
  523. query = self.query.get_sql(
  524. table=doctype,
  525. filters=filters,
  526. orderby=order_by,
  527. for_update=for_update,
  528. field_objects=field_objects,
  529. fields=fields,
  530. distinct=distinct,
  531. limit=limit,
  532. )
  533. if (
  534. fields == "*"
  535. and not isinstance(fields, (list, tuple))
  536. and not isinstance(fields, Criterion)
  537. ):
  538. as_dict = True
  539. r = self.sql(
  540. query, as_dict=as_dict, debug=debug, update=update, run=run, pluck=pluck
  541. )
  542. return r
  543. def _get_value_for_many_names(self, doctype, names, field, order_by, *, debug=False, run=True, pluck=False, distinct=False, limit=None):
  544. names = list(filter(None, names))
  545. if names:
  546. return self.get_all(
  547. doctype,
  548. fields=field,
  549. filters=names,
  550. order_by=order_by,
  551. pluck=pluck,
  552. debug=debug,
  553. as_list=1,
  554. run=run,
  555. distinct=distinct,
  556. limit_page_length=limit
  557. )
  558. else:
  559. return {}
  560. def update(self, *args, **kwargs):
  561. """Update multiple values. Alias for `set_value`."""
  562. return self.set_value(*args, **kwargs)
  563. def set_value(self, dt, dn, field, val=None, modified=None, modified_by=None,
  564. update_modified=True, debug=False, for_update=True):
  565. """Set a single value in the database, do not call the ORM triggers
  566. but update the modified timestamp (unless specified not to).
  567. **Warning:** this function will not call Document events and should be avoided in normal cases.
  568. :param dt: DocType name.
  569. :param dn: Document name.
  570. :param field: Property / field name or dictionary of values to be updated
  571. :param value: Value to be updated.
  572. :param modified: Use this as the `modified` timestamp.
  573. :param modified_by: Set this user as `modified_by`.
  574. :param update_modified: default True. Set as false, if you don't want to update the timestamp.
  575. :param debug: Print the query in the developer / js console.
  576. :param for_update: Will add a row-level lock to the value that is being set so that it can be released on commit.
  577. """
  578. is_single_doctype = not (dn and dt != dn)
  579. to_update = field if isinstance(field, dict) else {field: val}
  580. if update_modified:
  581. modified = modified or now()
  582. modified_by = modified_by or frappe.session.user
  583. to_update.update({"modified": modified, "modified_by": modified_by})
  584. if is_single_doctype:
  585. frappe.db.delete(
  586. "Singles",
  587. filters={"field": ("in", tuple(to_update)), "doctype": dt}, debug=debug
  588. )
  589. singles_data = ((dt, key, sbool(value)) for key, value in to_update.items())
  590. query = (
  591. frappe.qb.into("Singles")
  592. .columns("doctype", "field", "value")
  593. .insert(*singles_data)
  594. ).run(debug=debug)
  595. frappe.clear_document_cache(dt, dt)
  596. else:
  597. table = DocType(dt)
  598. if for_update:
  599. docnames = tuple(
  600. self.get_values(dt, dn, "name", debug=debug, for_update=for_update, pluck=True)
  601. ) or (NullValue(),)
  602. query = frappe.qb.update(table).where(table.name.isin(docnames))
  603. for docname in docnames:
  604. frappe.clear_document_cache(dt, docname)
  605. else:
  606. query = self.query.build_conditions(table=dt, filters=dn, update=True)
  607. # TODO: Fix this; doesn't work rn - gavin@frappe.io
  608. # frappe.cache().hdel_keys(dt, "document_cache")
  609. # Workaround: clear all document caches
  610. frappe.cache().delete_value('document_cache')
  611. for column, value in to_update.items():
  612. query = query.set(column, value)
  613. query.run(debug=debug)
  614. if dt in self.value_cache:
  615. del self.value_cache[dt]
  616. @staticmethod
  617. def set(doc, field, val):
  618. """Set value in document. **Avoid**"""
  619. doc.db_set(field, val)
  620. def touch(self, doctype, docname):
  621. """Update the modified timestamp of this document."""
  622. modified = now()
  623. self.sql("""update `tab{doctype}` set `modified`=%s
  624. where name=%s""".format(doctype=doctype), (modified, docname))
  625. return modified
  626. @staticmethod
  627. def set_temp(value):
  628. """Set a temperory value and return a key."""
  629. key = frappe.generate_hash()
  630. frappe.cache().hset("temp", key, value)
  631. return key
  632. @staticmethod
  633. def get_temp(key):
  634. """Return the temperory value and delete it."""
  635. return frappe.cache().hget("temp", key)
  636. def set_global(self, key, val, user='__global'):
  637. """Save a global key value. Global values will be automatically set if they match fieldname."""
  638. self.set_default(key, val, user)
  639. def get_global(self, key, user='__global'):
  640. """Returns a global key value."""
  641. return self.get_default(key, user)
  642. def get_default(self, key, parent="__default"):
  643. """Returns default value as a list if multiple or single"""
  644. d = self.get_defaults(key, parent)
  645. return isinstance(d, list) and d[0] or d
  646. @staticmethod
  647. def set_default(key, val, parent="__default", parenttype=None):
  648. """Sets a global / user default value."""
  649. frappe.defaults.set_default(key, val, parent, parenttype)
  650. @staticmethod
  651. def add_default(key, val, parent="__default", parenttype=None):
  652. """Append a default value for a key, there can be multiple default values for a particular key."""
  653. frappe.defaults.add_default(key, val, parent, parenttype)
  654. @staticmethod
  655. def get_defaults(key=None, parent="__default"):
  656. """Get all defaults"""
  657. if key:
  658. defaults = frappe.defaults.get_defaults(parent)
  659. d = defaults.get(key, None)
  660. if(not d and key != frappe.scrub(key)):
  661. d = defaults.get(frappe.scrub(key), None)
  662. return d
  663. else:
  664. return frappe.defaults.get_defaults(parent)
  665. def begin(self):
  666. self.sql("START TRANSACTION")
  667. def commit(self):
  668. """Commit current transaction. Calls SQL `COMMIT`."""
  669. for method in frappe.local.before_commit:
  670. frappe.call(method[0], *(method[1] or []), **(method[2] or {}))
  671. self.sql("commit")
  672. frappe.local.rollback_observers = []
  673. self.flush_realtime_log()
  674. enqueue_jobs_after_commit()
  675. flush_local_link_count()
  676. def add_before_commit(self, method, args=None, kwargs=None):
  677. frappe.local.before_commit.append([method, args, kwargs])
  678. @staticmethod
  679. def flush_realtime_log():
  680. for args in frappe.local.realtime_log:
  681. frappe.realtime.emit_via_redis(*args)
  682. frappe.local.realtime_log = []
  683. def savepoint(self, save_point):
  684. """Savepoints work as a nested transaction.
  685. Changes can be undone to a save point by doing frappe.db.rollback(save_point)
  686. Note: rollback watchers can not work with save points.
  687. so only changes to database are undone when rolling back to a savepoint.
  688. Avoid using savepoints when writing to filesystem."""
  689. self.sql(f"savepoint {save_point}")
  690. def release_savepoint(self, save_point):
  691. self.sql(f"release savepoint {save_point}")
  692. def rollback(self, *, save_point=None):
  693. """`ROLLBACK` current transaction. Optionally rollback to a known save_point."""
  694. if save_point:
  695. self.sql(f"rollback to savepoint {save_point}")
  696. else:
  697. self.sql("rollback")
  698. self.begin()
  699. for obj in frappe.local.rollback_observers:
  700. if hasattr(obj, "on_rollback"):
  701. obj.on_rollback()
  702. frappe.local.rollback_observers = []
  703. def field_exists(self, dt, fn):
  704. """Return true of field exists."""
  705. return self.exists('DocField', {
  706. 'fieldname': fn,
  707. 'parent': dt
  708. })
  709. def table_exists(self, doctype, cached=True):
  710. """Returns True if table for given doctype exists."""
  711. return ("tab" + doctype) in self.get_tables(cached=cached)
  712. def has_table(self, doctype):
  713. return self.table_exists(doctype)
  714. def get_tables(self, cached=True):
  715. tables = frappe.cache().get_value('db_tables')
  716. if not tables or not cached:
  717. table_rows = self.sql("""
  718. SELECT table_name
  719. FROM information_schema.tables
  720. WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
  721. """)
  722. tables = {d[0] for d in table_rows}
  723. frappe.cache().set_value('db_tables', tables)
  724. return tables
  725. def a_row_exists(self, doctype):
  726. """Returns True if atleast one row exists."""
  727. return self.sql("select name from `tab{doctype}` limit 1".format(doctype=doctype))
  728. def exists(self, dt, dn=None, cache=False):
  729. """Return the document name of a matching document, or None.
  730. Note: `cache` only works if `dt` and `dn` are of type `str`.
  731. ## Examples
  732. Pass doctype and docname (only in this case we can cache the result)
  733. ```
  734. exists("User", "jane@example.org", cache=True)
  735. ```
  736. Pass a dict of filters including the `"doctype"` key:
  737. ```
  738. exists({"doctype": "User", "full_name": "Jane Doe"})
  739. ```
  740. Pass the doctype and a dict of filters:
  741. ```
  742. exists("User", {"full_name": "Jane Doe"})
  743. ```
  744. """
  745. if dt != "DocType" and dt == dn:
  746. # single always exists (!)
  747. return dn
  748. if isinstance(dt, dict):
  749. dt = dt.copy() # don't modify the original dict
  750. dt, dn = dt.pop("doctype"), dt
  751. return self.get_value(dt, dn, ignore=True, cache=cache)
  752. def count(self, dt, filters=None, debug=False, cache=False):
  753. """Returns `COUNT(*)` for given DocType and filters."""
  754. if cache and not filters:
  755. cache_count = frappe.cache().get_value('doctype:count:{}'.format(dt))
  756. if cache_count is not None:
  757. return cache_count
  758. query = self.query.get_sql(table=dt, filters=filters, fields=Count("*"))
  759. if filters:
  760. count = self.sql(query, debug=debug)[0][0]
  761. return count
  762. else:
  763. count = self.sql(query, debug=debug)[0][0]
  764. if cache:
  765. frappe.cache().set_value('doctype:count:{}'.format(dt), count, expires_in_sec = 86400)
  766. return count
  767. @staticmethod
  768. def format_date(date):
  769. return getdate(date).strftime("%Y-%m-%d")
  770. @staticmethod
  771. def format_datetime(datetime):
  772. if not datetime:
  773. return '0001-01-01 00:00:00.000000'
  774. if isinstance(datetime, str):
  775. if ':' not in datetime:
  776. datetime = datetime + ' 00:00:00.000000'
  777. else:
  778. datetime = datetime.strftime("%Y-%m-%d %H:%M:%S.%f")
  779. return datetime
  780. def get_creation_count(self, doctype, minutes):
  781. """Get count of records created in the last x minutes"""
  782. from frappe.utils import now_datetime
  783. from dateutil.relativedelta import relativedelta
  784. return self.sql("""select count(name) from `tab{doctype}`
  785. where creation >= %s""".format(doctype=doctype),
  786. now_datetime() - relativedelta(minutes=minutes))[0][0]
  787. def get_db_table_columns(self, table):
  788. """Returns list of column names from given table."""
  789. columns = frappe.cache().hget('table_columns', table)
  790. if columns is None:
  791. columns = [r[0] for r in self.sql('''
  792. select column_name
  793. from information_schema.columns
  794. where table_name = %s ''', table)]
  795. if columns:
  796. frappe.cache().hset('table_columns', table, columns)
  797. return columns
  798. def get_table_columns(self, doctype):
  799. """Returns list of column names from given doctype."""
  800. columns = self.get_db_table_columns('tab' + doctype)
  801. if not columns:
  802. raise self.TableMissingError('DocType', doctype)
  803. return columns
  804. def has_column(self, doctype, column):
  805. """Returns True if column exists in database."""
  806. return column in self.get_table_columns(doctype)
  807. def get_column_type(self, doctype, column):
  808. return self.sql('''SELECT column_type FROM INFORMATION_SCHEMA.COLUMNS
  809. WHERE table_name = 'tab{0}' AND column_name = '{1}' '''.format(doctype, column))[0][0]
  810. def has_index(self, table_name, index_name):
  811. raise NotImplementedError
  812. def add_index(self, doctype, fields, index_name=None):
  813. raise NotImplementedError
  814. def add_unique(self, doctype, fields, constraint_name=None):
  815. raise NotImplementedError
  816. @staticmethod
  817. def get_index_name(fields):
  818. index_name = "_".join(fields) + "_index"
  819. # remove index length if present e.g. (10) from index name
  820. index_name = re.sub(r"\s*\([^)]+\)\s*", r"", index_name)
  821. return index_name
  822. def get_system_setting(self, key):
  823. def _load_system_settings():
  824. return self.get_singles_dict("System Settings")
  825. return frappe.cache().get_value("system_settings", _load_system_settings).get(key)
  826. def close(self):
  827. """Close database connection."""
  828. if self._conn:
  829. # self._cursor.close()
  830. self._conn.close()
  831. self._cursor = None
  832. self._conn = None
  833. @staticmethod
  834. def escape(s, percent=True):
  835. """Excape quotes and percent in given string."""
  836. # implemented in specific class
  837. raise NotImplementedError
  838. @staticmethod
  839. def is_column_missing(e):
  840. return frappe.db.is_missing_column(e)
  841. def get_descendants(self, doctype, name):
  842. '''Return descendants of the current record'''
  843. node_location_indexes = self.get_value(doctype, name, ('lft', 'rgt'))
  844. if node_location_indexes:
  845. lft, rgt = node_location_indexes
  846. return self.sql_list('''select name from `tab{doctype}`
  847. where lft > {lft} and rgt < {rgt}'''.format(doctype=doctype, lft=lft, rgt=rgt))
  848. else:
  849. # when document does not exist
  850. return []
  851. def is_missing_table_or_column(self, e):
  852. return self.is_missing_column(e) or self.is_table_missing(e)
  853. def multisql(self, sql_dict, values=(), **kwargs):
  854. current_dialect = frappe.db.db_type or 'mariadb'
  855. query = sql_dict.get(current_dialect)
  856. return self.sql(query, values, **kwargs)
  857. def delete(self, doctype: str, filters: Union[Dict, List] = None, debug=False, **kwargs):
  858. """Delete rows from a table in site which match the passed filters. This
  859. does trigger DocType hooks. Simply runs a DELETE query in the database.
  860. Doctype name can be passed directly, it will be pre-pended with `tab`.
  861. """
  862. values = ()
  863. filters = filters or kwargs.get("conditions")
  864. query = self.query.build_conditions(table=doctype, filters=filters).delete()
  865. if "debug" not in kwargs:
  866. kwargs["debug"] = debug
  867. return self.sql(query, values, **kwargs)
  868. def truncate(self, doctype: str):
  869. """Truncate a table in the database. This runs a DDL command `TRUNCATE TABLE`.
  870. This cannot be rolled back.
  871. Doctype name can be passed directly, it will be pre-pended with `tab`.
  872. """
  873. table = doctype if doctype.startswith("__") else f"tab{doctype}"
  874. return self.sql_ddl(f"truncate `{table}`")
  875. def clear_table(self, doctype):
  876. return self.truncate(doctype)
  877. def get_last_created(self, doctype):
  878. last_record = self.get_all(doctype, ('creation'), limit=1, order_by='creation desc')
  879. if last_record:
  880. return get_datetime(last_record[0].creation)
  881. else:
  882. return None
  883. def log_touched_tables(self, query, values=None):
  884. if values:
  885. query = frappe.safe_decode(self._cursor.mogrify(query, values))
  886. if query.strip().lower().split()[0] in ('insert', 'delete', 'update', 'alter', 'drop', 'rename'):
  887. # single_word_regex is designed to match following patterns
  888. # `tabXxx`, tabXxx and "tabXxx"
  889. # multi_word_regex is designed to match following patterns
  890. # `tabXxx Xxx` and "tabXxx Xxx"
  891. # ([`"]?) Captures " or ` at the begining of the table name (if provided)
  892. # \1 matches the first captured group (quote character) at the end of the table name
  893. # multi word table name must have surrounding quotes.
  894. # (tab([A-Z]\w+)( [A-Z]\w+)*) Captures table names that start with "tab"
  895. # and are continued with multiple words that start with a captital letter
  896. # e.g. 'tabXxx' or 'tabXxx Xxx' or 'tabXxx Xxx Xxx' and so on
  897. single_word_regex = r'([`"]?)(tab([A-Z]\w+))\1'
  898. multi_word_regex = r'([`"])(tab([A-Z]\w+)( [A-Z]\w+)+)\1'
  899. tables = []
  900. for regex in (single_word_regex, multi_word_regex):
  901. tables += [groups[1] for groups in re.findall(regex, query)]
  902. if frappe.flags.touched_tables is None:
  903. frappe.flags.touched_tables = set()
  904. frappe.flags.touched_tables.update(tables)
  905. def bulk_insert(self, doctype, fields, values, ignore_duplicates=False):
  906. """
  907. Insert multiple records at a time
  908. :param doctype: Doctype name
  909. :param fields: list of fields
  910. :params values: list of list of values
  911. """
  912. insert_list = []
  913. fields = ", ".join("`"+field+"`" for field in fields)
  914. for idx, value in enumerate(values):
  915. insert_list.append(tuple(value))
  916. if idx and (idx%10000 == 0 or idx < len(values)-1):
  917. self.sql("""INSERT {ignore_duplicates} INTO `tab{doctype}` ({fields}) VALUES {values}""".format(
  918. ignore_duplicates="IGNORE" if ignore_duplicates else "",
  919. doctype=doctype,
  920. fields=fields,
  921. values=", ".join(['%s'] * len(insert_list))
  922. ), tuple(insert_list))
  923. insert_list = []
  924. def enqueue_jobs_after_commit():
  925. from frappe.utils.background_jobs import execute_job, get_queue
  926. if frappe.flags.enqueue_after_commit and len(frappe.flags.enqueue_after_commit) > 0:
  927. for job in frappe.flags.enqueue_after_commit:
  928. q = get_queue(job.get("queue"), is_async=job.get("is_async"))
  929. q.enqueue_call(execute_job, timeout=job.get("timeout"),
  930. kwargs=job.get("queue_args"))
  931. frappe.flags.enqueue_after_commit = []
  932. @contextmanager
  933. def savepoint(catch: Union[type, Tuple[type, ...]] = Exception):
  934. """ Wrapper for wrapping blocks of DB operations in a savepoint.
  935. as contextmanager:
  936. for doc in docs:
  937. with savepoint(catch=DuplicateError):
  938. doc.insert()
  939. as decorator (wraps FULL function call):
  940. @savepoint(catch=DuplicateError)
  941. def process_doc(doc):
  942. doc.insert()
  943. """
  944. try:
  945. savepoint = ''.join(random.sample(string.ascii_lowercase, 10))
  946. frappe.db.savepoint(savepoint)
  947. yield # control back to calling function
  948. except catch:
  949. frappe.db.rollback(save_point=savepoint)
  950. else:
  951. frappe.db.release_savepoint(savepoint)