You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

database.py 28 KiB

10 years ago
Except and raise statement python 3 compatible style (#3216) * changes exception and raise statements to python 3 style * changes except statement to python 3 style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * adds six.reraise to fix python 3 style raise statements with traceback * fixes indentation
8 years ago
13 years ago
Except and raise statement python 3 compatible style (#3216) * changes exception and raise statements to python 3 style * changes except statement to python 3 style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * adds six.reraise to fix python 3 style raise statements with traceback * fixes indentation
8 years ago
10 years ago
13 years ago
13 years ago
13 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
Except and raise statement python 3 compatible style (#3216) * changes exception and raise statements to python 3 style * changes except statement to python 3 style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * changes except and raise statement to python 3 compatible style * adds six.reraise to fix python 3 style raise statements with traceback * fixes indentation
8 years ago
11 years ago
11 years ago
11 years ago
10 years ago
11 years ago
11 years ago
8 years ago
11 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904
  1. # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. # MIT License. See license.txt
  3. # Database Module
  4. # --------------------
  5. from __future__ import unicode_literals
  6. import warnings
  7. import datetime
  8. import frappe
  9. import frappe.defaults
  10. import frappe.async
  11. import re
  12. import frappe.model.meta
  13. from frappe.utils import now, get_datetime, cstr
  14. from frappe import _
  15. from frappe.model.utils.link_count import flush_local_link_count
  16. from frappe.utils.background_jobs import execute_job, get_queue
  17. # imports - compatibility imports
  18. from six import (
  19. integer_types,
  20. string_types,
  21. binary_type,
  22. text_type,
  23. iteritems
  24. )
  25. # imports - third-party imports
  26. from markdown2 import UnicodeWithAttrs
  27. from pymysql.times import TimeDelta
  28. from pymysql.constants import ER, FIELD_TYPE
  29. from pymysql.converters import conversions
  30. import pymysql
  31. class Database:
  32. """
  33. Open a database connection with the given parmeters, if use_default is True, use the
  34. login details from `conf.py`. This is called by the request handler and is accessible using
  35. the `db` global variable. the `sql` method is also global to run queries
  36. """
  37. def __init__(self, host=None, user=None, password=None, ac_name=None, use_default = 0):
  38. self.host = host or frappe.conf.db_host or 'localhost'
  39. self.user = user or frappe.conf.db_name
  40. self._conn = None
  41. if ac_name:
  42. self.user = self.get_db_login(ac_name) or frappe.conf.db_name
  43. if use_default:
  44. self.user = frappe.conf.db_name
  45. self.transaction_writes = 0
  46. self.auto_commit_on_many_writes = 0
  47. self.password = password or frappe.conf.db_password
  48. self.value_cache = {}
  49. def get_db_login(self, ac_name):
  50. return ac_name
  51. def connect(self):
  52. """Connects to a database as set in `site_config.json`."""
  53. warnings.filterwarnings('ignore', category=pymysql.Warning)
  54. usessl = 0
  55. if frappe.conf.db_ssl_ca and frappe.conf.db_ssl_cert and frappe.conf.db_ssl_key:
  56. usessl = 1
  57. self.ssl = {
  58. 'ca':frappe.conf.db_ssl_ca,
  59. 'cert':frappe.conf.db_ssl_cert,
  60. 'key':frappe.conf.db_ssl_key
  61. }
  62. conversions.update({
  63. FIELD_TYPE.NEWDECIMAL: float,
  64. FIELD_TYPE.DATETIME: get_datetime,
  65. TimeDelta: conversions[binary_type],
  66. UnicodeWithAttrs: conversions[text_type]
  67. })
  68. if usessl:
  69. self._conn = pymysql.connect(self.host, self.user or '', self.password or '',
  70. charset='utf8mb4', use_unicode = True, ssl=self.ssl, conv = conversions)
  71. else:
  72. self._conn = pymysql.connect(self.host, self.user or '', self.password or '',
  73. charset='utf8mb4', use_unicode = True, conv = conversions)
  74. # MYSQL_OPTION_MULTI_STATEMENTS_OFF = 1
  75. # # self._conn.set_server_option(MYSQL_OPTION_MULTI_STATEMENTS_OFF)
  76. self._cursor = self._conn.cursor()
  77. if self.user != 'root':
  78. self.use(self.user)
  79. frappe.local.rollback_observers = []
  80. def use(self, db_name):
  81. """`USE` db_name."""
  82. self._conn.select_db(db_name)
  83. self.cur_db_name = db_name
  84. def validate_query(self, q):
  85. """Throw exception for dangerous queries: `ALTER`, `DROP`, `TRUNCATE` if not `Administrator`."""
  86. cmd = q.strip().lower().split()[0]
  87. if cmd in ['alter', 'drop', 'truncate'] and frappe.session.user != 'Administrator':
  88. frappe.throw(_("Not permitted"), frappe.PermissionError)
  89. def sql(self, query, values=(), as_dict = 0, as_list = 0, formatted = 0,
  90. debug=0, ignore_ddl=0, as_utf8=0, auto_commit=0, update=None):
  91. """Execute a SQL query and fetch all rows.
  92. :param query: SQL query.
  93. :param values: List / dict of values to be escaped and substituted in the query.
  94. :param as_dict: Return as a dictionary.
  95. :param as_list: Always return as a list.
  96. :param formatted: Format values like date etc.
  97. :param debug: Print query and `EXPLAIN` in debug log.
  98. :param ignore_ddl: Catch exception if table, column missing.
  99. :param as_utf8: Encode values as UTF 8.
  100. :param auto_commit: Commit after executing the query.
  101. :param update: Update this dict to all rows (if returned `as_dict`).
  102. Examples:
  103. # return customer names as dicts
  104. frappe.db.sql("select name from tabCustomer", as_dict=True)
  105. # return names beginning with a
  106. frappe.db.sql("select name from tabCustomer where name like %s", "a%")
  107. # values as dict
  108. frappe.db.sql("select name from tabCustomer where name like %(name)s and owner=%(owner)s",
  109. {"name": "a%", "owner":"test@example.com"})
  110. """
  111. if not self._conn:
  112. self.connect()
  113. # in transaction validations
  114. self.check_transaction_status(query)
  115. # autocommit
  116. if auto_commit: self.commit()
  117. # execute
  118. try:
  119. if values!=():
  120. if isinstance(values, dict):
  121. values = dict(values)
  122. # MySQL-python==1.2.5 hack!
  123. if not isinstance(values, (dict, tuple, list)):
  124. values = (values,)
  125. if debug:
  126. try:
  127. self.explain_query(query, values)
  128. frappe.errprint(query % values)
  129. except TypeError:
  130. frappe.errprint([query, values])
  131. if (frappe.conf.get("logging") or False)==2:
  132. frappe.log("<<<< query")
  133. frappe.log(query)
  134. frappe.log("with values:")
  135. frappe.log(values)
  136. frappe.log(">>>>")
  137. self._cursor.execute(query, values)
  138. else:
  139. if debug:
  140. self.explain_query(query)
  141. frappe.errprint(query)
  142. if (frappe.conf.get("logging") or False)==2:
  143. frappe.log("<<<< query")
  144. frappe.log(query)
  145. frappe.log(">>>>")
  146. self._cursor.execute(query)
  147. except Exception as e:
  148. if ignore_ddl and e.args[0] in (ER.BAD_FIELD_ERROR, ER.NO_SUCH_TABLE,
  149. ER.CANT_DROP_FIELD_OR_KEY):
  150. pass
  151. # NOTE: causes deadlock
  152. # elif e.args[0]==2006:
  153. # # mysql has gone away
  154. # self.connect()
  155. # return self.sql(query=query, values=values,
  156. # as_dict=as_dict, as_list=as_list, formatted=formatted,
  157. # debug=debug, ignore_ddl=ignore_ddl, as_utf8=as_utf8,
  158. # auto_commit=auto_commit, update=update)
  159. else:
  160. raise
  161. if auto_commit: self.commit()
  162. # scrub output if required
  163. if as_dict:
  164. ret = self.fetch_as_dict(formatted, as_utf8)
  165. if update:
  166. for r in ret:
  167. r.update(update)
  168. return ret
  169. elif as_list:
  170. return self.convert_to_lists(self._cursor.fetchall(), formatted, as_utf8)
  171. elif as_utf8:
  172. return self.convert_to_lists(self._cursor.fetchall(), formatted, as_utf8)
  173. else:
  174. return self._cursor.fetchall()
  175. def explain_query(self, query, values=None):
  176. """Print `EXPLAIN` in error log."""
  177. try:
  178. frappe.errprint("--- query explain ---")
  179. if values is None:
  180. self._cursor.execute("explain " + query)
  181. else:
  182. self._cursor.execute("explain " + query, values)
  183. import json
  184. frappe.errprint(json.dumps(self.fetch_as_dict(), indent=1))
  185. frappe.errprint("--- query explain end ---")
  186. except:
  187. frappe.errprint("error in query explain")
  188. def sql_list(self, query, values=(), debug=False):
  189. """Return data as list of single elements (first column).
  190. Example:
  191. # doctypes = ["DocType", "DocField", "User", ...]
  192. doctypes = frappe.db.sql_list("select name from DocType")
  193. """
  194. return [r[0] for r in self.sql(query, values, debug=debug)]
  195. def sql_ddl(self, query, values=(), debug=False):
  196. """Commit and execute a query. DDL (Data Definition Language) queries that alter schema
  197. autocommit in MariaDB."""
  198. self.commit()
  199. self.sql(query, debug=debug)
  200. def check_transaction_status(self, query):
  201. """Raises exception if more than 20,000 `INSERT`, `UPDATE` queries are
  202. executed in one transaction. This is to ensure that writes are always flushed otherwise this
  203. could cause the system to hang."""
  204. if self.transaction_writes and \
  205. query and query.strip().split()[0].lower() in ['start', 'alter', 'drop', 'create', "begin", "truncate"]:
  206. raise Exception('This statement can cause implicit commit')
  207. if query and query.strip().lower() in ('commit', 'rollback'):
  208. self.transaction_writes = 0
  209. if query[:6].lower() in ('update', 'insert', 'delete'):
  210. self.transaction_writes += 1
  211. if self.transaction_writes > 200000:
  212. if self.auto_commit_on_many_writes:
  213. frappe.db.commit()
  214. else:
  215. frappe.throw(_("Too many writes in one request. Please send smaller requests"), frappe.ValidationError)
  216. def fetch_as_dict(self, formatted=0, as_utf8=0):
  217. """Internal. Converts results to dict."""
  218. result = self._cursor.fetchall()
  219. ret = []
  220. needs_formatting = self.needs_formatting(result, formatted)
  221. for r in result:
  222. row_dict = frappe._dict({})
  223. for i in range(len(r)):
  224. if needs_formatting:
  225. val = self.convert_to_simple_type(r[i], formatted)
  226. else:
  227. val = r[i]
  228. if as_utf8 and type(val) is text_type:
  229. val = val.encode('utf-8')
  230. row_dict[self._cursor.description[i][0]] = val
  231. ret.append(row_dict)
  232. return ret
  233. def needs_formatting(self, result, formatted):
  234. """Returns true if the first row in the result has a Date, Datetime, Long Int."""
  235. if result and result[0]:
  236. for v in result[0]:
  237. if isinstance(v, (datetime.date, datetime.timedelta, datetime.datetime, integer_types)):
  238. return True
  239. if formatted and isinstance(v, (int, float)):
  240. return True
  241. return False
  242. def get_description(self):
  243. """Returns result metadata."""
  244. return self._cursor.description
  245. def convert_to_simple_type(self, v, formatted=0):
  246. """Format date, time, longint values."""
  247. return v
  248. from frappe.utils import formatdate, fmt_money
  249. if isinstance(v, (datetime.date, datetime.timedelta, datetime.datetime, integer_types)):
  250. if isinstance(v, datetime.date):
  251. v = text_type(v)
  252. if formatted:
  253. v = formatdate(v)
  254. # time
  255. elif isinstance(v, (datetime.timedelta, datetime.datetime)):
  256. v = text_type(v)
  257. # long
  258. elif isinstance(v, integer_types):
  259. v=int(v)
  260. # convert to strings... (if formatted)
  261. if formatted:
  262. if isinstance(v, float):
  263. v=fmt_money(v)
  264. elif isinstance(v, int):
  265. v = text_type(v)
  266. return v
  267. def convert_to_lists(self, res, formatted=0, as_utf8=0):
  268. """Convert tuple output to lists (internal)."""
  269. nres = []
  270. needs_formatting = self.needs_formatting(res, formatted)
  271. for r in res:
  272. nr = []
  273. for c in r:
  274. if needs_formatting:
  275. val = self.convert_to_simple_type(c, formatted)
  276. else:
  277. val = c
  278. if as_utf8 and type(val) is text_type:
  279. val = val.encode('utf-8')
  280. nr.append(val)
  281. nres.append(nr)
  282. return nres
  283. def convert_to_utf8(self, res, formatted=0):
  284. """Encode result as UTF-8."""
  285. nres = []
  286. for r in res:
  287. nr = []
  288. for c in r:
  289. if type(c) is text_type:
  290. c = c.encode('utf-8')
  291. nr.append(self.convert_to_simple_type(c, formatted))
  292. nres.append(nr)
  293. return nres
  294. def build_conditions(self, filters):
  295. """Convert filters sent as dict, lists to SQL conditions. filter's key
  296. is passed by map function, build conditions like:
  297. * ifnull(`fieldname`, default_value) = %(fieldname)s
  298. * `fieldname` [=, !=, >, >=, <, <=] %(fieldname)s
  299. """
  300. conditions = []
  301. values = {}
  302. def _build_condition(key):
  303. """
  304. filter's key is passed by map function
  305. build conditions like:
  306. * ifnull(`fieldname`, default_value) = %(fieldname)s
  307. * `fieldname` [=, !=, >, >=, <, <=] %(fieldname)s
  308. """
  309. _operator = "="
  310. _rhs = " %(" + key + ")s"
  311. value = filters.get(key)
  312. values[key] = value
  313. if isinstance(value, (list, tuple)):
  314. # value is a tuble like ("!=", 0)
  315. _operator = value[0]
  316. values[key] = value[1]
  317. if isinstance(value[1], (tuple, list)):
  318. # value is a list in tuple ("in", ("A", "B"))
  319. inner_list = []
  320. for i, v in enumerate(value[1]):
  321. inner_key = "{0}_{1}".format(key, i)
  322. values[inner_key] = v
  323. inner_list.append("%({0})s".format(inner_key))
  324. _rhs = " ({0})".format(", ".join(inner_list))
  325. del values[key]
  326. if _operator not in ["=", "!=", ">", ">=", "<", "<=", "like", "in", "not in", "not like"]:
  327. _operator = "="
  328. if "[" in key:
  329. split_key = key.split("[")
  330. condition = "ifnull(`" + split_key[0] + "`, " + split_key[1][:-1] + ") " \
  331. + _operator + _rhs
  332. else:
  333. condition = "`" + key + "` " + _operator + _rhs
  334. conditions.append(condition)
  335. if isinstance(filters, string_types):
  336. filters = { "name": filters }
  337. for f in filters:
  338. _build_condition(f)
  339. return " and ".join(conditions), values
  340. def get(self, doctype, filters=None, as_dict=True, cache=False):
  341. """Returns `get_value` with fieldname='*'"""
  342. return self.get_value(doctype, filters, "*", as_dict=as_dict, cache=cache)
  343. def get_value(self, doctype, filters=None, fieldname="name", ignore=None, as_dict=False,
  344. debug=False, order_by=None, cache=False):
  345. """Returns a document property or list of properties.
  346. :param doctype: DocType name.
  347. :param filters: Filters like `{"x":"y"}` or name of the document. `None` if Single DocType.
  348. :param fieldname: Column name.
  349. :param ignore: Don't raise exception if table, column is missing.
  350. :param as_dict: Return values as dict.
  351. :param debug: Print query in error log.
  352. :param order_by: Column to order by
  353. Example:
  354. # return first customer starting with a
  355. frappe.db.get_value("Customer", {"name": ("like a%")})
  356. # return last login of **User** `test@example.com`
  357. frappe.db.get_value("User", "test@example.com", "last_login")
  358. last_login, last_ip = frappe.db.get_value("User", "test@example.com",
  359. ["last_login", "last_ip"])
  360. # returns default date_format
  361. frappe.db.get_value("System Settings", None, "date_format")
  362. """
  363. ret = self.get_values(doctype, filters, fieldname, ignore, as_dict, debug,
  364. order_by, cache=cache)
  365. return ((len(ret[0]) > 1 or as_dict) and ret[0] or ret[0][0]) if ret else None
  366. def get_values(self, doctype, filters=None, fieldname="name", ignore=None, as_dict=False,
  367. debug=False, order_by=None, update=None, cache=False):
  368. """Returns multiple document properties.
  369. :param doctype: DocType name.
  370. :param filters: Filters like `{"x":"y"}` or name of the document.
  371. :param fieldname: Column name.
  372. :param ignore: Don't raise exception if table, column is missing.
  373. :param as_dict: Return values as dict.
  374. :param debug: Print query in error log.
  375. :param order_by: Column to order by
  376. Example:
  377. # return first customer starting with a
  378. customers = frappe.db.get_values("Customer", {"name": ("like a%")})
  379. # return last login of **User** `test@example.com`
  380. user = frappe.db.get_values("User", "test@example.com", "*")[0]
  381. """
  382. out = None
  383. if cache and isinstance(filters, string_types) and \
  384. (doctype, filters, fieldname) in self.value_cache:
  385. return self.value_cache[(doctype, filters, fieldname)]
  386. if not order_by: order_by = 'modified desc'
  387. if isinstance(filters, list):
  388. out = self._get_value_for_many_names(doctype, filters, fieldname, debug=debug)
  389. else:
  390. fields = fieldname
  391. if fieldname!="*":
  392. if isinstance(fieldname, string_types):
  393. fields = [fieldname]
  394. else:
  395. fields = fieldname
  396. if (filters is not None) and (filters!=doctype or doctype=="DocType"):
  397. try:
  398. out = self._get_values_from_table(fields, filters, doctype, as_dict, debug, order_by, update)
  399. except Exception as e:
  400. if ignore and e.args[0] in (1146, 1054):
  401. # table or column not found, return None
  402. out = None
  403. elif (not ignore) and e.args[0]==1146:
  404. # table not found, look in singles
  405. out = self.get_values_from_single(fields, filters, doctype, as_dict, debug, update)
  406. else:
  407. raise
  408. else:
  409. out = self.get_values_from_single(fields, filters, doctype, as_dict, debug, update)
  410. if cache and isinstance(filters, string_types):
  411. self.value_cache[(doctype, filters, fieldname)] = out
  412. return out
  413. def get_values_from_single(self, fields, filters, doctype, as_dict=False, debug=False, update=None):
  414. """Get values from `tabSingles` (Single DocTypes) (internal).
  415. :param fields: List of fields,
  416. :param filters: Filters (dict).
  417. :param doctype: DocType name.
  418. """
  419. # TODO
  420. # if not frappe.model.meta.is_single(doctype):
  421. # raise frappe.DoesNotExistError("DocType", doctype)
  422. if fields=="*" or isinstance(filters, dict):
  423. # check if single doc matches with filters
  424. values = self.get_singles_dict(doctype)
  425. if isinstance(filters, dict):
  426. for key, value in filters.items():
  427. if values.get(key) != value:
  428. return []
  429. if as_dict:
  430. return values and [values] or []
  431. if isinstance(fields, list):
  432. return [map(lambda d: values.get(d), fields)]
  433. else:
  434. r = self.sql("""select field, value
  435. from tabSingles where field in (%s) and doctype=%s""" \
  436. % (', '.join(['%s'] * len(fields)), '%s'),
  437. tuple(fields) + (doctype,), as_dict=False, debug=debug)
  438. if as_dict:
  439. if r:
  440. r = frappe._dict(r)
  441. if update:
  442. r.update(update)
  443. return [r]
  444. else:
  445. return []
  446. else:
  447. return r and [[i[1] for i in r]] or []
  448. def get_singles_dict(self, doctype):
  449. """Get Single DocType as dict.
  450. :param doctype: DocType of the single object whose value is requested
  451. Example:
  452. # Get coulmn and value of the single doctype Accounts Settings
  453. account_settings = frappe.db.get_singles_dict("Accounts Settings")
  454. """
  455. return frappe._dict(self.sql("""select field, value from
  456. tabSingles where doctype=%s""", doctype))
  457. def get_all(self, *args, **kwargs):
  458. return frappe.get_all(*args, **kwargs)
  459. def get_list(self, *args, **kwargs):
  460. return frappe.get_list(*args, **kwargs)
  461. def get_single_value(self, doctype, fieldname, cache=False):
  462. """Get property of Single DocType. Cache locally by default
  463. :param doctype: DocType of the single object whose value is requested
  464. :param fieldname: `fieldname` of the property whose value is requested
  465. Example:
  466. # Get the default value of the company from the Global Defaults doctype.
  467. company = frappe.db.get_single_value('Global Defaults', 'default_company')
  468. """
  469. value = self.value_cache.setdefault(doctype, {}).get(fieldname)
  470. if value:
  471. return value
  472. val = self.sql("""select value from
  473. tabSingles where doctype=%s and field=%s""", (doctype, fieldname))
  474. val = val[0][0] if val else None
  475. if val=="0" or val=="1":
  476. # check type
  477. val = int(val)
  478. self.value_cache[doctype][fieldname] = val
  479. return val
  480. def get_singles_value(self, *args, **kwargs):
  481. """Alias for get_single_value"""
  482. return self.get_single_value(*args, **kwargs)
  483. def _get_values_from_table(self, fields, filters, doctype, as_dict, debug, order_by=None, update=None):
  484. fl = []
  485. if isinstance(fields, (list, tuple)):
  486. for f in fields:
  487. if "(" in f or " as " in f: # function
  488. fl.append(f)
  489. else:
  490. fl.append("`" + f + "`")
  491. fl = ", ".join(fl)
  492. else:
  493. fl = fields
  494. if fields=="*":
  495. as_dict = True
  496. conditions, values = self.build_conditions(filters)
  497. order_by = ("order by " + order_by) if order_by else ""
  498. r = self.sql("select {0} from `tab{1}` {2} {3} {4}"
  499. .format(fl, doctype, "where" if conditions else "", conditions, order_by), values,
  500. as_dict=as_dict, debug=debug, update=update)
  501. return r
  502. def _get_value_for_many_names(self, doctype, names, field, debug=False):
  503. names = list(filter(None, names))
  504. if names:
  505. return dict(self.sql("select name, `%s` from `tab%s` where name in (%s)" \
  506. % (field, doctype, ", ".join(["%s"]*len(names))), names, debug=debug))
  507. else:
  508. return {}
  509. def update(self, *args, **kwargs):
  510. """Update multiple values. Alias for `set_value`."""
  511. return self.set_value(*args, **kwargs)
  512. def set_value(self, dt, dn, field, val, modified=None, modified_by=None,
  513. update_modified=True, debug=False):
  514. """Set a single value in the database, do not call the ORM triggers
  515. but update the modified timestamp (unless specified not to).
  516. **Warning:** this function will not call Document events and should be avoided in normal cases.
  517. :param dt: DocType name.
  518. :param dn: Document name.
  519. :param field: Property / field name or dictionary of values to be updated
  520. :param value: Value to be updated.
  521. :param modified: Use this as the `modified` timestamp.
  522. :param modified_by: Set this user as `modified_by`.
  523. :param update_modified: default True. Set as false, if you don't want to update the timestamp.
  524. :param debug: Print the query in the developer / js console.
  525. """
  526. if not modified:
  527. modified = now()
  528. if not modified_by:
  529. modified_by = frappe.session.user
  530. to_update = {}
  531. if update_modified:
  532. to_update = {"modified": modified, "modified_by": modified_by}
  533. if isinstance(field, dict):
  534. to_update.update(field)
  535. else:
  536. to_update.update({field: val})
  537. if dn and dt!=dn:
  538. # with table
  539. conditions, values = self.build_conditions(dn)
  540. values.update(to_update)
  541. set_values = []
  542. for key in to_update:
  543. set_values.append('`{0}`=%({0})s'.format(key))
  544. self.sql("""update `tab{0}`
  545. set {1} where {2}""".format(dt, ', '.join(set_values), conditions),
  546. values, debug=debug)
  547. else:
  548. # for singles
  549. keys = to_update.keys()
  550. self.sql('''
  551. delete from tabSingles
  552. where field in ({0}) and
  553. doctype=%s'''.format(', '.join(['%s']*len(keys))),
  554. list(keys) + [dt], debug=debug)
  555. for key, value in iteritems(to_update):
  556. self.sql('''insert into tabSingles(doctype, field, value) values (%s, %s, %s)''',
  557. (dt, key, value), debug=debug)
  558. if dt in self.value_cache:
  559. del self.value_cache[dt]
  560. def set(self, doc, field, val):
  561. """Set value in document. **Avoid**"""
  562. doc.db_set(field, val)
  563. def touch(self, doctype, docname):
  564. """Update the modified timestamp of this document."""
  565. from frappe.utils import now
  566. modified = now()
  567. frappe.db.sql("""update `tab{doctype}` set `modified`=%s
  568. where name=%s""".format(doctype=doctype), (modified, docname))
  569. return modified
  570. def set_temp(self, value):
  571. """Set a temperory value and return a key."""
  572. key = frappe.generate_hash()
  573. frappe.cache().hset("temp", key, value)
  574. return key
  575. def get_temp(self, key):
  576. """Return the temperory value and delete it."""
  577. return frappe.cache().hget("temp", key)
  578. def set_global(self, key, val, user='__global'):
  579. """Save a global key value. Global values will be automatically set if they match fieldname."""
  580. self.set_default(key, val, user)
  581. def get_global(self, key, user='__global'):
  582. """Returns a global key value."""
  583. return self.get_default(key, user)
  584. def set_default(self, key, val, parent="__default", parenttype=None):
  585. """Sets a global / user default value."""
  586. frappe.defaults.set_default(key, val, parent, parenttype)
  587. def add_default(self, key, val, parent="__default", parenttype=None):
  588. """Append a default value for a key, there can be multiple default values for a particular key."""
  589. frappe.defaults.add_default(key, val, parent, parenttype)
  590. def get_default(self, key, parent="__default"):
  591. """Returns default value as a list if multiple or single"""
  592. d = self.get_defaults(key, parent)
  593. return isinstance(d, list) and d[0] or d
  594. def get_defaults(self, key=None, parent="__default"):
  595. """Get all defaults"""
  596. if key:
  597. defaults = frappe.defaults.get_defaults(parent)
  598. d = defaults.get(key, None)
  599. if(not d and key != frappe.scrub(key)):
  600. d = defaults.get(frappe.scrub(key), None)
  601. return d
  602. else:
  603. return frappe.defaults.get_defaults(parent)
  604. def begin(self):
  605. self.sql("start transaction")
  606. def commit(self):
  607. """Commit current transaction. Calls SQL `COMMIT`."""
  608. self.sql("commit")
  609. frappe.local.rollback_observers = []
  610. self.flush_realtime_log()
  611. enqueue_jobs_after_commit()
  612. flush_local_link_count()
  613. def flush_realtime_log(self):
  614. for args in frappe.local.realtime_log:
  615. frappe.async.emit_via_redis(*args)
  616. frappe.local.realtime_log = []
  617. def rollback(self):
  618. """`ROLLBACK` current transaction."""
  619. self.sql("rollback")
  620. self.begin()
  621. for obj in frappe.local.rollback_observers:
  622. if hasattr(obj, "on_rollback"):
  623. obj.on_rollback()
  624. frappe.local.rollback_observers = []
  625. def field_exists(self, dt, fn):
  626. """Return true of field exists."""
  627. return self.sql("select name from tabDocField where fieldname=%s and parent=%s", (dt, fn))
  628. def table_exists(self, tablename):
  629. """Returns True if table exists."""
  630. return ("tab" + tablename) in self.get_tables()
  631. def get_tables(self):
  632. return [d[0] for d in self.sql("show tables")]
  633. def a_row_exists(self, doctype):
  634. """Returns True if atleast one row exists."""
  635. return self.sql("select name from `tab{doctype}` limit 1".format(doctype=doctype))
  636. def exists(self, dt, dn=None):
  637. """Returns true if document exists.
  638. :param dt: DocType name.
  639. :param dn: Document name or filter dict."""
  640. if isinstance(dt, string_types):
  641. if dt!="DocType" and dt==dn:
  642. return True # single always exists (!)
  643. try:
  644. return self.get_value(dt, dn, "name")
  645. except:
  646. return None
  647. elif isinstance(dt, dict) and dt.get('doctype'):
  648. try:
  649. conditions = []
  650. for d in dt:
  651. if d == 'doctype': continue
  652. conditions.append('`%s` = "%s"' % (d, cstr(dt[d]).replace('"', '\"')))
  653. return self.sql('select name from `tab%s` where %s' % \
  654. (dt['doctype'], " and ".join(conditions)))
  655. except:
  656. return None
  657. def count(self, dt, filters=None, debug=False):
  658. """Returns `COUNT(*)` for given DocType and filters."""
  659. if filters:
  660. conditions, filters = self.build_conditions(filters)
  661. return frappe.db.sql("""select count(*)
  662. from `tab%s` where %s""" % (dt, conditions), filters, debug=debug)[0][0]
  663. else:
  664. return frappe.db.sql("""select count(*)
  665. from `tab%s`""" % (dt,))[0][0]
  666. def get_creation_count(self, doctype, minutes):
  667. """Get count of records created in the last x minutes"""
  668. from frappe.utils import now_datetime
  669. from dateutil.relativedelta import relativedelta
  670. return frappe.db.sql("""select count(name) from `tab{doctype}`
  671. where creation >= %s""".format(doctype=doctype),
  672. now_datetime() - relativedelta(minutes=minutes))[0][0]
  673. def get_db_table_columns(self, table):
  674. """Returns list of column names from given table."""
  675. return [r[0] for r in self.sql("DESC `%s`" % table)]
  676. def get_table_columns(self, doctype):
  677. """Returns list of column names from given doctype."""
  678. return self.get_db_table_columns('tab' + doctype)
  679. def has_column(self, doctype, column):
  680. """Returns True if column exists in database."""
  681. return column in self.get_table_columns(doctype)
  682. def add_index(self, doctype, fields, index_name=None):
  683. """Creates an index with given fields if not already created.
  684. Index name will be `fieldname1_fieldname2_index`"""
  685. if not index_name:
  686. index_name = "_".join(fields) + "_index"
  687. # remove index length if present e.g. (10) from index name
  688. index_name = re.sub(r"\s*\([^)]+\)\s*", r"", index_name)
  689. if not frappe.db.sql("""show index from `tab%s` where Key_name="%s" """ % (doctype, index_name)):
  690. frappe.db.commit()
  691. frappe.db.sql("""alter table `tab%s`
  692. add index `%s`(%s)""" % (doctype, index_name, ", ".join(fields)))
  693. def add_unique(self, doctype, fields, constraint_name=None):
  694. if isinstance(fields, string_types):
  695. fields = [fields]
  696. if not constraint_name:
  697. constraint_name = "unique_" + "_".join(fields)
  698. if not frappe.db.sql("""select CONSTRAINT_NAME from information_schema.TABLE_CONSTRAINTS
  699. where table_name=%s and constraint_type='UNIQUE' and CONSTRAINT_NAME=%s""",
  700. ('tab' + doctype, constraint_name)):
  701. frappe.db.commit()
  702. frappe.db.sql("""alter table `tab%s`
  703. add unique `%s`(%s)""" % (doctype, constraint_name, ", ".join(fields)))
  704. def get_system_setting(self, key):
  705. def _load_system_settings():
  706. return self.get_singles_dict("System Settings")
  707. return frappe.cache().get_value("system_settings", _load_system_settings).get(key)
  708. def close(self):
  709. """Close database connection."""
  710. if self._conn:
  711. # self._cursor.close()
  712. self._conn.close()
  713. self._cursor = None
  714. self._conn = None
  715. def escape(self, s, percent=True):
  716. """Excape quotes and percent in given string."""
  717. if isinstance(s, text_type):
  718. s = (s or "").encode("utf-8")
  719. s = text_type(pymysql.escape_string(s), "utf-8").replace("`", "\\`")
  720. # NOTE separating % escape, because % escape should only be done when using LIKE operator
  721. # or when you use python format string to generate query that already has a %s
  722. # for example: sql("select name from `tabUser` where name=%s and {0}".format(conditions), something)
  723. # defaulting it to True, as this is the most frequent use case
  724. # ideally we shouldn't have to use ESCAPE and strive to pass values via the values argument of sql
  725. if percent:
  726. s = s.replace("%", "%%")
  727. return s
  728. def enqueue_jobs_after_commit():
  729. if frappe.flags.enqueue_after_commit and len(frappe.flags.enqueue_after_commit) > 0:
  730. for job in frappe.flags.enqueue_after_commit:
  731. q = get_queue(job.get("queue"), async=job.get("async"))
  732. q.enqueue_call(execute_job, timeout=job.get("timeout"),
  733. kwargs=job.get("queue_args"))
  734. frappe.flags.enqueue_after_commit = []