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.
 
 
 
 
 
 

710 lines
22 KiB

  1. # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
  2. # MIT License. See license.txt
  3. # Database Module
  4. # --------------------
  5. from __future__ import unicode_literals
  6. import MySQLdb
  7. from markdown2 import UnicodeWithAttrs
  8. import warnings
  9. import datetime
  10. import frappe
  11. import frappe.defaults
  12. import re
  13. import frappe.model.meta
  14. from frappe.utils import now, get_datetime, cstr
  15. from frappe import _
  16. class Database:
  17. """
  18. Open a database connection with the given parmeters, if use_default is True, use the
  19. login details from `conf.py`. This is called by the request handler and is accessible using
  20. the `db` global variable. the `sql` method is also global to run queries
  21. """
  22. def __init__(self, host=None, user=None, password=None, ac_name=None, use_default = 0):
  23. self.host = host or frappe.conf.db_host or 'localhost'
  24. self.user = user or frappe.conf.db_name
  25. self._conn = None
  26. if ac_name:
  27. self.user = self.get_db_login(ac_name) or frappe.conf.db_name
  28. if use_default:
  29. self.user = frappe.conf.db_name
  30. self.transaction_writes = 0
  31. self.auto_commit_on_many_writes = 0
  32. self.password = password or frappe.conf.db_password
  33. def get_db_login(self, ac_name):
  34. return ac_name
  35. def connect(self):
  36. """Connects to a database as set in `site_config.json`."""
  37. warnings.filterwarnings('ignore', category=MySQLdb.Warning)
  38. self._conn = MySQLdb.connect(user=self.user, host=self.host, passwd=self.password,
  39. use_unicode=True, charset='utf8')
  40. self._conn.converter[246]=float
  41. self._conn.converter[12]=get_datetime
  42. self._conn.encoders[UnicodeWithAttrs] = self._conn.encoders[unicode]
  43. self._cursor = self._conn.cursor()
  44. if self.user != 'root':
  45. self.use(self.user)
  46. frappe.local.rollback_observers = []
  47. def use(self, db_name):
  48. """`USE` db_name."""
  49. self._conn.select_db(db_name)
  50. self.cur_db_name = db_name
  51. def validate_query(self, q):
  52. """Throw exception for dangerous queries: `ALTER`, `DROP`, `TRUNCATE` if not `Administrator`."""
  53. cmd = q.strip().lower().split()[0]
  54. if cmd in ['alter', 'drop', 'truncate'] and frappe.user.name != 'Administrator':
  55. frappe.throw(_("Not permitted"), frappe.PermissionError)
  56. def sql(self, query, values=(), as_dict = 0, as_list = 0, formatted = 0,
  57. debug=0, ignore_ddl=0, as_utf8=0, auto_commit=0, update=None):
  58. """Execute a SQL query and fetch all rows.
  59. :param query: SQL query.
  60. :param values: List / dict of values to be escaped and substituted in the query.
  61. :param as_dict: Return as a dictionary.
  62. :param as_list: Always return as a list.
  63. :param formatted: Format values like date etc.
  64. :param debug: Print query and `EXPLAIN` in debug log.
  65. :param ignore_ddl: Catch exception if table, column missing.
  66. :param as_utf8: Encode values as UTF 8.
  67. :param auto_commit: Commit after executing the query.
  68. :param update: Update this dict to all rows (if returned `as_dict`).
  69. Examples:
  70. # return customer names as dicts
  71. frappe.db.sql("select name from tabCustomer", as_dict=True)
  72. # return names beginning with a
  73. frappe.db.sql("select name from tabCustomer where name like %s", "a%")
  74. # values as dict
  75. frappe.db.sql("select name from tabCustomer where name like %(name)s and owner=%(owner)s",
  76. {"name": "a%", "owner":"test@example.com"})
  77. """
  78. if not self._conn:
  79. self.connect()
  80. # in transaction validations
  81. self.check_transaction_status(query)
  82. # autocommit
  83. if auto_commit: self.commit()
  84. # execute
  85. try:
  86. if values!=():
  87. if isinstance(values, dict):
  88. values = dict(values)
  89. # MySQL-python==1.2.5 hack!
  90. if not isinstance(values, (dict, tuple, list)):
  91. values = (values,)
  92. if debug:
  93. try:
  94. self.explain_query(query, values)
  95. frappe.errprint(query % values)
  96. except TypeError:
  97. frappe.errprint([query, values])
  98. if (frappe.conf.get("logging") or False)==2:
  99. frappe.log("<<<< query")
  100. frappe.log(query)
  101. frappe.log("with values:")
  102. frappe.log(values)
  103. frappe.log(">>>>")
  104. self._cursor.execute(query, values)
  105. else:
  106. if debug:
  107. self.explain_query(query)
  108. frappe.errprint(query)
  109. if (frappe.conf.get("logging") or False)==2:
  110. frappe.log("<<<< query")
  111. frappe.log(query)
  112. frappe.log(">>>>")
  113. self._cursor.execute(query)
  114. except Exception, e:
  115. # ignore data definition errors
  116. if ignore_ddl and e.args[0] in (1146,1054,1091):
  117. pass
  118. else:
  119. raise
  120. if auto_commit: self.commit()
  121. # scrub output if required
  122. if as_dict:
  123. ret = self.fetch_as_dict(formatted, as_utf8)
  124. if update:
  125. for r in ret:
  126. r.update(update)
  127. return ret
  128. elif as_list:
  129. return self.convert_to_lists(self._cursor.fetchall(), formatted, as_utf8)
  130. elif as_utf8:
  131. return self.convert_to_lists(self._cursor.fetchall(), formatted, as_utf8)
  132. else:
  133. return self._cursor.fetchall()
  134. def explain_query(self, query, values=None):
  135. """Print `EXPLAIN` in error log."""
  136. try:
  137. frappe.errprint("--- query explain ---")
  138. if values is None:
  139. self._cursor.execute("explain " + query)
  140. else:
  141. self._cursor.execute("explain " + query, values)
  142. import json
  143. frappe.errprint(json.dumps(self.fetch_as_dict(), indent=1))
  144. frappe.errprint("--- query explain end ---")
  145. except:
  146. frappe.errprint("error in query explain")
  147. def sql_list(self, query, values=(), debug=False):
  148. """Return data as list of single elements (first column).
  149. Example:
  150. # doctypes = ["DocType", "DocField", "User", ...]
  151. doctypes = frappe.db.sql_list("select name from DocType")
  152. """
  153. return [r[0] for r in self.sql(query, values, debug=debug)]
  154. def sql_ddl(self, query, values=()):
  155. """Commit and execute a query. DDL (Data Definition Language) queries that alter schema
  156. autocommit in MariaDB."""
  157. self.commit()
  158. self.sql(query)
  159. def check_transaction_status(self, query):
  160. """Raises exception if more than 20,000 `INSERT`, `UPDATE` queries are
  161. executed in one transaction. This is to ensure that writes are always flushed otherwise this
  162. could cause the system to hang."""
  163. if self.transaction_writes and \
  164. query and query.strip().split()[0].lower() in ['start', 'alter', 'drop', 'create', "begin"]:
  165. raise Exception, 'This statement can cause implicit commit'
  166. if query and query.strip().lower() in ('commit', 'rollback'):
  167. self.transaction_writes = 0
  168. if query[:6].lower() in ['update', 'insert']:
  169. self.transaction_writes += 1
  170. if self.transaction_writes > 20000:
  171. if self.auto_commit_on_many_writes:
  172. frappe.db.commit()
  173. else:
  174. frappe.throw(_("Too many writes in one request. Please send smaller requests"), frappe.ValidationError)
  175. def fetch_as_dict(self, formatted=0, as_utf8=0):
  176. """Internal. Converts results to dict."""
  177. result = self._cursor.fetchall()
  178. ret = []
  179. needs_formatting = self.needs_formatting(result, formatted)
  180. for r in result:
  181. row_dict = frappe._dict({})
  182. for i in range(len(r)):
  183. if needs_formatting:
  184. val = self.convert_to_simple_type(r[i], formatted)
  185. else:
  186. val = r[i]
  187. if as_utf8 and type(val) is unicode:
  188. val = val.encode('utf-8')
  189. row_dict[self._cursor.description[i][0]] = val
  190. ret.append(row_dict)
  191. return ret
  192. def needs_formatting(self, result, formatted):
  193. """Returns true if the first row in the result has a Date, Datetime, Long Int."""
  194. if result and result[0]:
  195. for v in result[0]:
  196. if isinstance(v, (datetime.date, datetime.timedelta, datetime.datetime, long)):
  197. return True
  198. if formatted and isinstance(v, (int, float)):
  199. return True
  200. return False
  201. def get_description(self):
  202. """Returns result metadata."""
  203. return self._cursor.description
  204. def convert_to_simple_type(self, v, formatted=0):
  205. """Format date, time, longint values."""
  206. from frappe.utils import formatdate, fmt_money
  207. if isinstance(v, (datetime.date, datetime.timedelta, datetime.datetime, long)):
  208. if isinstance(v, datetime.date):
  209. v = unicode(v)
  210. if formatted:
  211. v = formatdate(v)
  212. # time
  213. elif isinstance(v, (datetime.timedelta, datetime.datetime)):
  214. v = unicode(v)
  215. # long
  216. elif isinstance(v, long):
  217. v=int(v)
  218. # convert to strings... (if formatted)
  219. if formatted:
  220. if isinstance(v, float):
  221. v=fmt_money(v)
  222. elif isinstance(v, int):
  223. v = unicode(v)
  224. return v
  225. def convert_to_lists(self, res, formatted=0, as_utf8=0):
  226. """Convert tuple output to lists (internal)."""
  227. nres = []
  228. needs_formatting = self.needs_formatting(res, formatted)
  229. for r in res:
  230. nr = []
  231. for c in r:
  232. if needs_formatting:
  233. val = self.convert_to_simple_type(c, formatted)
  234. else:
  235. val = c
  236. if as_utf8 and type(val) is unicode:
  237. val = val.encode('utf-8')
  238. nr.append(val)
  239. nres.append(nr)
  240. return nres
  241. def convert_to_utf8(self, res, formatted=0):
  242. """Encode result as UTF-8."""
  243. nres = []
  244. for r in res:
  245. nr = []
  246. for c in r:
  247. if type(c) is unicode:
  248. c = c.encode('utf-8')
  249. nr.append(self.convert_to_simple_type(c, formatted))
  250. nres.append(nr)
  251. return nres
  252. def build_conditions(self, filters):
  253. """Convert filters sent as dict, lists to SQL conditions. filter's key
  254. is passed by map function, build conditions like:
  255. * ifnull(`fieldname`, default_value) = %(fieldname)s
  256. * `fieldname` [=, !=, >, >=, <, <=] %(fieldname)s
  257. """
  258. def _build_condition(key):
  259. """
  260. filter's key is passed by map function
  261. build conditions like:
  262. * ifnull(`fieldname`, default_value) = %(fieldname)s
  263. * `fieldname` [=, !=, >, >=, <, <=] %(fieldname)s
  264. """
  265. _operator = "="
  266. value = filters.get(key)
  267. if isinstance(value, (list, tuple)):
  268. _operator = value[0]
  269. filters[key] = value[1]
  270. if _operator not in ["=", "!=", ">", ">=", "<", "<=", "like"]:
  271. _operator = "="
  272. if "[" in key:
  273. split_key = key.split("[")
  274. return "ifnull(`" + split_key[0] + "`, " + split_key[1][:-1] + ") " \
  275. + _operator + " %(" + key + ")s"
  276. else:
  277. return "`" + key + "` " + _operator + " %(" + key + ")s"
  278. if isinstance(filters, basestring):
  279. filters = { "name": filters }
  280. conditions = map(_build_condition, filters)
  281. return " and ".join(conditions), filters
  282. def get(self, doctype, filters=None, as_dict=True):
  283. """Returns `get_value` with fieldname='*'"""
  284. return self.get_value(doctype, filters, "*", as_dict=as_dict)
  285. def get_value(self, doctype, filters=None, fieldname="name", ignore=None, as_dict=False, debug=False):
  286. """Returns a document property or list of properties.
  287. :param doctype: DocType name.
  288. :param filters: Filters like `{"x":"y"}` or name of the document. `None` if Single DocType.
  289. :param fieldname: Column name.
  290. :param ignore: Don't raise exception if table, column is missing.
  291. :param as_dict: Return values as dict.
  292. :param debug: Print query in error log.
  293. Example:
  294. # return first customer starting with a
  295. frappe.db.get_value("Customer", {"name": ("like a%")})
  296. # return last login of **User** `test@example.com`
  297. frappe.db.get_value("User", "test@example.com", "last_login")
  298. last_login, last_ip = frappe.db.get_value("User", "test@example.com",
  299. ["last_login", "last_ip"])
  300. # returns default date_format
  301. frappe.db.get_value("System Settings", None, "date_format")
  302. """
  303. ret = self.get_values(doctype, filters, fieldname, ignore, as_dict, debug)
  304. return ((len(ret[0]) > 1 or as_dict) and ret[0] or ret[0][0]) if ret else None
  305. def get_values(self, doctype, filters=None, fieldname="name", ignore=None, as_dict=False, debug=False, order_by=None, update=None):
  306. """Returns multiple document properties.
  307. :param doctype: DocType name.
  308. :param filters: Filters like `{"x":"y"}` or name of the document.
  309. :param fieldname: Column name.
  310. :param ignore: Don't raise exception if table, column is missing.
  311. :param as_dict: Return values as dict.
  312. :param debug: Print query in error log.
  313. Example:
  314. # return first customer starting with a
  315. customers = frappe.db.get_values("Customer", {"name": ("like a%")})
  316. # return last login of **User** `test@example.com`
  317. user = frappe.db.get_values("User", "test@example.com", "*")[0]
  318. """
  319. if isinstance(filters, list):
  320. return self._get_value_for_many_names(doctype, filters, fieldname, debug=debug)
  321. fields = fieldname
  322. if fieldname!="*":
  323. if isinstance(fieldname, basestring):
  324. fields = [fieldname]
  325. else:
  326. fields = fieldname
  327. if (filters is not None) and (filters!=doctype or doctype=="DocType"):
  328. try:
  329. return self._get_values_from_table(fields, filters, doctype, as_dict, debug, order_by, update)
  330. except Exception, e:
  331. if ignore and e.args[0] in (1146, 1054):
  332. # table or column not found, return None
  333. return None
  334. elif (not ignore) and e.args[0]==1146:
  335. # table not found, look in singles
  336. pass
  337. else:
  338. raise
  339. return self.get_values_from_single(fields, filters, doctype, as_dict, debug, update)
  340. def get_values_from_single(self, fields, filters, doctype, as_dict=False, debug=False, update=None):
  341. """Get values from `tabSingles` (Single DocTypes) (internal).
  342. :param fields: List of fields,
  343. :param filters: Filters (dict).
  344. :param doctype: DocType name.
  345. """
  346. # TODO
  347. # if not frappe.model.meta.is_single(doctype):
  348. # raise frappe.DoesNotExistError("DocType", doctype)
  349. if fields=="*" or isinstance(filters, dict):
  350. # check if single doc matches with filters
  351. values = self.get_singles_dict(doctype)
  352. if isinstance(filters, dict):
  353. for key, value in filters.items():
  354. if values.get(key) != value:
  355. return []
  356. if as_dict:
  357. return values and [values] or []
  358. if isinstance(fields, list):
  359. return [map(lambda d: values.get(d), fields)]
  360. else:
  361. r = self.sql("""select field, value
  362. from tabSingles where field in (%s) and doctype=%s""" \
  363. % (', '.join(['%s'] * len(fields)), '%s'),
  364. tuple(fields) + (doctype,), as_dict=False, debug=debug)
  365. if as_dict:
  366. if r:
  367. r = frappe._dict(r)
  368. if update:
  369. r.update(update)
  370. return [r]
  371. else:
  372. return []
  373. else:
  374. return r and [[i[1] for i in r]] or []
  375. def get_singles_dict(self, doctype):
  376. """Get Single DocType as dict."""
  377. return frappe._dict(self.sql("""select field, value from
  378. tabSingles where doctype=%s""", doctype))
  379. def get_all(self, *args, **kwargs):
  380. return frappe.get_all(*args, **kwargs)
  381. def get_list(self, *args, **kwargs):
  382. return frappe.get_list(*args, **kwargs)
  383. def get_single_value(self, doctype, fieldname):
  384. """Get property of Single DocType."""
  385. val = self.sql("""select value from
  386. tabSingles where doctype=%s and field=%s""", (doctype, fieldname))
  387. return val[0][0] if val else None
  388. def _get_values_from_table(self, fields, filters, doctype, as_dict, debug, order_by=None, update=None):
  389. fl = []
  390. if isinstance(fields, (list, tuple)):
  391. for f in fields:
  392. if "(" in f or " as " in f: # function
  393. fl.append(f)
  394. else:
  395. fl.append("`" + f + "`")
  396. fl = ", ".join(fl)
  397. else:
  398. fl = fields
  399. if fields=="*":
  400. as_dict = True
  401. conditions, filters = self.build_conditions(filters)
  402. order_by = ("order by " + order_by) if order_by else ""
  403. r = self.sql("select %s from `tab%s` where %s %s" % (fl, doctype,
  404. conditions, order_by), filters, as_dict=as_dict, debug=debug, update=update)
  405. return r
  406. def _get_value_for_many_names(self, doctype, names, field, debug=False):
  407. names = filter(None, names)
  408. if names:
  409. return dict(self.sql("select name, `%s` from `tab%s` where name in (%s)" \
  410. % (field, doctype, ", ".join(["%s"]*len(names))), names, debug=debug))
  411. else:
  412. return {}
  413. def update(self, *args, **kwargs):
  414. """Update multiple values. Alias for `set_value`."""
  415. return self.set_value(*args, **kwargs)
  416. def set_value(self, dt, dn, field, val, modified=None, modified_by=None):
  417. """Set a single value in the database, do not call the ORM triggers
  418. but update the modified timestamp.
  419. **Warning:** this function will not call Document events and should be avoided in normal cases.
  420. :param dt: DocType name.
  421. :param dn: Document name.
  422. :param field: Property / field name.
  423. :param value: Value to be updated.
  424. :param modified: Use this as the `modified` timestamp.
  425. :param modified_by: Set this user as `modified_by`.
  426. """
  427. if not modified:
  428. modified = now()
  429. if not modified_by:
  430. modified_by = frappe.session.user
  431. if dn and dt!=dn:
  432. conditions, values = self.build_conditions(dn)
  433. values.update({"val": val, "modified": modified, "modified_by": modified_by})
  434. self.sql("""update `tab{0}` set `{1}`=%(val)s, modified=%(modified)s, modified_by=%(modified_by)s where
  435. {2}""".format(dt, field, conditions), values)
  436. else:
  437. if self.sql("select value from tabSingles where field=%s and doctype=%s", (field, dt)):
  438. self.sql("""update tabSingles set value=%s where field=%s and doctype=%s""",
  439. (val, field, dt))
  440. else:
  441. self.sql("""insert into tabSingles(doctype, field, value)
  442. values (%s, %s, %s)""", (dt, field, val))
  443. if field not in ("modified", "modified_by"):
  444. self.set_value(dt, dn, "modified", modified)
  445. self.set_value(dt, dn, "modified_by", modified_by)
  446. def set(self, doc, field, val):
  447. """Set value in document. **Avoid**"""
  448. doc.db_set(field, val)
  449. def touch(self, doctype, docname):
  450. """Update the modified timestamp of this document."""
  451. from frappe.utils import now
  452. modified = now()
  453. frappe.db.sql("""update `tab{doctype}` set `modified`=%s
  454. where name=%s""".format(doctype=doctype), (modified, docname))
  455. return modified
  456. def set_temp(self, value):
  457. """Set a temperory value and return a key."""
  458. key = frappe.generate_hash()
  459. frappe.defaults.set_default(key, value, parent="__temp")
  460. return key
  461. def get_temp(self, key):
  462. """Return the temperory value and delete it."""
  463. value = self.get_global(key, "__temp")
  464. frappe.defaults.clear_default(key=key, parent="__temp")
  465. return value
  466. def set_global(self, key, val, user='__global'):
  467. """Save a global key value. Global values will be automatically set if they match fieldname."""
  468. self.set_default(key, val, user)
  469. def get_global(self, key, user='__global'):
  470. """Returns a global key value."""
  471. return self.get_default(key, user)
  472. def set_default(self, key, val, parent="__default", parenttype=None):
  473. """Sets a global / user default value."""
  474. frappe.defaults.set_default(key, val, parent, parenttype)
  475. def add_default(self, key, val, parent="__default", parenttype=None):
  476. """Append a default value for a key, there can be multiple default values for a particular key."""
  477. frappe.defaults.add_default(key, val, parent, parenttype)
  478. def get_default(self, key, parent="__default"):
  479. """Returns default value as a list if multiple or single"""
  480. d = frappe.defaults.get_defaults(parent).get(key)
  481. return isinstance(d, list) and d[0] or d
  482. def get_defaults_as_list(self, key, parent="__default"):
  483. """Returns default values as a list."""
  484. d = frappe.defaults.get_default(key, parent)
  485. return isinstance(d, basestring) and [d] or d
  486. def get_defaults(self, key=None, parent="__default"):
  487. """Get all defaults"""
  488. if key:
  489. return frappe.defaults.get_defaults(parent).get(key)
  490. else:
  491. return frappe.defaults.get_defaults(parent)
  492. def begin(self):
  493. self.sql("start transaction")
  494. def commit(self):
  495. """Commit current transaction. Calls SQL `COMMIT`."""
  496. self.sql("commit")
  497. frappe.local.rollback_observers = []
  498. def rollback(self):
  499. """`ROLLBACK` current transaction."""
  500. self.sql("rollback")
  501. self.begin()
  502. for obj in frappe.local.rollback_observers:
  503. if hasattr(obj, "on_rollback"):
  504. obj.on_rollback()
  505. frappe.local.rollback_observers = []
  506. def field_exists(self, dt, fn):
  507. """Return true of field exists."""
  508. return self.sql("select name from tabDocField where fieldname=%s and parent=%s", (dt, fn))
  509. def table_exists(self, tablename):
  510. """Returns True if table exists."""
  511. return tablename in [d[0] for d in self.sql("show tables")]
  512. def a_row_exists(self, doctype):
  513. """Returns True if atleast one row exists."""
  514. return self.sql("select name from `tab{doctype}` limit 1".format(doctype=doctype))
  515. def exists(self, dt, dn=None):
  516. """Returns true if document exists.
  517. :param dt: DocType name.
  518. :param dn: Document name or filter dict."""
  519. if isinstance(dt, basestring):
  520. if dt!="DocType" and dt==dn:
  521. return True # single always exists (!)
  522. try:
  523. return self.sql('select name from `tab%s` where name=%s' % (dt, '%s'), (dn,))
  524. except:
  525. return None
  526. elif isinstance(dt, dict) and dt.get('doctype'):
  527. try:
  528. conditions = []
  529. for d in dt:
  530. if d == 'doctype': continue
  531. conditions.append('`%s` = "%s"' % (d, cstr(dt[d]).replace('"', '\"')))
  532. return self.sql('select name from `tab%s` where %s' % \
  533. (dt['doctype'], " and ".join(conditions)))
  534. except:
  535. return None
  536. def count(self, dt, filters=None, debug=False):
  537. """Returns `COUNT(*)` for given DocType and filters."""
  538. if filters:
  539. conditions, filters = self.build_conditions(filters)
  540. return frappe.db.sql("""select count(*)
  541. from `tab%s` where %s""" % (dt, conditions), filters, debug=debug)[0][0]
  542. else:
  543. return frappe.db.sql("""select count(*)
  544. from `tab%s`""" % (dt,))[0][0]
  545. def get_creation_count(self, doctype, minutes):
  546. """Get count of records created in the last x minutes"""
  547. from frappe.utils import now_datetime
  548. from dateutil.relativedelta import relativedelta
  549. return frappe.db.sql("""select count(name) from `tab{doctype}`
  550. where creation >= %s""".format(doctype=doctype),
  551. now_datetime() - relativedelta(minutes=minutes))[0][0]
  552. def get_table_columns(self, doctype):
  553. """Returns list of column names from given doctype."""
  554. return [r[0] for r in self.sql("DESC `tab%s`" % doctype)]
  555. def add_index(self, doctype, fields, index_name=None):
  556. """Creates an index with given fields if not already created.
  557. Index name will be `fieldname1_fieldname2_index`"""
  558. if not index_name:
  559. index_name = "_".join(fields) + "_index"
  560. # remove index length if present e.g. (10) from index name
  561. index_name = re.sub(r"\s*\([^)]+\)\s*", r"", index_name)
  562. if not frappe.db.sql("""show index from `tab%s` where Key_name="%s" """ % (doctype, index_name)):
  563. frappe.db.commit()
  564. frappe.db.sql("""alter table `tab%s`
  565. add index `%s`(%s)""" % (doctype, index_name, ", ".join(fields)))
  566. def close(self):
  567. """Close database connection."""
  568. if self._conn:
  569. self._cursor.close()
  570. self._conn.close()
  571. self._cursor = None
  572. self._conn = None
  573. def escape(self, s):
  574. """Excape quotes in given string."""
  575. if isinstance(s, unicode):
  576. s = (s or "").encode("utf-8")
  577. return unicode(MySQLdb.escape_string(s), "utf-8")