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.

db_schema.py 12 KiB

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