# Copyright (c) 2015, JINGROW and Contributors
# License: MIT. See LICENSE
import datetime
from contextlib import contextmanager
from unittest.mock import MagicMock, patch

import jingrow
from jingrow.core.pagetype.pagetype.test_pagetype import new_pagetype
from jingrow.core.page.permission_manager.permission_manager import add, reset, update
from jingrow.custom.pagetype.property_setter.property_setter import make_property_setter
from jingrow.database.utils import DefaultOrderBy
from jingrow.desk.reportview import get_filters_cond
from jingrow.handler import execute_cmd
from jingrow.model.db_query import DatabaseQuery, get_between_date_filter
from jingrow.permissions import add_user_permission, clear_user_permissions_for_pagetype
from jingrow.query_builder import Column
from jingrow.tests.test_query_builder import db_type_is, run_only_if
from jingrow.tests.utils import JingrowTestCase
from jingrow.utils.testutils import add_custom_field, clear_custom_fields

test_dependencies = ["User", "Blog Post", "Blog Category", "Blogger"]


@contextmanager
def setup_test_user(set_user=False):
	test_user = jingrow.get_pg("User", "test@example.com")
	user_roles = jingrow.get_roles()
	test_user.remove_roles(*user_roles)
	test_user.add_roles("Blogger")

	if set_user:
		jingrow.set_user(test_user.name)

	yield test_user

	test_user.remove_roles("Blogger")
	test_user.add_roles(*user_roles)


@contextmanager
def setup_patched_blog_post():
	add_child_table_to_blog_post()
	make_property_setter("Blog Post", "published", "permlevel", 1, "Int")
	reset("Blog Post")
	add("Blog Post", "Website Manager", 1)
	update("Blog Post", "Website Manager", 1, "write", 1)
	yield


