Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 
 
 

555 рядки
18 KiB

  1. # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
  2. # License: MIT. See LICENSE
  3. import datetime
  4. import inspect
  5. import unittest
  6. from random import choice
  7. from unittest.mock import patch
  8. import frappe
  9. from frappe.custom.doctype.custom_field.custom_field import create_custom_field
  10. from frappe.database import savepoint
  11. from frappe.database.database import Database
  12. from frappe.query_builder import Field
  13. from frappe.query_builder.functions import Concat_ws
  14. from frappe.tests.test_query_builder import db_type_is, run_only_if
  15. from frappe.utils import add_days, now, random_string
  16. from frappe.utils.testutils import clear_custom_fields
  17. class TestDB(unittest.TestCase):
  18. def test_get_value(self):
  19. self.assertEqual(frappe.db.get_value("User", {"name": ["=", "Administrator"]}), "Administrator")
  20. self.assertEqual(frappe.db.get_value("User", {"name": ["like", "Admin%"]}), "Administrator")
  21. self.assertNotEqual(frappe.db.get_value("User", {"name": ["!=", "Guest"]}), "Guest")
  22. self.assertEqual(frappe.db.get_value("User", {"name": ["<", "Adn"]}), "Administrator")
  23. self.assertEqual(frappe.db.get_value("User", {"name": ["<=", "Administrator"]}), "Administrator")
  24. self.assertEqual(
  25. frappe.db.get_value("User", {}, ["Max(name)"], order_by=None),
  26. frappe.db.sql("SELECT Max(name) FROM tabUser")[0][0],
  27. )
  28. self.assertEqual(
  29. frappe.db.get_value("User", {}, "Min(name)", order_by=None),
  30. frappe.db.sql("SELECT Min(name) FROM tabUser")[0][0],
  31. )
  32. self.assertIn(
  33. "for update",
  34. frappe.db.get_value(
  35. "User", Field("name") == "Administrator", for_update=True, run=False
  36. ).lower(),
  37. )
  38. user_doctype = frappe.qb.DocType("User")
  39. self.assertEqual(
  40. frappe.qb.from_(user_doctype).select(user_doctype.name, user_doctype.email).run(),
  41. frappe.db.get_values(
  42. user_doctype,
  43. filters={},
  44. fieldname=[user_doctype.name, user_doctype.email],
  45. order_by=None,
  46. ),
  47. )
  48. self.assertEqual(frappe.db.sql("""SELECT name FROM `tabUser` WHERE name > 's' ORDER BY MODIFIED DESC""")[0][0],
  49. frappe.db.get_value("User", {"name": [">", "s"]}))
  50. self.assertEqual(frappe.db.sql("""SELECT name FROM `tabUser` WHERE name >= 't' ORDER BY MODIFIED DESC""")[0][0],
  51. frappe.db.get_value("User", {"name": [">=", "t"]}))
  52. self.assertEqual(
  53. frappe.db.get_values(
  54. "User",
  55. filters={"name": "Administrator"},
  56. distinct=True,
  57. fieldname="email",
  58. ),
  59. frappe.qb.from_(user_doctype)
  60. .where(user_doctype.name == "Administrator")
  61. .select("email")
  62. .distinct()
  63. .run(),
  64. )
  65. self.assertIn(
  66. "concat_ws",
  67. frappe.db.get_value(
  68. "User",
  69. filters={"name": "Administrator"},
  70. fieldname=Concat_ws(" ", "LastName"),
  71. run=False,
  72. ).lower(),
  73. )
  74. self.assertEqual(
  75. frappe.db.sql("select email from tabUser where name='Administrator' order by modified DESC"),
  76. frappe.db.get_values(
  77. "User", filters=[["name", "=", "Administrator"]], fieldname="email"
  78. ),
  79. )
  80. def test_escape(self):
  81. frappe.db.escape("香港濟生堂製藥有限公司 - IT".encode("utf-8"))
  82. def test_get_single_value(self):
  83. #setup
  84. values_dict = {
  85. "Float": 1.5,
  86. "Int": 1,
  87. "Percent": 55.5,
  88. "Currency": 12.5,
  89. "Data": "Test",
  90. "Date": datetime.datetime.now().date(),
  91. "Datetime": datetime.datetime.now(),
  92. "Time": datetime.timedelta(hours=9, minutes=45, seconds=10)
  93. }
  94. test_inputs = [{
  95. "fieldtype": fieldtype,
  96. "value": value} for fieldtype, value in values_dict.items()]
  97. for fieldtype in values_dict.keys():
  98. create_custom_field("Print Settings", {
  99. "fieldname": f"test_{fieldtype.lower()}",
  100. "label": f"Test {fieldtype}",
  101. "fieldtype": fieldtype,
  102. })
  103. #test
  104. for inp in test_inputs:
  105. fieldname = f"test_{inp['fieldtype'].lower()}"
  106. frappe.db.set_value("Print Settings", "Print Settings", fieldname, inp["value"])
  107. self.assertEqual(frappe.db.get_single_value("Print Settings", fieldname), inp["value"])
  108. #teardown
  109. clear_custom_fields("Print Settings")
  110. def test_log_touched_tables(self):
  111. frappe.flags.in_migrate = True
  112. frappe.flags.touched_tables = set()
  113. frappe.db.set_value('System Settings', 'System Settings', 'backup_limit', 5)
  114. self.assertIn('tabSingles', frappe.flags.touched_tables)
  115. frappe.flags.touched_tables = set()
  116. todo = frappe.get_doc({'doctype': 'ToDo', 'description': 'Random Description'})
  117. todo.save()
  118. self.assertIn('tabToDo', frappe.flags.touched_tables)
  119. frappe.flags.touched_tables = set()
  120. todo.description = "Another Description"
  121. todo.save()
  122. self.assertIn('tabToDo', frappe.flags.touched_tables)
  123. if frappe.db.db_type != "postgres":
  124. frappe.flags.touched_tables = set()
  125. frappe.db.sql("UPDATE tabToDo SET description = 'Updated Description'")
  126. self.assertNotIn('tabToDo SET', frappe.flags.touched_tables)
  127. self.assertIn('tabToDo', frappe.flags.touched_tables)
  128. frappe.flags.touched_tables = set()
  129. todo.delete()
  130. self.assertIn('tabToDo', frappe.flags.touched_tables)
  131. frappe.flags.touched_tables = set()
  132. create_custom_field('ToDo', {'label': 'ToDo Custom Field'})
  133. self.assertIn('tabToDo', frappe.flags.touched_tables)
  134. self.assertIn('tabCustom Field', frappe.flags.touched_tables)
  135. frappe.flags.in_migrate = False
  136. frappe.flags.touched_tables.clear()
  137. def test_db_keywords_as_fields(self):
  138. """Tests if DB keywords work as docfield names. If they're wrapped with grave accents."""
  139. # Using random.choices, picked out a list of 40 keywords for testing
  140. all_keywords = {
  141. "mariadb": ["CHARACTER", "DELAYED", "LINES", "EXISTS", "YEAR_MONTH", "LOCALTIME", "BOTH", "MEDIUMINT",
  142. "LEFT", "BINARY", "DEFAULT", "KILL", "WRITE", "SQL_SMALL_RESULT", "CURRENT_TIME", "CROSS", "INHERITS",
  143. "SELECT", "TABLE", "ALTER", "CURRENT_TIMESTAMP", "XOR", "CASE", "ALL", "WHERE", "INT", "TO", "SOME",
  144. "DAY_MINUTE", "ERRORS", "OPTIMIZE", "REPLACE", "HIGH_PRIORITY", "VARBINARY", "HELP", "IS",
  145. "CHAR", "DESCRIBE", "KEY"],
  146. "postgres": ["WORK", "LANCOMPILER", "REAL", "HAVING", "REPEATABLE", "DATA", "USING", "BIT", "DEALLOCATE",
  147. "SERIALIZABLE", "CURSOR", "INHERITS", "ARRAY", "TRUE", "IGNORE", "PARAMETER_MODE", "ROW", "CHECKPOINT",
  148. "SHOW", "BY", "SIZE", "SCALE", "UNENCRYPTED", "WITH", "AND", "CONVERT", "FIRST", "SCOPE", "WRITE", "INTERVAL",
  149. "CHARACTER_SET_SCHEMA", "ADD", "SCROLL", "NULL", "WHEN", "TRANSACTION_ACTIVE",
  150. "INT", "FORTRAN", "STABLE"]
  151. }
  152. created_docs = []
  153. # edit by rushabh: added [:1]
  154. # don't run every keyword! - if one works, they all do
  155. fields = all_keywords[frappe.conf.db_type][:1]
  156. test_doctype = "ToDo"
  157. def add_custom_field(field):
  158. create_custom_field(test_doctype, {
  159. "fieldname": field.lower(),
  160. "label": field.title(),
  161. "fieldtype": 'Data',
  162. })
  163. # Create custom fields for test_doctype
  164. for field in fields:
  165. add_custom_field(field)
  166. # Create documents under that doctype and query them via ORM
  167. for _ in range(10):
  168. docfields = {key.lower(): random_string(10) for key in fields}
  169. doc = frappe.get_doc({"doctype": test_doctype, "description": random_string(20), **docfields})
  170. doc.insert()
  171. created_docs.append(doc.name)
  172. random_field = choice(fields).lower()
  173. random_doc = choice(created_docs)
  174. random_value = random_string(20)
  175. # Testing read
  176. self.assertEqual(list(frappe.get_all("ToDo", fields=[random_field], limit=1)[0])[0], random_field)
  177. self.assertEqual(list(frappe.get_all("ToDo", fields=[f"`{random_field}` as total"], limit=1)[0])[0], "total")
  178. # Testing read for distinct and sql functions
  179. self.assertEqual(list(
  180. frappe.get_all("ToDo",
  181. fields=[f"`{random_field}` as total"],
  182. distinct=True,
  183. limit=1,
  184. )[0]
  185. )[0], "total")
  186. self.assertEqual(list(
  187. frappe.get_all("ToDo",
  188. fields=[f"`{random_field}`"],
  189. distinct=True,
  190. limit=1,
  191. )[0]
  192. )[0], random_field)
  193. self.assertEqual(list(
  194. frappe.get_all("ToDo",
  195. fields=[f"count(`{random_field}`)"],
  196. limit=1
  197. )[0]
  198. )[0], "count" if frappe.conf.db_type == "postgres" else f"count(`{random_field}`)")
  199. # Testing update
  200. frappe.db.set_value(test_doctype, random_doc, random_field, random_value)
  201. self.assertEqual(frappe.db.get_value(test_doctype, random_doc, random_field), random_value)
  202. # Cleanup - delete records and remove custom fields
  203. for doc in created_docs:
  204. frappe.delete_doc(test_doctype, doc)
  205. clear_custom_fields(test_doctype)
  206. def test_savepoints(self):
  207. frappe.db.rollback()
  208. save_point = "todonope"
  209. created_docs = []
  210. failed_docs = []
  211. for _ in range(5):
  212. frappe.db.savepoint(save_point)
  213. doc_gone = frappe.get_doc(doctype="ToDo", description="nope").save()
  214. failed_docs.append(doc_gone.name)
  215. frappe.db.rollback(save_point=save_point)
  216. doc_kept = frappe.get_doc(doctype="ToDo", description="nope").save()
  217. created_docs.append(doc_kept.name)
  218. frappe.db.commit()
  219. for d in failed_docs:
  220. self.assertFalse(frappe.db.exists("ToDo", d))
  221. for d in created_docs:
  222. self.assertTrue(frappe.db.exists("ToDo", d))
  223. def test_savepoints_wrapper(self):
  224. frappe.db.rollback()
  225. class SpecificExc(Exception):
  226. pass
  227. created_docs = []
  228. failed_docs = []
  229. for _ in range(5):
  230. with savepoint(catch=SpecificExc):
  231. doc_kept = frappe.get_doc(doctype="ToDo", description="nope").save()
  232. created_docs.append(doc_kept.name)
  233. with savepoint(catch=SpecificExc):
  234. doc_gone = frappe.get_doc(doctype="ToDo", description="nope").save()
  235. failed_docs.append(doc_gone.name)
  236. raise SpecificExc
  237. frappe.db.commit()
  238. for d in failed_docs:
  239. self.assertFalse(frappe.db.exists("ToDo", d))
  240. for d in created_docs:
  241. self.assertTrue(frappe.db.exists("ToDo", d))
  242. def test_transaction_writes_error(self):
  243. from frappe.database.database import Database
  244. frappe.db.rollback()
  245. frappe.db.MAX_WRITES_PER_TRANSACTION = 1
  246. note = frappe.get_last_doc("ToDo")
  247. note.description = "changed"
  248. with self.assertRaises(frappe.TooManyWritesError) as tmw:
  249. note.save()
  250. frappe.db.MAX_WRITES_PER_TRANSACTION = Database.MAX_WRITES_PER_TRANSACTION
  251. @run_only_if(db_type_is.MARIADB)
  252. class TestDDLCommandsMaria(unittest.TestCase):
  253. test_table_name = "TestNotes"
  254. def setUp(self) -> None:
  255. frappe.db.commit()
  256. frappe.db.sql(
  257. f"""
  258. CREATE TABLE `tab{self.test_table_name}` (`id` INT NULL, content TEXT, PRIMARY KEY (`id`));
  259. """
  260. )
  261. def tearDown(self) -> None:
  262. frappe.db.sql(f"DROP TABLE tab{self.test_table_name};")
  263. self.test_table_name = "TestNotes"
  264. def test_rename(self) -> None:
  265. new_table_name = f"{self.test_table_name}_new"
  266. frappe.db.rename_table(self.test_table_name, new_table_name)
  267. check_exists = frappe.db.sql(
  268. f"""
  269. SELECT * FROM INFORMATION_SCHEMA.TABLES
  270. WHERE TABLE_NAME = N'tab{new_table_name}';
  271. """
  272. )
  273. self.assertGreater(len(check_exists), 0)
  274. self.assertIn(f"tab{new_table_name}", check_exists[0])
  275. # * so this table is deleted after the rename
  276. self.test_table_name = new_table_name
  277. def test_describe(self) -> None:
  278. self.assertEqual(
  279. (
  280. ("id", "int(11)", "NO", "PRI", None, ""),
  281. ("content", "text", "YES", "", None, ""),
  282. ),
  283. frappe.db.describe(self.test_table_name),
  284. )
  285. def test_change_type(self) -> None:
  286. frappe.db.change_column_type("TestNotes", "id", "varchar(255)")
  287. test_table_description = frappe.db.sql(f"DESC tab{self.test_table_name};")
  288. self.assertGreater(len(test_table_description), 0)
  289. self.assertIn("varchar(255)", test_table_description[0])
  290. def test_add_index(self) -> None:
  291. index_name = "test_index"
  292. frappe.db.add_index(self.test_table_name, ["id", "content(50)"], index_name)
  293. indexs_in_table = frappe.db.sql(
  294. f"""
  295. SHOW INDEX FROM tab{self.test_table_name}
  296. WHERE Key_name = '{index_name}';
  297. """
  298. )
  299. self.assertEquals(len(indexs_in_table), 2)
  300. class TestDBSetValue(unittest.TestCase):
  301. @classmethod
  302. def setUpClass(cls):
  303. cls.todo1 = frappe.get_doc(doctype="ToDo", description="test_set_value 1").insert()
  304. cls.todo2 = frappe.get_doc(doctype="ToDo", description="test_set_value 2").insert()
  305. def test_update_single_doctype_field(self):
  306. value = frappe.db.get_single_value("System Settings", "deny_multiple_sessions")
  307. changed_value = not value
  308. frappe.db.set_value("System Settings", "System Settings", "deny_multiple_sessions", changed_value)
  309. current_value = frappe.db.get_single_value("System Settings", "deny_multiple_sessions")
  310. self.assertEqual(current_value, changed_value)
  311. changed_value = not current_value
  312. frappe.db.set_value("System Settings", None, "deny_multiple_sessions", changed_value)
  313. current_value = frappe.db.get_single_value("System Settings", "deny_multiple_sessions")
  314. self.assertEqual(current_value, changed_value)
  315. changed_value = not current_value
  316. frappe.db.set_single_value("System Settings", "deny_multiple_sessions", changed_value)
  317. current_value = frappe.db.get_single_value("System Settings", "deny_multiple_sessions")
  318. self.assertEqual(current_value, changed_value)
  319. def test_update_single_row_single_column(self):
  320. frappe.db.set_value("ToDo", self.todo1.name, "description", "test_set_value change 1")
  321. updated_value = frappe.db.get_value("ToDo", self.todo1.name, "description")
  322. self.assertEqual(updated_value, "test_set_value change 1")
  323. def test_update_single_row_multiple_columns(self):
  324. description, status = "Upated by test_update_single_row_multiple_columns", "Closed"
  325. frappe.db.set_value("ToDo", self.todo1.name, {
  326. "description": description,
  327. "status": status,
  328. }, update_modified=False)
  329. updated_desciption, updated_status = frappe.db.get_value("ToDo",
  330. filters={"name": self.todo1.name},
  331. fieldname=["description", "status"]
  332. )
  333. self.assertEqual(description, updated_desciption)
  334. self.assertEqual(status, updated_status)
  335. def test_update_multiple_rows_single_column(self):
  336. frappe.db.set_value("ToDo", {"description": ("like", "%test_set_value%")}, "description", "change 2")
  337. self.assertEqual(frappe.db.get_value("ToDo", self.todo1.name, "description"), "change 2")
  338. self.assertEqual(frappe.db.get_value("ToDo", self.todo2.name, "description"), "change 2")
  339. def test_update_multiple_rows_multiple_columns(self):
  340. todos_to_update = frappe.get_all("ToDo", filters={
  341. "description": ("like", "%test_set_value%"),
  342. "status": ("!=", "Closed")
  343. }, pluck="name")
  344. frappe.db.set_value("ToDo", {
  345. "description": ("like", "%test_set_value%"),
  346. "status": ("!=", "Closed")
  347. }, {
  348. "status": "Closed",
  349. "priority": "High"
  350. })
  351. test_result = frappe.get_all("ToDo", filters={"name": ("in", todos_to_update)}, fields=["status", "priority"])
  352. self.assertTrue(all(x for x in test_result if x["status"] == "Closed"))
  353. self.assertTrue(all(x for x in test_result if x["priority"] == "High"))
  354. def test_update_modified_options(self):
  355. self.todo2.reload()
  356. todo = self.todo2
  357. updated_description = f"{todo.description} - by `test_update_modified_options`"
  358. custom_modified = datetime.datetime.fromisoformat(add_days(now(), 10))
  359. custom_modified_by = "user_that_doesnt_exist@example.com"
  360. frappe.db.set_value("ToDo", todo.name, "description", updated_description, update_modified=False)
  361. self.assertEqual(updated_description, frappe.db.get_value("ToDo", todo.name, "description"))
  362. self.assertEqual(todo.modified, frappe.db.get_value("ToDo", todo.name, "modified"))
  363. frappe.db.set_value("ToDo", todo.name, "description", "test_set_value change 1", modified=custom_modified, modified_by=custom_modified_by)
  364. self.assertTupleEqual(
  365. (custom_modified, custom_modified_by),
  366. frappe.db.get_value("ToDo", todo.name, ["modified", "modified_by"])
  367. )
  368. def test_for_update(self):
  369. self.todo1.reload()
  370. with patch.object(Database, "sql") as sql_called:
  371. frappe.db.set_value(
  372. self.todo1.doctype,
  373. self.todo1.name,
  374. "description",
  375. f"{self.todo1.description}-edit by `test_for_update`"
  376. )
  377. first_query = sql_called.call_args_list[0].args[0]
  378. second_query = sql_called.call_args_list[1].args[0]
  379. self.assertTrue(sql_called.call_count == 2)
  380. self.assertTrue("FOR UPDATE" in first_query)
  381. if frappe.conf.db_type == "postgres":
  382. from frappe.database.postgres.database import modify_query
  383. self.assertTrue(modify_query("UPDATE `tabToDo` SET") in second_query)
  384. if frappe.conf.db_type == "mariadb":
  385. self.assertTrue("UPDATE `tabToDo` SET" in second_query)
  386. def test_cleared_cache(self):
  387. self.todo2.reload()
  388. with patch.object(frappe, "clear_document_cache") as clear_cache:
  389. frappe.db.set_value(
  390. self.todo2.doctype,
  391. self.todo2.name,
  392. "description",
  393. f"{self.todo2.description}-edit by `test_cleared_cache`"
  394. )
  395. clear_cache.assert_called()
  396. def test_update_alias(self):
  397. args = (self.todo1.doctype, self.todo1.name, "description", "Updated by `test_update_alias`")
  398. kwargs = {"for_update": False, "modified": None, "modified_by": None, "update_modified": True, "debug": False}
  399. self.assertTrue("return self.set_value(" in inspect.getsource(frappe.db.update))
  400. with patch.object(Database, "set_value") as set_value:
  401. frappe.db.update(*args, **kwargs)
  402. set_value.assert_called_once()
  403. set_value.assert_called_with(*args, **kwargs)
  404. @classmethod
  405. def tearDownClass(cls):
  406. frappe.db.rollback()
  407. @run_only_if(db_type_is.POSTGRES)
  408. class TestDDLCommandsPost(unittest.TestCase):
  409. test_table_name = "TestNotes"
  410. def setUp(self) -> None:
  411. frappe.db.sql(
  412. f"""
  413. CREATE TABLE "tab{self.test_table_name}" ("id" INT NULL, content text, PRIMARY KEY ("id"))
  414. """
  415. )
  416. def tearDown(self) -> None:
  417. frappe.db.sql(f'DROP TABLE "tab{self.test_table_name}"')
  418. self.test_table_name = "TestNotes"
  419. def test_rename(self) -> None:
  420. new_table_name = f"{self.test_table_name}_new"
  421. frappe.db.rename_table(self.test_table_name, new_table_name)
  422. check_exists = frappe.db.sql(
  423. f"""
  424. SELECT EXISTS (
  425. SELECT FROM information_schema.tables
  426. WHERE table_name = 'tab{new_table_name}'
  427. );
  428. """
  429. )
  430. self.assertTrue(check_exists[0][0])
  431. # * so this table is deleted after the rename
  432. self.test_table_name = new_table_name
  433. def test_describe(self) -> None:
  434. self.assertEqual(
  435. [("id",), ("content",)], frappe.db.describe(self.test_table_name)
  436. )
  437. def test_change_type(self) -> None:
  438. frappe.db.change_column_type(self.test_table_name, "id", "varchar(255)")
  439. check_change = frappe.db.sql(
  440. f"""
  441. SELECT
  442. table_name,
  443. column_name,
  444. data_type
  445. FROM
  446. information_schema.columns
  447. WHERE
  448. table_name = 'tab{self.test_table_name}'
  449. """
  450. )
  451. self.assertGreater(len(check_change), 0)
  452. self.assertIn("character varying", check_change[0])
  453. def test_add_index(self) -> None:
  454. index_name = "test_index"
  455. frappe.db.add_index(self.test_table_name, ["id", "content(50)"], index_name)
  456. indexs_in_table = frappe.db.sql(
  457. f"""
  458. SELECT indexname
  459. FROM pg_indexes
  460. WHERE tablename = 'tab{self.test_table_name}'
  461. AND indexname = '{index_name}' ;
  462. """,
  463. )
  464. self.assertEquals(len(indexs_in_table), 1)