Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 
 

439 linhas
12 KiB

  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)))