class TestDBQuery(JingrowTestCase):
	def setUp(self):
		jingrow.set_user("Administrator")

	def test_basic(self):
		self.assertTrue({"name": "PageType"} in DatabaseQuery("PageType").execute(limit_page_length=None))

	def test_extract_tables(self):
		db_query = DatabaseQuery("PageType")
		add_custom_field("PageType", "test_tab_field", "Data")

		db_query.fields = [
			"tabNote.creation",
			"test_tab_field",
			"tabPageType.test_tab_field",
		]
		db_query.extract_tables()
		self.assertIn("`tabNote`", db_query.tables)
		self.assertIn("`tabPageType`", db_query.tables)
		self.assertNotIn("test_tab_field", db_query.tables)

		clear_custom_fields("PageType")

	def test_child_table_field_syntax(self):
		note = jingrow.get_pg(
			pagetype="Note",
			title=f"Test {jingrow.utils.random_string(8)}",
			content="test",
			seen_by=[{"user": "Administrator"}],
		).insert()
		result = jingrow.get_all(
			"Note",
			filters={"name": note.name},
			fields=["name", "seen_by.user as seen_by"],
			limit=1,
		)
		self.assertEqual(result[0].seen_by, "Administrator")
		note.delete()

	def test_child_table_join(self):
		jingrow.delete_pg_if_exists("PageType", "Parent PageType 1")
		jingrow.delete_pg_if_exists("PageType", "Parent PageType 2")
		jingrow.delete_pg_if_exists("PageType", "Child PageType")
		# child table
		jingrow.get_pg(
			{
				"pagetype": "PageType",
				"name": "Child PageType",
				"module": "Custom",
				"custom": 1,
				"istable": 1,
				"fields": [
					{"label": "Title", "fieldname": "title", "fieldtype": "Data"},
				],
			}
		).insert()
		# pagetype 1
		jingrow.get_pg(
			{
				"pagetype": "PageType",
				"name": "Parent PageType 1",
				"module": "Custom",
				"autoname": "Prompt",
				"custom": 1,
				"fields": [
					{"label": "Title", "fieldname": "title", "fieldtype": "Data"},
					{
						"label": "Table Field 1",
						"fieldname": "child",
						"fieldtype": "Table",
						"options": "Child PageType",
					},
				],
				"permissions": [{"role": "System Manager"}],
			}
		).insert()
		# pagetype 2
		jingrow.get_pg(
			{
				"pagetype": "PageType",
				"name": "Parent PageType 2",
				"autoname": "Prompt",
				"module": "Custom",
				"custom": 1,
				"fields": [
					{"label": "Title", "fieldname": "title", "fieldtype": "Data"},
					{
						"label": "Table Field 1",
						"fieldname": "child",
						"fieldtype": "Table",
						"options": "Child PageType",
					},
				],
				"permissions": [{"role": "System Manager"}],
			}
		).insert()

		# clear records
		jingrow.db.delete("Parent PageType 1")
		jingrow.db.delete("Parent PageType 2")
		jingrow.db.delete("Child PageType")

		# insert records
		jingrow.get_pg(
			pagetype="Parent PageType 1",
			title="test",
			child=[
				{"title": "parent 1 child record 1"},
				{"title": "parent 1 child record 2"},
			],
			__newname="test_parent",
		).insert(ignore_if_duplicate=True)
		jingrow.get_pg(
			pagetype="Parent PageType 2",
			title="test",
			child=[{"title": "parent 2 child record 1"}],
			__newname="test_parent",
		).insert(ignore_if_duplicate=True)

		# test query
		results1 = jingrow.get_all("Parent PageType 1", fields=["name", "child.title as child_title"])
		results2 = jingrow.get_all("Parent PageType 2", fields=["name", "child.title as child_title"])
		# check both parents have same name
		self.assertEqual(results1[0].name, results2[0].name)
		# check both parents have different number of child records
		self.assertEqual(len(results1), 2)
		self.assertEqual(len(results2), 1)
		parent1_children = [result.child_title for result in results1]
		self.assertIn("parent 1 child record 1", parent1_children)
		self.assertIn("parent 1 child record 2", parent1_children)
		self.assertEqual(results2[0].child_title, "parent 2 child record 1")

	def test_link_field_syntax(self):
		todo = jingrow.get_pg(pagetype="ToDo", description="Test ToDo", allocated_to="Administrator").insert()
		result = jingrow.get_all(
			"ToDo",
			filters={"name": todo.name},
			fields=["name", "allocated_to.email as allocated_user_email"],
			limit=1,
		)
		self.assertEqual(result[0].allocated_user_email, "admin@example.com")
		todo.delete()

	def test_build_match_conditions(self):
		clear_user_permissions_for_pagetype("Blog Post", "test2@example.com")

		test2user = jingrow.get_pg("User", "test2@example.com")
		test2user.add_roles("Blogger")
		jingrow.set_user("test2@example.com")

		# this will get match conditions for Blog Post
		build_match_conditions = DatabaseQuery("Blog Post").build_match_conditions

		# Before any user permission is applied
		# get as filters
		self.assertEqual(build_match_conditions(as_condition=False), [])
		# get as conditions
		self.assertEqual(build_match_conditions(as_condition=True), "")

		add_user_permission("Blog Post", "-test-blog-post", "test2@example.com", True)
		add_user_permission("Blog Post", "-test-blog-post-1", "test2@example.com", True)

		# After applying user permission
		# get as filters
		self.assertTrue(
			{"Blog Post": ["-test-blog-post-1", "-test-blog-post"]}
			in build_match_conditions(as_condition=False)
		)
		# get as conditions
		if jingrow.db.db_type == "mariadb":
			assertion_string = """(((ifnull(`tabBlog Post`.`name`, '')='' or `tabBlog Post`.`name` in ('-test-blog-post-1', '-test-blog-post'))))"""
		else:
			assertion_string = """(((ifnull(cast(`tabBlog Post`.`name` as varchar), '')='' or cast(`tabBlog Post`.`name` as varchar) in ('-test-blog-post-1', '-test-blog-post'))))"""

		self.assertEqual(build_match_conditions(as_condition=True), assertion_string)

		jingrow.set_user("Administrator")

	def test_fields(self):
		self.assertTrue(
			{"name": "PageType", "issingle": 0}
			in DatabaseQuery("PageType").execute(fields=["name", "issingle"], limit_page_length=None)
		)

	def test_filters_1(self):
		self.assertFalse(
			{"name": "PageType"}
			in DatabaseQuery("PageType").execute(filters=[["PageType", "name", "like", "J%"]])
		)

	def test_filters_2(self):
		self.assertFalse(
			{"name": "PageType"} in DatabaseQuery("PageType").execute(filters=[{"name": ["like", "J%"]}])
		)

	def test_filters_3(self):
		self.assertFalse(
			{"name": "PageType"} in DatabaseQuery("PageType").execute(filters={"name": ["like", "J%"]})
		)

	def test_filters_4(self):
		self.assertTrue(
			{"name": "PageField"} in DatabaseQuery("PageType").execute(filters={"name": "PageField"})
		)

	def test_in_not_in_filters(self):
		self.assertFalse(DatabaseQuery("PageType").execute(filters={"name": ["in", None]}))
		self.assertTrue(
			{"name": "PageType"} in DatabaseQuery("PageType").execute(filters={"name": ["not in", None]})
		)

		for result in [{"name": "PageType"}, {"name": "PageField"}]:
			self.assertTrue(
				result in DatabaseQuery("PageType").execute(filters={"name": ["in", "PageType,PageField"]})
			)

		for result in [{"name": "PageType"}, {"name": "PageField"}]:
			self.assertFalse(
				result in DatabaseQuery("PageType").execute(filters={"name": ["not in", "PageType,PageField"]})
			)

	def test_none_filter(self):
		query = jingrow.qb.get_query("PageType", fields="name", filters={"restrict_to_domain": None})
		sql = str(query).replace("`", "").replace('"', "")
		condition = "restrict_to_domain IS NULL"
		self.assertIn(condition, sql)

	def test_or_filters(self):
		data = DatabaseQuery("PageField").execute(
			filters={"parent": "PageType"},
			fields=["fieldname", "fieldtype"],
			or_filters=[{"fieldtype": "Table"}, {"fieldtype": "Select"}],
		)

		self.assertTrue({"fieldtype": "Table", "fieldname": "fields"} in data)
		self.assertTrue({"fieldtype": "Select", "fieldname": "document_type"} in data)
		self.assertFalse({"fieldtype": "Check", "fieldname": "issingle"} in data)

	def test_between_filters(self):
		"""test case to check between filter for date fields"""
		jingrow.db.delete("Event")

		# create events to test the between operator filter
		todays_event = create_event()
		event1 = create_event(starts_on="2016-07-05 23:59:59")
		event2 = create_event(starts_on="2016-07-06 00:00:00")
		event3 = create_event(starts_on="2016-07-07 23:59:59")
		event4 = create_event(starts_on="2016-07-08 00:00:00")

		# if the values are not passed in filters then event should be filter as current datetime
		data = DatabaseQuery("Event").execute(filters={"starts_on": ["between", None]}, fields=["name"])

		self.assertTrue({"name": event1.name} not in data)

		# if both from and to_date values are passed
		data = DatabaseQuery("Event").execute(
			filters={"starts_on": ["between", ["2016-07-06", "2016-07-07"]]},
			fields=["name"],
		)

		self.assertIn({"name": event2.name}, data)
		self.assertIn({"name": event3.name}, data)
		self.assertNotIn({"name": event1.name}, data)
		self.assertNotIn({"name": event4.name}, data)

		# if only one value is passed in the filter
		data = DatabaseQuery("Event").execute(
			filters={"starts_on": ["between", ["2016-07-07"]]}, fields=["name"]
		)

		self.assertIn({"name": event3.name}, data)
		self.assertIn({"name": event4.name}, data)
		self.assertIn({"name": todays_event.name}, data)
		self.assertNotIn({"name": event1.name}, data)
		self.assertNotIn({"name": event2.name}, data)

		# test between is formatted for creation column
		data = DatabaseQuery("Event").execute(
			filters={"creation": ["between", ["2016-07-06", "2016-07-07"]]},
			fields=["name"],
		)

	def test_between_filters_date_bounds(self):
		date_df = jingrow._dict(fieldtype="Date")
		datetime_df = jingrow._dict(fieldtype="Datetime")
		today = jingrow.utils.nowdate()

		# No filters -> assumes today
		cond = get_between_date_filter("", date_df)
		self.assertQueryEqual(cond, f"'{today}' AND '{today}'")

		# One filter assumes "from" bound and to is today
		start = "2021-01-01"
		cond = get_between_date_filter([start], date_df)
		self.assertQueryEqual(cond, f"'{start}' AND '{today}'")

		# both date filters are applied
		start = "2021-01-01"
		end = "2022-01-02"
		cond = get_between_date_filter([start, end], date_df)
		self.assertQueryEqual(cond, f"'{start}' AND '{end}'")

		# single date should include entire day
		start = "2021-01-01"
		cond = get_between_date_filter([start, start], datetime_df)
		self.assertQueryEqual(cond, f"'{start} 00:00:00.000000' AND '{start} 23:59:59.999999'")

		# datetime field on datetime type should remain same
		start = "2021-01-01 01:01:00"
		end = "2022-01-02 12:23:43"
		cond = get_between_date_filter([start, end], datetime_df)
		self.assertQueryEqual(cond, f"'{start}.000000' AND '{end}.000000'")

	def test_ignore_permissions_for_get_filters_cond(self):
		jingrow.set_user("test2@example.com")
		self.assertRaises(jingrow.PermissionError, get_filters_cond, "PageType", dict(istable=1), [])
		self.assertTrue(get_filters_cond("PageType", dict(istable=1), [], ignore_permissions=True))
		jingrow.set_user("Administrator")

	def test_query_fields_sanitizer(self):
		self.assertRaises(
			jingrow.DataError,
			DatabaseQuery("PageType").execute,
			fields=["name", "issingle, version()"],
			limit_start=0,
			limit_page_length=1,
		)

		self.assertRaises(
			jingrow.DataError,
			DatabaseQuery("PageType").execute,
			fields=[
				"name",
				"issingle, IF(issingle=1, (select name from tabUser), count(name))",
			],
			limit_start=0,
			limit_page_length=1,
		)

		self.assertRaises(
			jingrow.DataError,
			DatabaseQuery("PageType").execute,
			fields=["name", "issingle, (select count(*) from tabSessions)"],
			limit_start=0,
			limit_page_length=1,
		)

		self.assertRaises(
			jingrow.DataError,
			DatabaseQuery("PageType").execute,
			fields=["name", "issingle, SELECT LOCATE('', `tabUser`.`user`) AS user;"],
			limit_start=0,
			limit_page_length=1,
		)

		self.assertRaises(
			jingrow.DataError,
			DatabaseQuery("PageType").execute,
			fields=[
				"name",
				"issingle, IF(issingle=1, (SELECT name from tabUser), count(*))",
			],
			limit_start=0,
			limit_page_length=1,
		)

		self.assertRaises(
			jingrow.DataError,
			DatabaseQuery("PageType").execute,
			fields=["name", "issingle ''"],
			limit_start=0,
			limit_page_length=1,
		)

		self.assertRaises(
			jingrow.DataError,
			DatabaseQuery("PageType").execute,
			fields=["name", "issingle,'"],
			limit_start=0,
			limit_page_length=1,
		)

		self.assertRaises(
			jingrow.DataError,
			DatabaseQuery("PageType").execute,
			fields=["name", "select * from tabSessions"],
			limit_start=0,
			limit_page_length=1,
		)

		self.assertRaises(
			jingrow.DataError,
			DatabaseQuery("PageType").execute,
			fields=["name", "issingle from --"],
			limit_start=0,
			limit_page_length=1,
		)

		self.assertRaises(
			jingrow.DataError,
			DatabaseQuery("PageType").execute,
			fields=["name", "issingle from tabPageType order by 2 --"],
			limit_start=0,
			limit_page_length=1,
		)

		self.assertRaises(
			jingrow.DataError,
			DatabaseQuery("PageType").execute,
			fields=["name", "1' UNION SELECT * FROM __Auth --"],
			limit_start=0,
			limit_page_length=1,
		)

		self.assertRaises(
			jingrow.DataError,
			DatabaseQuery("PageType").execute,
			fields=["@@version"],
			limit_start=0,
			limit_page_length=1,
		)

		data = DatabaseQuery("PageType").execute(
			fields=["count(`name`) as count"], limit_start=0, limit_page_length=1
		)
		self.assertTrue("count" in data[0])

		data = DatabaseQuery("PageType").execute(
			fields=["name", "issingle", "locate('', name) as _relevance"],
			limit_start=0,
			limit_page_length=1,
		)
		self.assertTrue("_relevance" in data[0])

		data = DatabaseQuery("PageType").execute(
			fields=["name", "issingle", "date(creation) as creation"],
			limit_start=0,
			limit_page_length=1,
		)
		self.assertTrue("creation" in data[0])

		if jingrow.db.db_type != "postgres":
			# datediff function does not exist in postgres
			data = DatabaseQuery("PageType").execute(
				fields=[
					"name",
					"issingle",
					"datediff(modified, creation) as date_diff",
				],
				limit_start=0,
				limit_page_length=1,
			)
			self.assertTrue("date_diff" in data[0])

		with self.assertRaises(jingrow.DataError):
			DatabaseQuery("PageType").execute(
				fields=[
					"name",
					"issingle",
					"if (issingle=1, (select name from tabUser), count(name))",
				],
				limit_start=0,
				limit_page_length=1,
			)

		with self.assertRaises(jingrow.DataError):
			DatabaseQuery("PageType").execute(
				fields=[
					"name",
					"issingle",
					"if(issingle=1, (select name from tabUser), count(name))",
				],
				limit_start=0,
				limit_page_length=1,
			)

		with self.assertRaises(jingrow.DataError):
			DatabaseQuery("PageType").execute(
				fields=[
					"name",
					"issingle",
					"( select name from `tabUser` where `tabPageType`.owner = `tabUser`.name )",
				],
				limit_start=0,
				limit_page_length=1,
				ignore_permissions=True,
			)

		with self.assertRaises(jingrow.DataError):
			DatabaseQuery("PageType").execute(
				fields=[
					"name",
					"issingle",
					"(select name from `tabUser` where `tabPageType`.owner = `tabUser`.name )",
				],
				limit_start=0,
				limit_page_length=1,
			)

	def test_nested_permission(self):
		jingrow.set_user("Administrator")
		create_nested_pagetype()
		create_nested_pagetype_records()
		clear_user_permissions_for_pagetype("Nested PageType")

		# user permission for only one root folder
		add_user_permission("Nested PageType", "Level 1 A", "test2@example.com")

		from jingrow.core.page.permission_manager.permission_manager import update

		# to avoid if_owner filter
		update("Nested PageType", "All", 0, "if_owner", 0)

		jingrow.set_user("test2@example.com")
		data = DatabaseQuery("Nested PageType").execute()

		# children of root folder (for which we added user permission) should be accessible
		self.assertTrue({"name": "Level 2 A"} in data)
		self.assertTrue({"name": "Level 2 A"} in data)

		# other folders should not be accessible
		self.assertFalse({"name": "Level 1 B"} in data)
		self.assertFalse({"name": "Level 2 B"} in data)
		update("Nested PageType", "All", 0, "if_owner", 1)
		jingrow.set_user("Administrator")

	def test_filter_sanitizer(self):
		self.assertRaises(
			jingrow.DataError,
			DatabaseQuery("PageType").execute,
			fields=["name"],
			filters={"istable,": 1},
			limit_start=0,
			limit_page_length=1,
		)

		self.assertRaises(
			jingrow.DataError,
			DatabaseQuery("PageType").execute,
			fields=["name"],
			filters={"editable_grid,": 1},
			or_filters={"istable,": 1},
			limit_start=0,
			limit_page_length=1,
		)

		self.assertRaises(
			jingrow.DataError,
			DatabaseQuery("PageType").execute,
			fields=["name"],
			filters={"editable_grid,": 1},
			or_filters=[["PageType", "istable,", "=", 1]],
			limit_start=0,
			limit_page_length=1,
		)

		self.assertRaises(
			jingrow.DataError,
			DatabaseQuery("PageType").execute,
			fields=["name"],
			filters={"editable_grid,": 1},
			or_filters=[
				["PageType", "istable", "=", 1],
				["PageType", "beta and 1=1", "=", 0],
			],
			limit_start=0,
			limit_page_length=1,
		)

		out = DatabaseQuery("PageType").execute(
			fields=["name"],
			filters={"editable_grid": 1, "module": "Core"},
			or_filters=[["PageType", "istable", "=", 1]],
			order_by="creation",
		)
		self.assertTrue("PageField" in [d["name"] for d in out])

		out = DatabaseQuery("PageType").execute(
			fields=["name"],
			filters={"issingle": 1},
			or_filters=[["PageType", "module", "=", "Core"]],
			order_by="creation",
		)
		self.assertTrue("Role Permission for Page and Report" in [d["name"] for d in out])

		out = DatabaseQuery("PageType").execute(
			fields=["name"],
			filters={"track_changes": 1, "module": "Core"},
			order_by="creation",
		)
		self.assertTrue("File" in [d["name"] for d in out])

		out = DatabaseQuery("PageType").execute(
			fields=["name"],
			filters=[
				["PageType", "ifnull(track_changes, 0)", "=", 0],
				["PageType", "module", "=", "Core"],
			],
			order_by="creation",
		)
		self.assertTrue("DefaultValue" in [d["name"] for d in out])

	def test_order_by_group_by_sanitizer(self):
		# order by with blacklisted function
		with self.assertRaises(jingrow.ValidationError):
			DatabaseQuery("PageType").execute(
				fields=["name"],
				order_by="sleep (1) asc",
			)

		# group by with blacklisted function
		with self.assertRaises(jingrow.ValidationError):
			DatabaseQuery("PageType").execute(
				fields=["name"],
				group_by="SLEEP(0)",
			)

		# sub query in order by
		with self.assertRaises(jingrow.ValidationError):
			DatabaseQuery("PageType").execute(
				fields=["name"],
				order_by="(select rank from tabRankedPageTypes where tabRankedPageTypes.name = tabPageType.name) asc",
			)

		# validate allowed usage
		DatabaseQuery("PageType").execute(
			fields=["name"],
			order_by="name asc",
		)
		DatabaseQuery("PageType").execute(
			fields=["name"],
			order_by="name asc",
			group_by="name",
		)

		# check mariadb specific syntax
		if jingrow.db.db_type == "mariadb":
			DatabaseQuery("PageType").execute(
				fields=["name"],
				order_by="timestamp(modified)",
			)

	def test_of_not_of_descendant_ancestors(self):
		jingrow.set_user("Administrator")
		clear_user_permissions_for_pagetype("Nested PageType")

		# in descendants filter
		data = jingrow.get_all("Nested PageType", {"name": ("descendants of", "Level 2 A")})
		self.assertTrue({"name": "Level 3 A"} in data)

		data = jingrow.get_all("Nested PageType", {"name": ("descendants of", "Level 1 A")})
		self.assertTrue({"name": "Level 3 A"} in data)
		self.assertTrue({"name": "Level 2 A"} in data)
		self.assertFalse({"name": "Level 2 B"} in data)
		self.assertFalse({"name": "Level 1 B"} in data)
		self.assertFalse({"name": "Level 1 A"} in data)
		self.assertFalse({"name": "Root"} in data)

		# in ancestors of filter
		data = jingrow.get_all("Nested PageType", {"name": ("ancestors of", "Level 2 A")})
		self.assertFalse({"name": "Level 3 A"} in data)
		self.assertFalse({"name": "Level 2 A"} in data)
		self.assertFalse({"name": "Level 2 B"} in data)
		self.assertFalse({"name": "Level 1 B"} in data)
		self.assertTrue({"name": "Level 1 A"} in data)
		self.assertTrue({"name": "Root"} in data)

		data = jingrow.get_all("Nested PageType", {"name": ("ancestors of", "Level 1 A")})
		self.assertFalse({"name": "Level 3 A"} in data)
		self.assertFalse({"name": "Level 2 A"} in data)
		self.assertFalse({"name": "Level 2 B"} in data)
		self.assertFalse({"name": "Level 1 B"} in data)
		self.assertFalse({"name": "Level 1 A"} in data)
		self.assertTrue({"name": "Root"} in data)

		# not descendants filter
		data = jingrow.get_all("Nested PageType", {"name": ("not descendants of", "Level 2 A")})
		self.assertFalse({"name": "Level 3 A"} in data)
		self.assertTrue({"name": "Level 2 A"} in data)
		self.assertTrue({"name": "Level 2 B"} in data)
		self.assertTrue({"name": "Level 1 A"} in data)
		self.assertTrue({"name": "Root"} in data)

		data = jingrow.get_all("Nested PageType", {"name": ("not descendants of", "Level 1 A")})
		self.assertFalse({"name": "Level 3 A"} in data)
		self.assertFalse({"name": "Level 2 A"} in data)
		self.assertTrue({"name": "Level 2 B"} in data)
		self.assertTrue({"name": "Level 1 B"} in data)
		self.assertTrue({"name": "Level 1 A"} in data)
		self.assertTrue({"name": "Root"} in data)

		# not ancestors of filter
		data = jingrow.get_all("Nested PageType", {"name": ("not ancestors of", "Level 2 A")})
		self.assertTrue({"name": "Level 3 A"} in data)
		self.assertTrue({"name": "Level 2 A"} in data)
		self.assertTrue({"name": "Level 2 B"} in data)
		self.assertTrue({"name": "Level 1 B"} in data)
		self.assertTrue({"name": "Level 1 A"} not in data)
		self.assertTrue({"name": "Root"} not in data)

		data = jingrow.get_all("Nested PageType", {"name": ("not ancestors of", "Level 1 A")})
		self.assertTrue({"name": "Level 3 A"} in data)
		self.assertTrue({"name": "Level 2 A"} in data)
		self.assertTrue({"name": "Level 2 B"} in data)
		self.assertTrue({"name": "Level 1 B"} in data)
		self.assertTrue({"name": "Level 1 A"} in data)
		self.assertFalse({"name": "Root"} in data)

		data = jingrow.get_all("Nested PageType", {"name": ("ancestors of", "Root")})
		self.assertTrue(len(data) == 0)
		self.assertTrue(
			len(jingrow.get_all("Nested PageType", {"name": ("not ancestors of", "Root")}))
			== len(jingrow.get_all("Nested PageType"))
		)

	def test_is_set_is_not_set(self):
		res = DatabaseQuery("PageType").execute(filters={"autoname": ["is", "not set"]})
		self.assertTrue({"name": "Integration Request"} in res)
		self.assertTrue({"name": "User"} in res)
		self.assertFalse({"name": "Blogger"} in res)

		res = DatabaseQuery("PageType").execute(filters={"autoname": ["is", "set"]})
		self.assertTrue({"name": "PageField"} in res)
		self.assertTrue({"name": "Prepared Report"} in res)
		self.assertFalse({"name": "Property Setter"} in res)

		jingrow.db.set_value("PageType", "Property Setter", "autoname", None, update_modified=False)

		res = DatabaseQuery("PageType").execute(filters={"autoname": ["is", "set"]})
		self.assertFalse({"name": "Property Setter"} in res)

	def test_set_field_tables(self):
		# Tests _in_standard_sql_methods method in test_set_field_tables
		# The following query will break if the above method is broken
		jingrow.db.get_list(
			"Web Form",
			filters=[["Web Form Field", "reqd", "=", 1]],
			fields=["count(*) as count"],
			order_by="count desc",
			limit=50,
		)

	def test_virtual_field_get_list(self):
		try:
			jingrow.get_list("Prepared Report", ["*"])
			jingrow.get_list("Scheduled Job Type", ["*"])
		except Exception:
			print(jingrow.get_traceback())
			self.fail("get_list not working with virtual field")

	def test_pluck_name(self):
		names = DatabaseQuery("PageType").execute(filters={"name": "PageType"}, pluck="name")
		self.assertEqual(names, ["PageType"])

	def test_pluck_any_field(self):
		owners = DatabaseQuery("PageType").execute(filters={"name": "PageType"}, pluck="owner")
		self.assertEqual(owners, ["Administrator"])

	def test_prepare_select_args(self):
		# jingrow.get_all inserts modified field into order_by clause
		# test to make sure this is inserted into select field when postgres
		pagetypes = jingrow.get_all(
			"PageType",
			filters={"pagestatus": 0, "document_type": ("!=", "")},
			group_by="document_type",
			fields=["document_type", "sum(is_submittable) as is_submittable"],
			limit=1,
			as_list=True,
		)
		if jingrow.conf.db_type == "mariadb":
			self.assertTrue(len(pagetypes[0]) == 2)
		else:
			self.assertTrue(len(pagetypes[0]) == 3)
			self.assertTrue(isinstance(pagetypes[0][2], datetime.datetime))

	def test_column_comparison(self):
		"""Test DatabaseQuery.execute to test column comparison"""
		users_unedited = jingrow.get_all(
			"User",
			filters={"creation": Column("modified")},
			fields=["name", "creation", "modified"],
			limit=1,
		)
		users_edited = jingrow.get_all(
			"User",
			filters={"creation": ("!=", Column("modified"))},
			fields=["name", "creation", "modified"],
			limit=1,
		)

		self.assertEqual(users_unedited[0].modified, users_unedited[0].creation)
		self.assertNotEqual(users_edited[0].modified, users_edited[0].creation)

	def test_permlevel_fields(self):
		with setup_patched_blog_post(), setup_test_user(set_user=True):
			data = jingrow.get_list(
				"Blog Post",
				filters={"published": 1},
				fields=["name", "published"],
				limit=1,
			)
			self.assertFalse("published" in data[0])
			self.assertTrue("name" in data[0])
			self.assertEqual(len(data[0]), 1)

			data = jingrow.get_list(
				"Blog Post",
				filters={"published": 1},
				fields=["name", "`published`"],
				limit=1,
			)
			self.assertFalse("published" in data[0])
			self.assertTrue("name" in data[0])
			self.assertEqual(len(data[0]), 1)

			data = jingrow.get_list(
				"Blog Post",
				filters={"published": 1},
				fields=["name", "`tabBlog Post`.`published`"],
				limit=1,
			)
			self.assertFalse("published" in data[0])
			self.assertTrue("name" in data[0])
			self.assertEqual(len(data[0]), 1)

			data = jingrow.get_list(
				"Blog Post",
				filters={"published": 1},
				fields=["name", "`tabTest Child`.`test_field`"],
				limit=1,
			)
			self.assertFalse("test_field" in data[0])
			self.assertTrue("name" in data[0])
			self.assertEqual(len(data[0]), 1)

			data = jingrow.get_list(
				"Blog Post",
				filters={"published": 1},
				fields=["name", "MAX(`published`)"],
				limit=1,
			)
			self.assertTrue("name" in data[0])
			self.assertEqual(len(data[0]), 1)

			data = jingrow.get_list(
				"Blog Post",
				filters={"published": 1},
				fields=["name", "LAST(published)"],
				limit=1,
			)
			self.assertTrue("name" in data[0])
			self.assertEqual(len(data[0]), 1)

			data = jingrow.get_list(
				"Blog Post",
				filters={"published": 1},
				fields=["name", "MAX(`modified`)"],
				limit=1,
				order_by=None,
				group_by="name",
			)
			self.assertEqual(len(data[0]), 2)

			data = jingrow.get_list(
				"Blog Post",
				filters={"published": 1},
				fields=["name", "now() abhi"],
				limit=1,
			)
			self.assertIsInstance(data[0]["abhi"], datetime.datetime)
			self.assertEqual(len(data[0]), 2)

			data = jingrow.get_list(
				"Blog Post",
				filters={"published": 1},
				fields=["name", "'LABEL'"],
				limit=1,
			)
			self.assertTrue("name" in data[0])
			self.assertTrue("LABEL" in data[0].values())
			self.assertEqual(len(data[0]), 2)

			data = jingrow.get_list(
				"Blog Post",
				filters={"published": 1},
				fields=["name", "COUNT(*) as count"],
				limit=1,
				order_by=None,
				group_by="name",
			)
			self.assertTrue("count" in data[0])
			self.assertEqual(len(data[0]), 2)

			data = jingrow.get_list(
				"Blog Post",
				filters={"published": 1},
				fields=["name", "COUNT(*) count"],
				limit=1,
				order_by=None,
				group_by="name",
			)
			self.assertTrue("count" in data[0])
			self.assertEqual(len(data[0]), 2)

			data = jingrow.get_list(
				"Blog Post",
				fields=[
					"name",
					"blogger.full_name as blogger_full_name",
					"blog_category.description",
				],
				limit=1,
			)
			self.assertTrue("name" in data[0])
			self.assertTrue("blogger_full_name" in data[0])
			self.assertTrue("description" in data[0])

	def test_cast_name(self):
		from jingrow.core.pagetype.pagetype.test_pagetype import new_pagetype

		jingrow.delete_pg_if_exists("PageType", "autoinc_dt_test")
		dt = new_pagetype("autoinc_dt_test", autoname="autoincrement").insert(ignore_permissions=True)

		query = DatabaseQuery("autoinc_dt_test").execute(
			fields=[
				"locate('1', `tabautoinc_dt_test`.`name`)",
				"name",
				"locate('1', name)",
			],
			filters={"name": 1},
			run=False,
		)

		if jingrow.db.db_type == "postgres":
			self.assertTrue('strpos( cast("tabautoinc_dt_test"."name" as varchar), \'1\')' in query)
			self.assertTrue("strpos( cast(name as varchar), '1')" in query)
			self.assertTrue('where cast("tabautoinc_dt_test"."name" as varchar) = \'1\'' in query)
		else:
			self.assertTrue("locate('1', `tabautoinc_dt_test`.`name`)" in query)
			self.assertTrue("locate('1', name)" in query)
			self.assertTrue("where `tabautoinc_dt_test`.`name` = 1" in query)

		dt.delete(ignore_permissions=True)

	def test_fieldname_starting_with_int(self):
		from jingrow.core.pagetype.pagetype.test_pagetype import new_pagetype

		jingrow.delete_pg_if_exists("PageType", "dt_with_int_named_fieldname")
		jingrow.delete_pg_if_exists("PageType", "table_dt")

		table_dt = new_pagetype(
			"table_dt",
			istable=1,
			fields=[{"label": "1field", "fieldname": "2field", "fieldtype": "Data"}],
		).insert()

		dt = new_pagetype(
			"dt_with_int_named_fieldname",
			fields=[
				{"label": "1field", "fieldname": "1field", "fieldtype": "Data"},
				{
					"label": "2table_field",
					"fieldname": "2table_field",
					"fieldtype": "Table",
					"options": table_dt.name,
				},
			],
		).insert(ignore_permissions=True)

		dt_data = jingrow.get_pg({"pagetype": "dt_with_int_named_fieldname", "1field": "10"}).insert(
			ignore_permissions=True
		)

		query = DatabaseQuery("dt_with_int_named_fieldname")
		self.assertTrue(query.execute(filters={"1field": "10"}))
		self.assertTrue(query.execute(filters={"1field": ["like", "1%"]}))
		self.assertTrue(query.execute(filters={"1field": ["in", "1,2,10"]}))
		self.assertTrue(query.execute(filters={"1field": ["is", "set"]}))
		self.assertFalse(query.execute(filters={"1field": ["not like", "1%"]}))
		self.assertTrue(query.execute(filters=[["table_dt", "2field", "is", "not set"]]))

		jingrow.get_pg(
			{
				"pagetype": table_dt.name,
				"2field": "10",
				"parent": dt_data.name,
				"parenttype": dt_data.pagetype,
				"parentfield": "2table_field",
			}
		).insert(ignore_permissions=True)

		self.assertTrue(query.execute(filters=[["table_dt", "2field", "is", "set"]]))

		# cleanup
		dt.delete()
		table_dt.delete()

	def test_permission_query_condition(self):
		from jingrow.desk.pagetype.dashboard_settings.dashboard_settings import (
			create_dashboard_settings,
		)

		self.pagetype = "Dashboard Settings"
		self.user = "test'5@example.com"

		permission_query_conditions = DatabaseQuery.get_permission_query_conditions(self)

		create_dashboard_settings(self.user)

		dashboard_settings = jingrow.db.sql(
			f"""
				SELECT name
				FROM `tabDashboard Settings`
				WHERE {permission_query_conditions}
			""",
			as_dict=1,
		)[0]

		self.assertTrue(dashboard_settings)

	def test_virtual_pagetype(self):
		"""Test that virtual pagetypes can be queried using get_all"""

		virtual_pagetype = new_pagetype("Virtual PageType")
		virtual_pagetype.is_virtual = 1
		virtual_pagetype.insert(ignore_if_duplicate=True)

		class VirtualPageType:
			@staticmethod
			def get_list(args):
				...

		with patch(
			"jingrow.controllers",
			new={jingrow.local.site: {"Virtual PageType": VirtualPageType}},
		):
			VirtualPageType.get_list = MagicMock()

			jingrow.get_all("Virtual PageType", filters={"name": "test"}, fields=["name"], limit=1)

			call_args = VirtualPageType.get_list.call_args[0][0]
			VirtualPageType.get_list.assert_called_once()
			self.assertIsInstance(call_args, dict)
			self.assertEqual(call_args["pagetype"], "Virtual PageType")
			self.assertEqual(call_args["filters"], [["Virtual PageType", "name", "=", "test"]])
			self.assertEqual(call_args["fields"], ["name"])
			self.assertEqual(call_args["limit_page_length"], 1)
			self.assertEqual(call_args["limit_start"], 0)
			self.assertEqual(call_args["order_by"], DefaultOrderBy)

	def test_coalesce_with_in_ops(self):
		self.assertNotIn("ifnull", jingrow.get_all("User", {"first_name": ("in", ["a", "b"])}, run=0))
		self.assertIn("ifnull", jingrow.get_all("User", {"first_name": ("in", ["a", None])}, run=0))
		self.assertIn("ifnull", jingrow.get_all("User", {"first_name": ("in", ["a", ""])}, run=0))
		self.assertIn("ifnull", jingrow.get_all("User", {"first_name": ("in", [])}, run=0))
		self.assertIn("ifnull", jingrow.get_all("User", {"first_name": ("not in", ["a"])}, run=0))
		self.assertIn("ifnull", jingrow.get_all("User", {"first_name": ("not in", [])}, run=0))
		self.assertIn("ifnull", jingrow.get_all("User", {"first_name": ("not in", [""])}, run=0))

		# primary key is never nullable
		self.assertNotIn("ifnull", jingrow.get_all("User", {"name": ("in", ["a", None])}, run=0))
		self.assertNotIn("ifnull", jingrow.get_all("User", {"name": ("in", ["a", ""])}, run=0))
		self.assertNotIn("ifnull", jingrow.get_all("User", {"name": ("in", (""))}, run=0))
		self.assertNotIn("ifnull", jingrow.get_all("User", {"name": ("in", ())}, run=0))

	def test_coalesce_with_datetime_ops(self):
		self.assertNotIn("ifnull", jingrow.get_all("User", {"last_active": (">", "2022-01-01")}, run=0))
		self.assertNotIn("ifnull", jingrow.get_all("User", {"creation": ("<", "2022-01-01")}, run=0))
		self.assertNotIn(
			"ifnull",
			jingrow.get_all("User", {"last_active": ("between", ("2022-01-01", "2023-01-01"))}, run=0),
		)
		self.assertIn("ifnull", jingrow.get_all("User", {"last_active": ("<", "2022-01-01")}, run=0))

	def test_ambiguous_linked_tables(self):
		from jingrow.desk.reportview import get

		if not jingrow.db.exists("PageType", "Related Todos"):
			jingrow.get_pg(
				{
					"pagetype": "PageType",
					"custom": 1,
					"module": "Custom",
					"name": "Related Todos",
					"naming_rule": "Random",
					"autoname": "hash",
					"fields": [
						{
							"label": "Todo One",
							"fieldname": "todo_one",
							"fieldtype": "Link",
							"options": "ToDo",
							"reqd": 1,
						},
						{
							"label": "Todo Two",
							"fieldname": "todo_two",
							"fieldtype": "Link",
							"options": "ToDo",
							"reqd": 1,
						},
					],
				}
			).insert()
		else:
			jingrow.db.delete("Related Todos")

		todo_one = jingrow.get_pg(
			{
				"pagetype": "ToDo",
				"description": "Todo One",
			}
		).insert()

		todo_two = jingrow.get_pg(
			{
				"pagetype": "ToDo",
				"description": "Todo Two",
			}
		).insert()

		jingrow.get_pg(
			{
				"pagetype": "Related Todos",
				"todo_one": todo_one.name,
				"todo_two": todo_two.name,
			}
		).insert()

		jingrow.form_dict.pagetype = "Related Todos"
		jingrow.form_dict.fields = [
			"`tabRelated Todos`.`name`",
			"`tabRelated Todos`.`todo_one`",
			"`tabRelated Todos`.`todo_two`",
			# because ToDo.show_title_as_field_link = 1
			"todo_one.description as todo_one_description",
			"todo_two.description as todo_two_description",
		]

		# Shouldn't raise pymysql.err.OperationalError: (1066, "Not unique table/alias: 'tabToDo'")
		data = get()
		self.assertEqual(len(data["values"]), 1)

	def test_select_star_expansion(self):
		count = jingrow.get_list("Language", ["SUM(1)", "COUNT(*)"], as_list=1, order_by=None)[0]
		self.assertEqual(count[0], jingrow.db.count("Language"))
		self.assertEqual(count[1], jingrow.db.count("Language"))


