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

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