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.
 
 
 
 
 
 

910 lines
27 KiB

  1. # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
  2. # License: MIT. See LICENSE
  3. import datetime
  4. import inspect
  5. from math import ceil
  6. from random import choice
  7. from unittest.mock import patch
  8. import frappe
  9. from frappe.core.utils import find
  10. from frappe.custom.doctype.custom_field.custom_field import create_custom_field
  11. from frappe.database import savepoint
  12. from frappe.database.database import Database
  13. from frappe.database.utils import FallBackDateTimeStr
  14. from frappe.query_builder import Field
  15. from frappe.query_builder.functions import Concat_ws
  16. from frappe.tests.test_query_builder import db_type_is, run_only_if
  17. from frappe.tests.utils import FrappeTestCase
  18. from frappe.utils import add_days, cint, now, random_string
  19. from frappe.utils.testutils import clear_custom_fields
  20. class TestDB(FrappeTestCase):
  21. def test_datetime_format(self):
  22. now_str = now()
  23. self.assertEqual(frappe.db.format_datetime(None), FallBackDateTimeStr)
  24. self.assertEqual(frappe.db.format_datetime(now_str), now_str)
  25. @run_only_if(db_type_is.MARIADB)
  26. def test_get_column_type(self):
  27. desc_data = frappe.db.sql("desc `tabUser`", as_dict=1)
  28. user_name_type = find(desc_data, lambda x: x["Field"] == "name")["Type"]
  29. self.assertEqual(frappe.db.get_column_type("User", "name"), user_name_type)
  30. def test_get_database_size(self):
  31. self.assertIsInstance(frappe.db.get_database_size(), (float, int))
  32. def test_get_value(self):
  33. self.assertEqual(frappe.db.get_value("User", {"name": ["=", "Administrator"]}), "Administrator")
  34. self.assertEqual(frappe.db.get_value("User", {"name": ["like", "Admin%"]}), "Administrator")
  35. self.assertNotEqual(frappe.db.get_value("User", {"name": ["!=", "Guest"]}), "Guest")
  36. self.assertEqual(frappe.db.get_value("User", {"name": ["<", "Adn"]}), "Administrator")
  37. self.assertEqual(frappe.db.get_value("User", {"name": ["<=", "Administrator"]}), "Administrator")
  38. self.assertEqual(
  39. frappe.db.get_value("User", {}, ["Max(name)"], order_by=None),
  40. frappe.db.sql("SELECT Max(name) FROM tabUser")[0][0],
  41. )
  42. self.assertEqual(
  43. frappe.db.get_value("User", {}, "Min(name)", order_by=None),
  44. frappe.db.sql("SELECT Min(name) FROM tabUser")[0][0],
  45. )
  46. self.assertIn(
  47. "for update",
  48. frappe.db.get_value(
  49. "User", Field("name") == "Administrator", for_update=True, run=False
  50. ).lower(),
  51. )
  52. user_doctype = frappe.qb.DocType("User")
  53. self.assertEqual(
  54. frappe.qb.from_(user_doctype).select(user_doctype.name, user_doctype.email).run(),
  55. frappe.db.get_values(
  56. user_doctype,
  57. filters={},
  58. fieldname=[user_doctype.name, user_doctype.email],
  59. order_by=None,
  60. ),
  61. )
  62. self.assertEqual(
  63. frappe.db.sql("""SELECT name FROM `tabUser` WHERE name > 's' ORDER BY MODIFIED DESC""")[0][0],
  64. frappe.db.get_value("User", {"name": [">", "s"]}),
  65. )
  66. self.assertEqual(
  67. frappe.db.sql("""SELECT name FROM `tabUser` WHERE name >= 't' ORDER BY MODIFIED DESC""")[0][0],
  68. frappe.db.get_value("User", {"name": [">=", "t"]}),
  69. )
  70. self.assertEqual(
  71. frappe.db.get_values(
  72. "User",
  73. filters={"name": "Administrator"},
  74. distinct=True,
  75. fieldname="email",
  76. ),
  77. frappe.qb.from_(user_doctype)
  78. .where(user_doctype.name == "Administrator")
  79. .select("email")
  80. .distinct()
  81. .run(),
  82. )
  83. self.assertIn(
  84. "concat_ws",
  85. frappe.db.get_value(
  86. "User",
  87. filters={"name": "Administrator"},
  88. fieldname=Concat_ws(" ", "LastName"),
  89. run=False,
  90. ).lower(),
  91. )
  92. self.assertEqual(
  93. frappe.db.sql("select email from tabUser where name='Administrator' order by modified DESC"),
  94. frappe.db.get_values("User", filters=[["name", "=", "Administrator"]], fieldname="email"),
  95. )
  96. # test multiple orderby's
  97. delimiter = '"' if frappe.db.db_type == "postgres" else "`"
  98. self.assertIn(
  99. "ORDER BY {deli}creation{deli} DESC,{deli}modified{deli} ASC,{deli}name{deli} DESC".format(
  100. deli=delimiter
  101. ),
  102. frappe.db.get_value("DocType", "DocField", order_by="creation desc, modified asc, name", run=0),
  103. )
  104. def test_get_value_limits(self):
  105. # check both dict and list style filters
  106. filters = [{"enabled": 1}, [["enabled", "=", 1]]]
  107. for filter in filters:
  108. self.assertEqual(1, len(frappe.db.get_values("User", filters=filter, limit=1)))
  109. # count of last touched rows as per DB-API 2.0 https://peps.python.org/pep-0249/#rowcount
  110. self.assertGreaterEqual(1, cint(frappe.db._cursor.rowcount))
  111. self.assertEqual(2, len(frappe.db.get_values("User", filters=filter, limit=2)))
  112. self.assertGreaterEqual(2, cint(frappe.db._cursor.rowcount))
  113. # without limits length == count
  114. self.assertEqual(
  115. len(frappe.db.get_values("User", filters=filter)), frappe.db.count("User", filter)
  116. )
  117. frappe.db.get_value("User", filters=filter)
  118. self.assertGreaterEqual(1, cint(frappe.db._cursor.rowcount))
  119. frappe.db.exists("User", filter)
  120. self.assertGreaterEqual(1, cint(frappe.db._cursor.rowcount))
  121. def test_escape(self):
  122. frappe.db.escape("香港濟生堂製藥有限公司 - IT".encode())
  123. def test_get_single_value(self):
  124. # setup
  125. values_dict = {
  126. "Float": 1.5,
  127. "Int": 1,
  128. "Percent": 55.5,
  129. "Currency": 12.5,
  130. "Data": "Test",
  131. "Date": datetime.datetime.now().date(),
  132. "Datetime": datetime.datetime.now(),
  133. "Time": datetime.timedelta(hours=9, minutes=45, seconds=10),
  134. }
  135. test_inputs = [
  136. {"fieldtype": fieldtype, "value": value} for fieldtype, value in values_dict.items()
  137. ]
  138. for fieldtype in values_dict:
  139. create_custom_field(
  140. "Print Settings",
  141. {
  142. "fieldname": f"test_{fieldtype.lower()}",
  143. "label": f"Test {fieldtype}",
  144. "fieldtype": fieldtype,
  145. },
  146. )
  147. # test
  148. for inp in test_inputs:
  149. fieldname = f"test_{inp['fieldtype'].lower()}"
  150. frappe.db.set_value("Print Settings", "Print Settings", fieldname, inp["value"])
  151. self.assertEqual(frappe.db.get_single_value("Print Settings", fieldname), inp["value"])
  152. # teardown
  153. clear_custom_fields("Print Settings")
  154. def test_log_touched_tables(self):
  155. frappe.flags.in_migrate = True
  156. frappe.flags.touched_tables = set()
  157. frappe.db.set_value("System Settings", "System Settings", "backup_limit", 5)
  158. self.assertIn("tabSingles", frappe.flags.touched_tables)
  159. frappe.flags.touched_tables = set()
  160. todo = frappe.get_doc({"doctype": "ToDo", "description": "Random Description"})
  161. todo.save()
  162. self.assertIn("tabToDo", frappe.flags.touched_tables)
  163. frappe.flags.touched_tables = set()
  164. todo.description = "Another Description"
  165. todo.save()
  166. self.assertIn("tabToDo", frappe.flags.touched_tables)
  167. if frappe.db.db_type != "postgres":
  168. frappe.flags.touched_tables = set()
  169. frappe.db.sql("UPDATE tabToDo SET description = 'Updated Description'")
  170. self.assertNotIn("tabToDo SET", frappe.flags.touched_tables)
  171. self.assertIn("tabToDo", frappe.flags.touched_tables)
  172. frappe.flags.touched_tables = set()
  173. todo.delete()
  174. self.assertIn("tabToDo", frappe.flags.touched_tables)
  175. frappe.flags.touched_tables = set()
  176. cf = create_custom_field("ToDo", {"label": "ToDo Custom Field"})
  177. self.assertIn("tabToDo", frappe.flags.touched_tables)
  178. self.assertIn("tabCustom Field", frappe.flags.touched_tables)
  179. if cf:
  180. cf.delete()
  181. frappe.db.commit()
  182. frappe.flags.in_migrate = False
  183. frappe.flags.touched_tables.clear()
  184. def test_db_keywords_as_fields(self):
  185. """Tests if DB keywords work as docfield names. If they're wrapped with grave accents."""
  186. # Using random.choices, picked out a list of 40 keywords for testing
  187. all_keywords = {
  188. "mariadb": [
  189. "CHARACTER",
  190. "DELAYED",
  191. "LINES",
  192. "EXISTS",
  193. "YEAR_MONTH",
  194. "LOCALTIME",
  195. "BOTH",
  196. "MEDIUMINT",
  197. "LEFT",
  198. "BINARY",
  199. "DEFAULT",
  200. "KILL",
  201. "WRITE",
  202. "SQL_SMALL_RESULT",
  203. "CURRENT_TIME",
  204. "CROSS",
  205. "INHERITS",
  206. "SELECT",
  207. "TABLE",
  208. "ALTER",
  209. "CURRENT_TIMESTAMP",
  210. "XOR",
  211. "CASE",
  212. "ALL",
  213. "WHERE",
  214. "INT",
  215. "TO",
  216. "SOME",
  217. "DAY_MINUTE",
  218. "ERRORS",
  219. "OPTIMIZE",
  220. "REPLACE",
  221. "HIGH_PRIORITY",
  222. "VARBINARY",
  223. "HELP",
  224. "IS",
  225. "CHAR",
  226. "DESCRIBE",
  227. "KEY",
  228. ],
  229. "postgres": [
  230. "WORK",
  231. "LANCOMPILER",
  232. "REAL",
  233. "HAVING",
  234. "REPEATABLE",
  235. "DATA",
  236. "USING",
  237. "BIT",
  238. "DEALLOCATE",
  239. "SERIALIZABLE",
  240. "CURSOR",
  241. "INHERITS",
  242. "ARRAY",
  243. "TRUE",
  244. "IGNORE",
  245. "PARAMETER_MODE",
  246. "ROW",
  247. "CHECKPOINT",
  248. "SHOW",
  249. "BY",
  250. "SIZE",
  251. "SCALE",
  252. "UNENCRYPTED",
  253. "WITH",
  254. "AND",
  255. "CONVERT",
  256. "FIRST",
  257. "SCOPE",
  258. "WRITE",
  259. "INTERVAL",
  260. "CHARACTER_SET_SCHEMA",
  261. "ADD",
  262. "SCROLL",
  263. "NULL",
  264. "WHEN",
  265. "TRANSACTION_ACTIVE",
  266. "INT",
  267. "FORTRAN",
  268. "STABLE",
  269. ],
  270. }
  271. created_docs = []
  272. # edit by rushabh: added [:1]
  273. # don't run every keyword! - if one works, they all do
  274. fields = all_keywords[frappe.conf.db_type][:1]
  275. test_doctype = "ToDo"
  276. def add_custom_field(field):
  277. create_custom_field(
  278. test_doctype,
  279. {
  280. "fieldname": field.lower(),
  281. "label": field.title(),
  282. "fieldtype": "Data",
  283. },
  284. )
  285. # Create custom fields for test_doctype
  286. for field in fields:
  287. add_custom_field(field)
  288. # Create documents under that doctype and query them via ORM
  289. for _ in range(10):
  290. docfields = {key.lower(): random_string(10) for key in fields}
  291. doc = frappe.get_doc({"doctype": test_doctype, "description": random_string(20), **docfields})
  292. doc.insert()
  293. created_docs.append(doc.name)
  294. random_field = choice(fields).lower()
  295. random_doc = choice(created_docs)
  296. random_value = random_string(20)
  297. # Testing read
  298. self.assertEqual(
  299. list(frappe.get_all("ToDo", fields=[random_field], limit=1)[0])[0], random_field
  300. )
  301. self.assertEqual(
  302. list(frappe.get_all("ToDo", fields=[f"`{random_field}` as total"], limit=1)[0])[0], "total"
  303. )
  304. # Testing read for distinct and sql functions
  305. self.assertEqual(
  306. list(
  307. frappe.get_all(
  308. "ToDo",
  309. fields=[f"`{random_field}` as total"],
  310. distinct=True,
  311. limit=1,
  312. )[0]
  313. )[0],
  314. "total",
  315. )
  316. self.assertEqual(
  317. list(
  318. frappe.get_all(
  319. "ToDo",
  320. fields=[f"`{random_field}`"],
  321. distinct=True,
  322. limit=1,
  323. )[0]
  324. )[0],
  325. random_field,
  326. )
  327. self.assertEqual(
  328. list(frappe.get_all("ToDo", fields=[f"count(`{random_field}`)"], limit=1)[0])[0],
  329. "count" if frappe.conf.db_type == "postgres" else f"count(`{random_field}`)",
  330. )
  331. # Testing update
  332. frappe.db.set_value(test_doctype, random_doc, random_field, random_value)
  333. self.assertEqual(frappe.db.get_value(test_doctype, random_doc, random_field), random_value)
  334. # Cleanup - delete records and remove custom fields
  335. for doc in created_docs:
  336. frappe.delete_doc(test_doctype, doc)
  337. clear_custom_fields(test_doctype)
  338. def test_savepoints(self):
  339. frappe.db.rollback()
  340. save_point = "todonope"
  341. created_docs = []
  342. failed_docs = []
  343. for _ in range(5):
  344. frappe.db.savepoint(save_point)
  345. doc_gone = frappe.get_doc(doctype="ToDo", description="nope").save()
  346. failed_docs.append(doc_gone.name)
  347. frappe.db.rollback(save_point=save_point)
  348. doc_kept = frappe.get_doc(doctype="ToDo", description="nope").save()
  349. created_docs.append(doc_kept.name)
  350. frappe.db.commit()
  351. for d in failed_docs:
  352. self.assertFalse(frappe.db.exists("ToDo", d))
  353. for d in created_docs:
  354. self.assertTrue(frappe.db.exists("ToDo", d))
  355. def test_savepoints_wrapper(self):
  356. frappe.db.rollback()
  357. class SpecificExc(Exception):
  358. pass
  359. created_docs = []
  360. failed_docs = []
  361. for _ in range(5):
  362. with savepoint(catch=SpecificExc):
  363. doc_kept = frappe.get_doc(doctype="ToDo", description="nope").save()
  364. created_docs.append(doc_kept.name)
  365. with savepoint(catch=SpecificExc):
  366. doc_gone = frappe.get_doc(doctype="ToDo", description="nope").save()
  367. failed_docs.append(doc_gone.name)
  368. raise SpecificExc
  369. frappe.db.commit()
  370. for d in failed_docs:
  371. self.assertFalse(frappe.db.exists("ToDo", d))
  372. for d in created_docs:
  373. self.assertTrue(frappe.db.exists("ToDo", d))
  374. def test_transaction_writes_error(self):
  375. from frappe.database.database import Database
  376. frappe.db.rollback()
  377. frappe.db.MAX_WRITES_PER_TRANSACTION = 1
  378. note = frappe.get_last_doc("ToDo")
  379. note.description = "changed"
  380. with self.assertRaises(frappe.TooManyWritesError) as tmw:
  381. note.save()
  382. frappe.db.MAX_WRITES_PER_TRANSACTION = Database.MAX_WRITES_PER_TRANSACTION
  383. def test_transaction_write_counting(self):
  384. note = frappe.get_doc(doctype="Note", title="transaction counting").insert()
  385. writes = frappe.db.transaction_writes
  386. frappe.db.set_value("Note", note.name, "content", "abc")
  387. self.assertEqual(1, frappe.db.transaction_writes - writes)
  388. writes = frappe.db.transaction_writes
  389. frappe.db.sql(
  390. """
  391. update `tabNote`
  392. set content = 'abc'
  393. where name = %s
  394. """,
  395. note.name,
  396. )
  397. self.assertEqual(1, frappe.db.transaction_writes - writes)
  398. def test_pk_collision_ignoring(self):
  399. # note has `name` generated from title
  400. for _ in range(3):
  401. frappe.get_doc(doctype="Note", title="duplicate name").insert(ignore_if_duplicate=True)
  402. with savepoint():
  403. self.assertRaises(
  404. frappe.DuplicateEntryError, frappe.get_doc(doctype="Note", title="duplicate name").insert
  405. )
  406. # recover transaction to continue other tests
  407. raise Exception
  408. def test_read_only_errors(self):
  409. frappe.db.rollback()
  410. frappe.db.begin(read_only=True)
  411. self.addCleanup(frappe.db.rollback)
  412. with self.assertRaises(frappe.InReadOnlyMode):
  413. frappe.db.set_value("User", "Administrator", "full_name", "Haxor")
  414. def test_exists(self):
  415. dt, dn = "User", "Administrator"
  416. self.assertEqual(frappe.db.exists(dt, dn, cache=True), dn)
  417. self.assertEqual(frappe.db.exists(dt, dn), dn)
  418. self.assertEqual(frappe.db.exists(dt, {"name": ("=", dn)}), dn)
  419. filters = {"doctype": dt, "name": ("like", "Admin%")}
  420. self.assertEqual(frappe.db.exists(filters), dn)
  421. self.assertEqual(filters["doctype"], dt) # make sure that doctype was not removed from filters
  422. self.assertEqual(frappe.db.exists(dt, [["name", "=", dn]]), dn)
  423. def test_bulk_insert(self):
  424. current_count = frappe.db.count("ToDo")
  425. test_body = f"test_bulk_insert - {random_string(10)}"
  426. chunk_size = 10
  427. for number_of_values in (1, 2, 5, 27):
  428. current_transaction_writes = frappe.db.transaction_writes
  429. frappe.db.bulk_insert(
  430. "ToDo",
  431. ["name", "description"],
  432. [[f"ToDo Test Bulk Insert {i}", test_body] for i in range(number_of_values)],
  433. ignore_duplicates=True,
  434. chunk_size=chunk_size,
  435. )
  436. # check that all records were inserted
  437. self.assertEqual(number_of_values, frappe.db.count("ToDo") - current_count)
  438. # check if inserts were done in chunks
  439. expected_number_of_writes = ceil(number_of_values / chunk_size)
  440. self.assertEqual(
  441. expected_number_of_writes, frappe.db.transaction_writes - current_transaction_writes
  442. )
  443. frappe.db.delete("ToDo", {"description": test_body})
  444. def test_count(self):
  445. frappe.db.delete("Note")
  446. frappe.get_doc(doctype="Note", title="note1", content="something").insert()
  447. frappe.get_doc(doctype="Note", title="note2", content="someting else").insert()
  448. # Count with no filtes
  449. self.assertEqual((frappe.db.count("Note")), 2)
  450. # simple filters
  451. self.assertEqual((frappe.db.count("Note", ["title", "=", "note1"])), 1)
  452. frappe.get_doc(doctype="Note", title="note3", content="something other").insert()
  453. # List of list filters with tables
  454. self.assertEqual(
  455. (
  456. frappe.db.count(
  457. "Note",
  458. [["Note", "title", "like", "note%"], ["Note", "content", "like", "some%"]],
  459. )
  460. ),
  461. 3,
  462. )
  463. frappe.db.rollback()
  464. @run_only_if(db_type_is.POSTGRES)
  465. def test_modify_query(self):
  466. from frappe.database.postgres.database import modify_query
  467. query = "select * from `tabtree b` where lft > 13 and rgt <= 16 and name =1.0 and parent = 4134qrsdc and isgroup = 1.00045"
  468. self.assertEqual(
  469. "select * from \"tabtree b\" where lft > '13' and rgt <= '16' and name = '1' and parent = 4134qrsdc and isgroup = 1.00045",
  470. modify_query(query),
  471. )
  472. query = (
  473. 'select locate(".io", "frappe.io"), locate("3", cast(3 as varchar)), locate("3", 3::varchar)'
  474. )
  475. self.assertEqual(
  476. 'select strpos( "frappe.io", ".io"), strpos( cast(3 as varchar), "3"), strpos( 3::varchar, "3")',
  477. modify_query(query),
  478. )
  479. @run_only_if(db_type_is.POSTGRES)
  480. def test_modify_values(self):
  481. from frappe.database.postgres.database import modify_values
  482. self.assertEqual(
  483. {"a": "23", "b": 23.0, "c": 23.0345, "d": "wow", "e": ("1", "2", "3", "abc")},
  484. modify_values({"a": 23, "b": 23.0, "c": 23.0345, "d": "wow", "e": [1, 2, 3, "abc"]}),
  485. )
  486. self.assertEqual(
  487. ["23", 23.0, 23.00004345, "wow", ("1", "2", "3", "abc")],
  488. modify_values((23, 23.0, 23.00004345, "wow", [1, 2, 3, "abc"])),
  489. )
  490. @run_only_if(db_type_is.MARIADB)
  491. class TestDDLCommandsMaria(FrappeTestCase):
  492. test_table_name = "TestNotes"
  493. def setUp(self) -> None:
  494. frappe.db.sql_ddl(
  495. f"""
  496. CREATE TABLE IF NOT EXISTS `tab{self.test_table_name}` (`id` INT NULL, content TEXT, PRIMARY KEY (`id`));
  497. """
  498. )
  499. def tearDown(self) -> None:
  500. frappe.db.sql(f"DROP TABLE tab{self.test_table_name};")
  501. self.test_table_name = "TestNotes"
  502. def test_rename(self) -> None:
  503. new_table_name = f"{self.test_table_name}_new"
  504. frappe.db.rename_table(self.test_table_name, new_table_name)
  505. check_exists = frappe.db.sql(
  506. f"""
  507. SELECT * FROM INFORMATION_SCHEMA.TABLES
  508. WHERE TABLE_NAME = N'tab{new_table_name}';
  509. """
  510. )
  511. self.assertGreater(len(check_exists), 0)
  512. self.assertIn(f"tab{new_table_name}", check_exists[0])
  513. # * so this table is deleted after the rename
  514. self.test_table_name = new_table_name
  515. def test_describe(self) -> None:
  516. self.assertSequenceEqual(
  517. [
  518. ("id", "int(11)", "NO", "PRI", None, ""),
  519. ("content", "text", "YES", "", None, ""),
  520. ],
  521. frappe.db.describe(self.test_table_name),
  522. )
  523. def test_change_type(self) -> None:
  524. def get_table_description():
  525. return frappe.db.sql(f"DESC `tab{self.test_table_name}`")
  526. # try changing from int to varchar
  527. frappe.db.change_column_type("TestNotes", "id", "varchar(255)")
  528. self.assertIn("varchar(255)", get_table_description()[0])
  529. # try changing from varchar to bigint
  530. frappe.db.change_column_type("TestNotes", "id", "bigint")
  531. self.assertIn("bigint(20)", get_table_description()[0])
  532. def test_add_index(self) -> None:
  533. index_name = "test_index"
  534. frappe.db.add_index(self.test_table_name, ["id", "content(50)"], index_name)
  535. indexs_in_table = frappe.db.sql(
  536. f"""
  537. SHOW INDEX FROM tab{self.test_table_name}
  538. WHERE Key_name = '{index_name}';
  539. """
  540. )
  541. self.assertEqual(len(indexs_in_table), 2)
  542. class TestDBSetValue(FrappeTestCase):
  543. @classmethod
  544. def setUpClass(cls):
  545. super().setUpClass()
  546. cls.todo1 = frappe.get_doc(doctype="ToDo", description="test_set_value 1").insert()
  547. cls.todo2 = frappe.get_doc(doctype="ToDo", description="test_set_value 2").insert()
  548. def test_update_single_doctype_field(self):
  549. value = frappe.db.get_single_value("System Settings", "deny_multiple_sessions")
  550. changed_value = not value
  551. frappe.db.set_value(
  552. "System Settings", "System Settings", "deny_multiple_sessions", changed_value
  553. )
  554. current_value = frappe.db.get_single_value("System Settings", "deny_multiple_sessions")
  555. self.assertEqual(current_value, changed_value)
  556. changed_value = not current_value
  557. frappe.db.set_value("System Settings", None, "deny_multiple_sessions", changed_value)
  558. current_value = frappe.db.get_single_value("System Settings", "deny_multiple_sessions")
  559. self.assertEqual(current_value, changed_value)
  560. changed_value = not current_value
  561. frappe.db.set_single_value("System Settings", "deny_multiple_sessions", changed_value)
  562. current_value = frappe.db.get_single_value("System Settings", "deny_multiple_sessions")
  563. self.assertEqual(current_value, changed_value)
  564. def test_update_single_row_single_column(self):
  565. frappe.db.set_value("ToDo", self.todo1.name, "description", "test_set_value change 1")
  566. updated_value = frappe.db.get_value("ToDo", self.todo1.name, "description")
  567. self.assertEqual(updated_value, "test_set_value change 1")
  568. def test_update_single_row_multiple_columns(self):
  569. description, status = "Upated by test_update_single_row_multiple_columns", "Closed"
  570. frappe.db.set_value(
  571. "ToDo",
  572. self.todo1.name,
  573. {
  574. "description": description,
  575. "status": status,
  576. },
  577. update_modified=False,
  578. )
  579. updated_desciption, updated_status = frappe.db.get_value(
  580. "ToDo", filters={"name": self.todo1.name}, fieldname=["description", "status"]
  581. )
  582. self.assertEqual(description, updated_desciption)
  583. self.assertEqual(status, updated_status)
  584. def test_update_multiple_rows_single_column(self):
  585. frappe.db.set_value(
  586. "ToDo", {"description": ("like", "%test_set_value%")}, "description", "change 2"
  587. )
  588. self.assertEqual(frappe.db.get_value("ToDo", self.todo1.name, "description"), "change 2")
  589. self.assertEqual(frappe.db.get_value("ToDo", self.todo2.name, "description"), "change 2")
  590. def test_update_multiple_rows_multiple_columns(self):
  591. todos_to_update = frappe.get_all(
  592. "ToDo",
  593. filters={"description": ("like", "%test_set_value%"), "status": ("!=", "Closed")},
  594. pluck="name",
  595. )
  596. frappe.db.set_value(
  597. "ToDo",
  598. {"description": ("like", "%test_set_value%"), "status": ("!=", "Closed")},
  599. {"status": "Closed", "priority": "High"},
  600. )
  601. test_result = frappe.get_all(
  602. "ToDo", filters={"name": ("in", todos_to_update)}, fields=["status", "priority"]
  603. )
  604. self.assertTrue(all(x for x in test_result if x["status"] == "Closed"))
  605. self.assertTrue(all(x for x in test_result if x["priority"] == "High"))
  606. def test_update_modified_options(self):
  607. self.todo2.reload()
  608. todo = self.todo2
  609. updated_description = f"{todo.description} - by `test_update_modified_options`"
  610. custom_modified = datetime.datetime.fromisoformat(add_days(now(), 10))
  611. custom_modified_by = "user_that_doesnt_exist@example.com"
  612. frappe.db.set_value("ToDo", todo.name, "description", updated_description, update_modified=False)
  613. self.assertEqual(updated_description, frappe.db.get_value("ToDo", todo.name, "description"))
  614. self.assertEqual(todo.modified, frappe.db.get_value("ToDo", todo.name, "modified"))
  615. frappe.db.set_value(
  616. "ToDo",
  617. todo.name,
  618. "description",
  619. "test_set_value change 1",
  620. modified=custom_modified,
  621. modified_by=custom_modified_by,
  622. )
  623. self.assertTupleEqual(
  624. (custom_modified, custom_modified_by),
  625. frappe.db.get_value("ToDo", todo.name, ["modified", "modified_by"]),
  626. )
  627. def test_set_value(self):
  628. self.todo1.reload()
  629. with patch.object(Database, "sql") as sql_called:
  630. frappe.db.set_value(
  631. self.todo1.doctype,
  632. self.todo1.name,
  633. "description",
  634. f"{self.todo1.description}-edit by `test_for_update`",
  635. )
  636. first_query = sql_called.call_args_list[0].args[0]
  637. if frappe.conf.db_type == "postgres":
  638. from frappe.database.postgres.database import modify_query
  639. self.assertTrue(modify_query("UPDATE `tabToDo` SET") in first_query)
  640. if frappe.conf.db_type == "mariadb":
  641. self.assertTrue("UPDATE `tabToDo` SET" in first_query)
  642. def test_cleared_cache(self):
  643. self.todo2.reload()
  644. frappe.get_cached_doc(self.todo2.doctype, self.todo2.name) # init cache
  645. description = f"{self.todo2.description}-edit by `test_cleared_cache`"
  646. frappe.db.set_value(self.todo2.doctype, self.todo2.name, "description", description)
  647. cached_doc = frappe.get_cached_doc(self.todo2.doctype, self.todo2.name)
  648. self.assertEqual(cached_doc.description, description)
  649. def test_update_alias(self):
  650. args = (self.todo1.doctype, self.todo1.name, "description", "Updated by `test_update_alias`")
  651. kwargs = {
  652. "for_update": False,
  653. "modified": None,
  654. "modified_by": None,
  655. "update_modified": True,
  656. "debug": False,
  657. }
  658. self.assertTrue("return self.set_value(" in inspect.getsource(frappe.db.update))
  659. with patch.object(Database, "set_value") as set_value:
  660. frappe.db.update(*args, **kwargs)
  661. set_value.assert_called_once()
  662. set_value.assert_called_with(*args, **kwargs)
  663. @classmethod
  664. def tearDownClass(cls):
  665. frappe.db.rollback()
  666. @run_only_if(db_type_is.POSTGRES)
  667. class TestDDLCommandsPost(FrappeTestCase):
  668. test_table_name = "TestNotes"
  669. def setUp(self) -> None:
  670. frappe.db.sql(
  671. f"""
  672. CREATE TABLE "tab{self.test_table_name}" ("id" INT NULL, content text, PRIMARY KEY ("id"))
  673. """
  674. )
  675. def tearDown(self) -> None:
  676. frappe.db.sql(f'DROP TABLE "tab{self.test_table_name}"')
  677. self.test_table_name = "TestNotes"
  678. def test_rename(self) -> None:
  679. new_table_name = f"{self.test_table_name}_new"
  680. frappe.db.rename_table(self.test_table_name, new_table_name)
  681. check_exists = frappe.db.sql(
  682. f"""
  683. SELECT EXISTS (
  684. SELECT FROM information_schema.tables
  685. WHERE table_name = 'tab{new_table_name}'
  686. );
  687. """
  688. )
  689. self.assertTrue(check_exists[0][0])
  690. # * so this table is deleted after the rename
  691. self.test_table_name = new_table_name
  692. def test_describe(self) -> None:
  693. self.assertSequenceEqual([("id",), ("content",)], frappe.db.describe(self.test_table_name))
  694. def test_change_type(self) -> None:
  695. from psycopg2.errors import DatatypeMismatch
  696. def get_table_description():
  697. return frappe.db.sql(
  698. f"""
  699. SELECT
  700. table_name,
  701. column_name,
  702. data_type
  703. FROM
  704. information_schema.columns
  705. WHERE
  706. table_name = 'tab{self.test_table_name}'"""
  707. )
  708. # try changing from int to varchar
  709. frappe.db.change_column_type(self.test_table_name, "id", "varchar(255)")
  710. self.assertIn("character varying", get_table_description()[0])
  711. # try changing from varchar to int
  712. try:
  713. frappe.db.change_column_type(self.test_table_name, "id", "bigint")
  714. except DatatypeMismatch:
  715. frappe.db.rollback()
  716. # try changing from varchar to int (using cast)
  717. frappe.db.change_column_type(self.test_table_name, "id", "bigint", use_cast=True)
  718. self.assertIn("bigint", get_table_description()[0])
  719. def test_add_index(self) -> None:
  720. index_name = "test_index"
  721. frappe.db.add_index(self.test_table_name, ["id", "content(50)"], index_name)
  722. indexs_in_table = frappe.db.sql(
  723. f"""
  724. SELECT indexname
  725. FROM pg_indexes
  726. WHERE tablename = 'tab{self.test_table_name}'
  727. AND indexname = '{index_name}' ;
  728. """,
  729. )
  730. self.assertEqual(len(indexs_in_table), 1)
  731. def test_sequence_table_creation(self):
  732. from frappe.core.doctype.doctype.test_doctype import new_doctype
  733. dt = new_doctype("autoinc_dt_seq_test", autoname="autoincrement").insert(ignore_permissions=True)
  734. if frappe.db.db_type == "postgres":
  735. self.assertTrue(
  736. frappe.db.sql(
  737. """select sequence_name FROM information_schema.sequences
  738. where sequence_name ilike 'autoinc_dt_seq_test%'"""
  739. )[0][0]
  740. )
  741. else:
  742. self.assertTrue(
  743. frappe.db.sql(
  744. """select data_type FROM information_schema.tables
  745. where table_type = 'SEQUENCE' and table_name like 'autoinc_dt_seq_test%'"""
  746. )[0][0]
  747. )
  748. dt.delete(ignore_permissions=True)
  749. def test_is(self):
  750. user = frappe.qb.DocType("User")
  751. self.assertIn(
  752. "is not null", frappe.db.get_values(user, filters={user.name: ("is", "set")}, run=False).lower()
  753. )
  754. self.assertIn(
  755. "is null", frappe.db.get_values(user, filters={user.name: ("is", "not set")}, run=False).lower()
  756. )
  757. @run_only_if(db_type_is.POSTGRES)
  758. class TestTransactionManagement(FrappeTestCase):
  759. def test_create_proper_transactions(self):
  760. def _get_transaction_id():
  761. return frappe.db.sql("select txid_current()", pluck=True)
  762. self.assertEqual(_get_transaction_id(), _get_transaction_id())
  763. frappe.db.rollback()
  764. self.assertEqual(_get_transaction_id(), _get_transaction_id())
  765. frappe.db.commit()
  766. self.assertEqual(_get_transaction_id(), _get_transaction_id())