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