class TestReportView(JingrowTestCase):
	@run_only_if(db_type_is.MARIADB)  # TODO: postgres name casting is messed up
	def test_get_count(self):
		jingrow.local.request = jingrow._dict()
		jingrow.local.request.method = "GET"

		# test with data check field
		jingrow.local.form_dict = jingrow._dict(
			{
				"pagetype": "PageType",
				"filters": [["PageType", "show_title_field_in_link", "=", 1]],
				"fields": [],
				"distinct": "false",
			}
		)
		count = execute_cmd("jingrow.desk.reportview.get_count")
		jingrow.local.form_dict = jingrow._dict(
			{
				"pagetype": "PageType",
				"filters": {"show_title_field_in_link": 1},
				"distinct": "true",
			}
		)
		dict_filter_response = execute_cmd("jingrow.desk.reportview.get_count")
		self.assertIsInstance(count, int)
		self.assertEqual(count, dict_filter_response)

		# test with child table filter
		jingrow.local.form_dict = jingrow._dict(
			{
				"pagetype": "PageType",
				"filters": [["PageField", "fieldtype", "=", "Data"]],
				"fields": [],
				"distinct": "true",
			}
		)
		child_filter_response = execute_cmd("jingrow.desk.reportview.get_count")
		current_value = jingrow.db.sql(
			# the below query is equivalent to the one in reportview.get_count
			"select distinct count(distinct `tabPageType`.name) as total_count"
			" from `tabPageType` left join `tabPageField`"
			" on (`tabPageField`.parenttype = 'PageType' and `tabPageField`.parent = `tabPageType`.name)"
			" where `tabPageField`.`fieldtype` = 'Data'"
		)[0][0]
		self.assertEqual(child_filter_response, current_value)

		# test with limit
		limit = 2
		jingrow.local.form_dict = jingrow._dict(
			{
				"pagetype": "PageType",
				"filters": [["PageType", "is_virtual", "=", 1]],
				"fields": [],
				"distinct": "false",
				"limit": limit,
			}
		)
		count = execute_cmd("jingrow.desk.reportview.get_count")
		self.assertIsInstance(count, int)
		self.assertLessEqual(count, limit)

		# test with distinct
		limit = 2
		jingrow.local.form_dict = jingrow._dict(
			{
				"pagetype": "PageType",
				"fields": [],
				"distinct": "true",
				"limit": limit,
			}
		)
		count = execute_cmd("jingrow.desk.reportview.get_count")
		self.assertIsInstance(count, int)
		self.assertLessEqual(count, limit)

		# pagetype with space in name
		limit = 2
		jingrow.local.form_dict = jingrow._dict(
			{
				"pagetype": "Role Profile",
				"fields": [],
				"distinct": "true",
				"limit": limit,
			}
		)
		count = execute_cmd("jingrow.desk.reportview.get_count")
		self.assertIsInstance(count, int)
		self.assertLessEqual(count, limit)

	def test_reportview_get(self):
		user = jingrow.get_pg("User", "test@example.com")
		add_child_table_to_blog_post()

		user_roles = jingrow.get_roles()
		user.remove_roles(*user_roles)
		user.add_roles("Blogger")

		make_property_setter("Blog Post", "published", "permlevel", 1, "Int")
		reset("Blog Post")
		add("Blog Post", "Website Manager", 1)
		update("Blog Post", "Website Manager", 1, "write", 1)

		jingrow.set_user(user.name)

		jingrow.local.request = jingrow._dict()
		jingrow.local.request.method = "POST"

		jingrow.local.form_dict = jingrow._dict(
			{
				"pagetype": "Blog Post",
				"fields": ["published", "title", "`tabTest Child`.`test_field`"],
			}
		)

		# even if * is passed, fields which are not accessible should be filtered out
		response = execute_cmd("jingrow.desk.reportview.get")
		self.assertListEqual(response["keys"], ["title"])
		jingrow.local.form_dict = jingrow._dict(
			{
				"pagetype": "Blog Post",
				"fields": ["*"],
			}
		)

		response = execute_cmd("jingrow.desk.reportview.get")
		self.assertNotIn("published", response["keys"])

		jingrow.set_user("Administrator")
		user.add_roles("Website Manager")
		jingrow.set_user(user.name)

		jingrow.set_user("Administrator")

		# Admin should be able to see access all fields
		jingrow.local.form_dict = jingrow._dict(
			{
				"pagetype": "Blog Post",
				"fields": ["published", "title", "`tabTest Child`.`test_field`"],
			}
		)

		response = execute_cmd("jingrow.desk.reportview.get")
		self.assertListEqual(response["keys"], ["published", "title", "test_field"])

		# reset user roles
		user.remove_roles("Blogger", "Website Manager")
		user.add_roles(*user_roles)

	def test_reportview_get_aggregation(self):
		# test aggregation based on child table field
		jingrow.local.request = jingrow._dict()
		jingrow.local.request.method = "POST"
		jingrow.local.form_dict = jingrow._dict(
			{
				"pagetype": "PageType",
				"fields": """["`tabPageField`.`label` as field_label","`tabPageField`.`name` as field_name"]""",
				"filters": "[]",
				"order_by": "_aggregate_column desc",
				"start": 0,
				"page_length": 20,
				"view": "Report",
				"with_comment_count": 0,
				"group_by": "field_label, field_name",
				"aggregate_on_field": "columns",
				"aggregate_on_pagetype": "PageField",
				"aggregate_function": "sum",
			}
		)

		response = execute_cmd("jingrow.desk.reportview.get")
		self.assertListEqual(response["keys"], ["field_label", "field_name", "_aggregate_column"])

	def test_reportview_get_permlevel_system_users(self):
		with setup_patched_blog_post(), setup_test_user(set_user=True):
			jingrow.local.request = jingrow._dict()
			jingrow.local.request.method = "POST"
			jingrow.local.form_dict = jingrow._dict(
				{
					"pagetype": "Blog Post",
					"fields": ["published", "title", "`tabTest Child`.`test_field`"],
				}
			)

			# even if * is passed, fields which are not accessible should be filtered out
			response = execute_cmd("jingrow.desk.reportview.get")
			self.assertListEqual(response["keys"], ["title"])
			jingrow.local.form_dict = jingrow._dict(
				{
					"pagetype": "Blog Post",
					"fields": ["*"],
				}
			)

			response = execute_cmd("jingrow.desk.reportview.get")
			self.assertNotIn("published", response["keys"])

			# If none of the fields are accessible then result should be empty
			self.assertEqual(jingrow.get_list("Blog Post", "published"), [])

	def test_reportview_get_admin(self):
		# Admin should be able to see access all fields
		with setup_patched_blog_post():
			jingrow.local.request = jingrow._dict()
			jingrow.local.request.method = "POST"
			jingrow.local.form_dict = jingrow._dict(
				{
					"pagetype": "Blog Post",
					"fields": ["published", "title", "`tabTest Child`.`test_field`"],
				}
			)
			response = execute_cmd("jingrow.desk.reportview.get")
			self.assertListEqual(response["keys"], ["published", "title", "test_field"])


