Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

db_schema.py 13 KiB

vor 13 Jahren
vor 13 Jahren
vor 13 Jahren
vor 13 Jahren
vor 13 Jahren
vor 13 Jahren
vor 13 Jahren
vor 13 Jahren
vor 13 Jahren
vor 13 Jahren
vor 13 Jahren
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]))