Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

db_schema.py 13 KiB

il y a 13 ans
il y a 13 ans
il y a 13 ans
il y a 13 ans
il y a 13 ans
il y a 13 ans
il y a 13 ans
il y a 13 ans
il y a 13 ans
il y a 13 ans
il y a 13 ans
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. # Copyright (c) 2012 Web Notes Technologies Pvt Ltd (http://erpnext.com)
  2. #
  3. # MIT License (MIT)
  4. #
  5. # Permission is hereby granted, free of charge, to any person obtaining a
  6. # copy of this software and associated documentation files (the "Software"),
  7. # to deal in the Software without restriction, including without limitation
  8. # the rights to use, copy, modify, merge, publish, distribute, sublicense,
  9. # and/or sell copies of the Software, and to permit persons to whom the
  10. # Software is furnished to do so, subject to the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be included in
  13. # all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  16. # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  17. # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  18. # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
  19. # CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
  20. # OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  21. #
  22. from __future__ import unicode_literals
  23. """
  24. Syncs a database table to the `DocType` (metadata)
  25. .. note:: This module is only used internally
  26. """
  27. import os
  28. import webnotes
  29. type_map = {
  30. 'currency': ('decimal', '18,6')
  31. ,'int': ('int', '11')
  32. ,'float': ('decimal', '18,6')
  33. ,'check': ('int', '1')
  34. ,'small text': ('text', '')
  35. ,'long text': ('longtext', '')
  36. ,'code': ('text', '')
  37. ,'text editor': ('text', '')
  38. ,'date': ('date', '')
  39. ,'time': ('time', '')
  40. ,'text': ('text', '')
  41. ,'data': ('varchar', '180')
  42. ,'link': ('varchar', '180')
  43. ,'password': ('varchar', '180')
  44. ,'select': ('varchar', '180')
  45. ,'read only': ('varchar', '180')
  46. ,'blob': ('longblob', '')
  47. }
  48. default_columns = ['name', 'creation', 'modified', 'modified_by', 'owner', 'docstatus', 'parent',\
  49. 'parentfield', 'parenttype', 'idx']
  50. default_shortcuts = ['_Login', '__user', '_Full Name', 'Today', '__today']
  51. import _mysql_exceptions
  52. # -------------------------------------------------
  53. # Class database table
  54. # -------------------------------------------------
  55. class DbTable:
  56. def __init__(self, doctype, prefix = 'tab'):
  57. self.doctype = doctype
  58. self.name = prefix + doctype
  59. self.columns = {}
  60. self.current_columns = {}
  61. # lists for change
  62. self.add_column = []
  63. self.change_type = []
  64. self.add_index = []
  65. self.drop_index = []
  66. self.set_default = []
  67. # load
  68. self.get_columns_from_docfields()
  69. def create(self):
  70. add_text = ''
  71. # columns
  72. t = self.get_column_definitions()
  73. if t: add_text += ',\n'.join(self.get_column_definitions()) + ',\n'
  74. # index
  75. t = self.get_index_definitions()
  76. if t: add_text += ',\n'.join(self.get_index_definitions()) + ',\n'
  77. # create table
  78. webnotes.conn.sql("""create table `%s` (
  79. name varchar(120) not null primary key,
  80. creation datetime,
  81. modified datetime,
  82. modified_by varchar(40),
  83. owner varchar(40),
  84. docstatus int(1) default '0',
  85. parent varchar(120),
  86. parentfield varchar(120),
  87. parenttype varchar(120),
  88. idx int(8),
  89. %sindex parent(parent))
  90. ENGINE=InnoDB
  91. CHARACTER SET=utf8""" % (self.name, add_text))
  92. def get_columns_from_docfields(self):
  93. """
  94. get columns from docfields and custom fields
  95. """
  96. fl = webnotes.conn.sql("SELECT * FROM tabDocField WHERE parent = '%s'" % self.doctype, as_dict = 1)
  97. try:
  98. custom_fl = webnotes.conn.sql("""\
  99. SELECT * FROM `tabCustom Field`
  100. WHERE dt = %s AND docstatus < 2""", self.doctype, as_dict=1)
  101. if custom_fl: fl += custom_fl
  102. except Exception, e:
  103. if e.args[0]!=1146: # ignore no custom field
  104. raise e
  105. for f in fl:
  106. self.columns[f['fieldname']] = DbColumn(self, f['fieldname'],
  107. f['fieldtype'], f.get('length'), f.get('default'),
  108. f.get('search_index'), f.get('options'))
  109. def get_columns_from_db(self):
  110. self.show_columns = webnotes.conn.sql("desc `%s`" % self.name)
  111. for c in self.show_columns:
  112. self.current_columns[c[0]] = {'name': c[0], 'type':c[1], 'index':c[3], 'default':c[4]}
  113. def get_column_definitions(self):
  114. column_list = [] + default_columns
  115. ret = []
  116. for k in self.columns.keys():
  117. if k not in column_list:
  118. d = self.columns[k].get_definition()
  119. if d:
  120. ret.append('`'+ k+ '` ' + d)
  121. column_list.append(k)
  122. return ret
  123. def get_index_definitions(self):
  124. ret = []
  125. for k in self.columns.keys():
  126. if type_map.get(self.columns[k].fieldtype) and type_map.get(self.columns[k].fieldtype.lower())[0] not in ('text', 'blob'):
  127. ret.append('index `' + k + '`(`' + k + '`)')
  128. return ret
  129. # GET foreign keys
  130. def get_foreign_keys(self):
  131. fk_list = []
  132. txt = webnotes.conn.sql("show create table `%s`" % self.name)[0][1]
  133. for line in txt.split('\n'):
  134. if line.strip().startswith('CONSTRAINT') and line.find('FOREIGN')!=-1:
  135. try:
  136. fk_list.append((line.split('`')[3], line.split('`')[1]))
  137. except IndexError, e:
  138. pass
  139. return fk_list
  140. # Drop foreign keys
  141. def drop_foreign_keys(self):
  142. if not self.drop_foreign_key:
  143. return
  144. fk_list = self.get_foreign_keys()
  145. # make dictionary of constraint names
  146. fk_dict = {}
  147. for f in fk_list:
  148. fk_dict[f[0]] = f[1]
  149. # drop
  150. for col in self.drop_foreign_key:
  151. webnotes.conn.sql("set foreign_key_checks=0")
  152. webnotes.conn.sql("alter table `%s` drop foreign key `%s`" % (self.name, fk_dict[col.fieldname]))
  153. webnotes.conn.sql("set foreign_key_checks=1")
  154. def sync(self):
  155. if not self.name in DbManager(webnotes.conn).get_tables_list(webnotes.conn.cur_db_name):
  156. self.create()
  157. else:
  158. self.alter()
  159. def alter(self):
  160. self.get_columns_from_db()
  161. for col in self.columns.values():
  162. col.check(self.current_columns.get(col.fieldname, None))
  163. for col in self.add_column:
  164. webnotes.conn.sql("alter table `%s` add column `%s` %s" % (self.name, col.fieldname, col.get_definition()))
  165. for col in self.change_type:
  166. webnotes.conn.sql("alter table `%s` change `%s` `%s` %s" % (self.name, col.fieldname, col.fieldname, col.get_definition()))
  167. for col in self.add_index:
  168. # if index key not exists
  169. if not webnotes.conn.sql("show index from `%s` where key_name = '%s'" % (self.name, col.fieldname)):
  170. webnotes.conn.sql("alter table `%s` add index `%s`(`%s`)" % (self.name, col.fieldname, col.fieldname))
  171. for col in self.drop_index:
  172. if col.fieldname != 'name': # primary key
  173. # if index key exists
  174. if webnotes.conn.sql("show index from `%s` where key_name = '%s'" % (self.name, col.fieldname)):
  175. webnotes.conn.sql("alter table `%s` drop index `%s`" % (self.name, col.fieldname))
  176. for col in self.set_default:
  177. webnotes.conn.sql("alter table `%s` alter column `%s` set default %s" % (self.name, col.fieldname, '%s'), col.default)
  178. # -------------------------------------------------
  179. # Class database column
  180. # -------------------------------------------------
  181. class DbColumn:
  182. def __init__(self, table, fieldname, fieldtype, length, default, set_index, options):
  183. self.table = table
  184. self.fieldname = fieldname
  185. self.fieldtype = fieldtype
  186. self.length = length
  187. self.set_index = set_index
  188. self.default = default
  189. self.options = options
  190. def get_definition(self, with_default=1):
  191. d = type_map.get(self.fieldtype.lower())
  192. if not d:
  193. return
  194. ret = d[0]
  195. if d[1]:
  196. ret += '(' + d[1] + ')'
  197. if with_default and self.default and (self.default not in default_shortcuts) \
  198. and d[0] not in ['text', 'longblob']:
  199. ret += ' default "' + self.default.replace('"', '\"') + '"'
  200. return ret
  201. def check(self, current_def):
  202. column_def = self.get_definition(0)
  203. # no columns
  204. if not column_def:
  205. return
  206. # to add?
  207. if not current_def:
  208. self.fieldname = validate_column_name(self.fieldname)
  209. self.table.add_column.append(self)
  210. return
  211. # type
  212. if current_def['type'] != column_def:
  213. self.table.change_type.append(self)
  214. # index
  215. else:
  216. if (current_def['index'] and not self.set_index):
  217. self.table.drop_index.append(self)
  218. if (not current_def['index'] and self.set_index and not (column_def in ['text','blob'])):
  219. self.table.add_index.append(self)
  220. # default
  221. if (self.default and (current_def['default'] != self.default) and (self.default not in default_shortcuts) and not (column_def in ['text','blob'])):
  222. self.table.set_default.append(self)
  223. class DbManager:
  224. """
  225. Basically, a wrapper for oft-used mysql commands. like show tables,databases, variables etc...
  226. #TODO:
  227. 0. Simplify / create settings for the restore database source folder
  228. 0a. Merge restore database and extract_sql(from webnotes_server_tools).
  229. 1. Setter and getter for different mysql variables.
  230. 2. Setter and getter for mysql variables at global level??
  231. """
  232. def __init__(self,conn):
  233. """
  234. Pass root_conn here for access to all databases.
  235. """
  236. if conn:
  237. self.conn = conn
  238. def get_variables(self,regex):
  239. """
  240. Get variables that match the passed pattern regex
  241. """
  242. return list(self.conn.sql("SHOW VARIABLES LIKE '%s'"%regex))
  243. def get_table_schema(self,table):
  244. """
  245. Just returns the output of Desc tables.
  246. """
  247. return list(self.conn.sql("DESC `%s`"%table))
  248. def get_tables_list(self,target=None):
  249. """get list of tables"""
  250. if target:
  251. self.conn.use(target)
  252. return [t[0] for t in self.conn.sql("SHOW TABLES")]
  253. def create_user(self,user,password):
  254. #Create user if it doesn't exist.
  255. try:
  256. if password:
  257. self.conn.sql("CREATE USER '%s'@'localhost' IDENTIFIED BY '%s';" % (user[:16], password))
  258. else:
  259. self.conn.sql("CREATE USER '%s'@'localhost';"%user[:16])
  260. except Exception, e:
  261. raise e
  262. def delete_user(self,target):
  263. # delete user if exists
  264. try:
  265. self.conn.sql("DROP USER '%s'@'localhost';" % target)
  266. except Exception, e:
  267. if e.args[0]==1396:
  268. pass
  269. else:
  270. raise e
  271. def create_database(self,target):
  272. if target in self.get_database_list():
  273. self.drop_database(target)
  274. self.conn.sql("CREATE DATABASE IF NOT EXISTS `%s` ;" % target)
  275. def drop_database(self,target):
  276. try:
  277. self.conn.sql("DROP DATABASE IF EXISTS `%s`;"%target)
  278. except Exception,e:
  279. raise e
  280. def grant_all_privileges(self,target,user):
  281. try:
  282. self.conn.sql("GRANT ALL PRIVILEGES ON `%s`.* TO '%s'@'localhost';" % (target, user))
  283. except Exception,e:
  284. raise e
  285. def grant_select_privilges(self,db,table,user):
  286. try:
  287. if table:
  288. self.conn.sql("GRANT SELECT ON %s.%s to '%s'@'localhost';" % (db,table,user))
  289. else:
  290. self.conn.sql("GRANT SELECT ON %s.* to '%s'@'localhost';" % (db,user))
  291. except Exception,e:
  292. raise e
  293. def flush_privileges(self):
  294. try:
  295. self.conn.sql("FLUSH PRIVILEGES")
  296. except Exception,e:
  297. raise e
  298. def get_database_list(self):
  299. """get list of databases"""
  300. return [d[0] for d in self.conn.sql("SHOW DATABASES")]
  301. def restore_database(self,target,source,root_password):
  302. from webnotes.utils import make_esc
  303. esc = make_esc('$ ')
  304. try:
  305. ret = os.system("mysql -u root -p%s %s < %s" % \
  306. (esc(root_password), esc(target), source))
  307. except Exception,e:
  308. raise e
  309. def drop_table(self,table_name):
  310. """drop table if exists"""
  311. if not table_name in self.get_tables_list():
  312. return
  313. try:
  314. self.conn.sql("DROP TABLE IF EXISTS %s "%(table_name))
  315. except Exception,e:
  316. raise e
  317. def set_transaction_isolation_level(self,scope='SESSION',level='READ COMMITTED'):
  318. #Sets the transaction isolation level. scope = global/session
  319. try:
  320. self.conn.sql("SET %s TRANSACTION ISOLATION LEVEL %s"%(scope,level))
  321. except Exception,e:
  322. raise e
  323. # -------------------------------------------------
  324. # validate column name to be code-friendly
  325. # -------------------------------------------------
  326. def validate_column_name(n):
  327. n = n.replace(' ','_').strip().lower()
  328. import re
  329. if re.search("[\W]", n):
  330. webnotes.msgprint('err:%s is not a valid fieldname.<br>A valid name must contain letters / numbers / spaces.<br><b>Tip: </b>You can change the Label after the fieldname has been set' % n)
  331. raise Exception
  332. return n
  333. # -------------------------------------------------
  334. # sync table - called from form.py
  335. # -------------------------------------------------
  336. def updatedb(dt):
  337. """
  338. Syncs a `DocType` to the table
  339. * creates if required
  340. * updates columns
  341. * updates indices
  342. """
  343. res = webnotes.conn.sql("select ifnull(issingle, 0) from tabDocType where name=%s", dt)
  344. if not res:
  345. raise Exception, 'Wrong doctype "%s" in updatedb' % dt
  346. if not res[0][0]:
  347. webnotes.conn.commit()
  348. tab = DbTable(dt, 'tab')
  349. tab.sync()
  350. webnotes.conn.begin()
  351. # patch to remove foreign keys
  352. # ----------------------------
  353. def remove_all_foreign_keys():
  354. webnotes.conn.sql("set foreign_key_checks = 0")
  355. webnotes.conn.commit()
  356. for t in webnotes.conn.sql("select name from tabDocType where ifnull(issingle,0)=0"):
  357. dbtab = webnotes.model.db_schema.DbTable(t[0])
  358. try:
  359. fklist = dbtab.get_foreign_keys()
  360. except Exception, e:
  361. if e.args[0]==1146:
  362. fklist = []
  363. else:
  364. raise e
  365. for f in fklist:
  366. webnotes.conn.sql("alter table `tab%s` drop foreign key `%s`" % (t[0], f[1]))