def add_child_table_to_blog_post():
	child_table = jingrow.get_pg(
		{
			"pagetype": "PageType",
			"istable": 1,
			"custom": 1,
			"name": "Test Child",
			"module": "Custom",
			"autoname": "Prompt",
			"fields": [{"fieldname": "test_field", "fieldtype": "Data", "permlevel": 1}],
		}
	)

	child_table.insert(ignore_permissions=True, ignore_if_duplicate=True)
	clear_custom_fields("Blog Post")
	add_custom_field("Blog Post", "child_table", "Table", child_table.name)


def create_event(subject="_Test Event", starts_on=None):
	"""create a test event"""

	from jingrow.utils import get_datetime

	return jingrow.get_pg(
		{
			"pagetype": "Event",
			"subject": subject,
			"event_type": "Public",
			"starts_on": get_datetime(starts_on),
		}
	).insert(ignore_permissions=True)


def create_nested_pagetype():
	if jingrow.db.exists("PageType", "Nested PageType"):
		return

	jingrow.get_pg(
		{
			"pagetype": "PageType",
			"name": "Nested PageType",
			"module": "Custom",
			"is_tree": 1,
			"custom": 1,
			"autoname": "Prompt",
			"fields": [{"label": "Description", "fieldname": "description"}],
			"permissions": [{"role": "Blogger"}],
		}
	).insert()


def create_nested_pagetype_records():
	"""
	Create a structure like:
	- Root
	        - Level 1 A
	                - Level 2 A
	                        - Level 3 A
	        - Level 1 B
	                - Level 2 B
	"""
	records = [
		{"name": "Root", "is_group": 1},
		{"name": "Level 1 A", "parent_nested_pagetype": "Root", "is_group": 1},
		{"name": "Level 2 A", "parent_nested_pagetype": "Level 1 A", "is_group": 1},
		{"name": "Level 3 A", "parent_nested_pagetype": "Level 2 A"},
		{"name": "Level 1 B", "parent_nested_pagetype": "Root", "is_group": 1},
		{"name": "Level 2 B", "parent_nested_pagetype": "Level 1 B"},
	]

	for r in records:
		d = jingrow.new_pg("Nested PageType")
		d.update(r)
		d.insert(ignore_permissions=True, ignore_if_duplicate=True)
