Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#73 add RLQUERY support #90

Merged
merged 2 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions docs/user/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,25 @@ See the API documentation for additional details::
>>> join_query.add_query('active','true')
>>> gr.query()

Related List Queries
--------------------

Allows a user to perform a query comparing a related list, which allows the simulation of LEFT OUTER JOIN and etc.
See the API documentation and tests for additional details.

All users with the role which has a `name` of `admin`::

>>> gr = client.GlideRecord('sys_user')
>>> qc = gr.add_rl_query('sys_user_has_role', 'user', '>0', True)
>>> qc.add_query('role.name', 'admin')
>>> gr.query()

All users without any role::

>>> gr = client.GlideRecord('sys_user')
>>> qc = gr.add_rl_query('sys_user_has_role', 'user', '=0')
>>> gr.query()

Proxies
-------

Expand Down
20 changes: 20 additions & 0 deletions pysnc/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ def add_join_query(self, join_table, primary_field=None, join_table_field=None)
self.__sub_query.append(join_query)
return join_query

def add_rl_query(self, related_table, related_field, operator_condition, stop_at_relationship):
rl_query = RLQuery(self._table, related_table, related_field, operator_condition, stop_at_relationship)
self.__sub_query.append(rl_query)
return rl_query

def _add_query_condition(self, qc):
assert isinstance(qc, QueryCondition)
self.__conditions.append(qc)
Expand Down Expand Up @@ -106,3 +111,18 @@ def generate_query(self, encoded_query=None, order_by=None) -> str:
return res


class RLQuery(Query):

def __init__(self, table, related_table, related_field, operator_condition, stop_at_relationship):
super(self.__class__, self).__init__(table)
self._related_table = related_table
self._related_field = related_field
self.operator_condition = operator_condition
self.stop_at_relationship = stop_at_relationship

def generate_query(self, encoded_query=None, order_by=None) -> str:
query = super(self.__class__, self).generate_query(encoded_query, order_by)
identifier = "{}.{}".format(self._related_table, self._related_field)
stop_condition = ",m2m" if self.stop_at_relationship else ""
query = "^{}".format(query) if query else ""
return "RLQUERY{},{}{}{}^ENDRLQUERY".format(identifier, self.operator_condition, stop_condition, query)
25 changes: 25 additions & 0 deletions pysnc/record.py
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,31 @@ def add_join_query(self, join_table, primary_field=None, join_table_field=None)
"""
return self.__query.add_join_query(join_table, primary_field, join_table_field)

def add_rl_query(self, related_table, related_field, operator_condition, stop_at_relationship=False):
"""
Generates a 'Related List Query' which is defined as:

RLQUERY<other_table_name>.<field>,<operator><value>[,m2m][^subquery]^ENDRLQUERY

For example, when querying sys_user to simulate a LEFT OUTER JOIN to find active users with no manager:

RLQUERYsys_user.manager,=0^active=true^ENDRLQUERY

If we find users with a specific role:

RLQUERYsys_user_has_role.user,>0^role=ROLESYSID^ENDRLQUERY

But if we want to dotwalk the role (aka set stop_at_relationship=True):

RLQUERYsys_user_has_role.user,>0,m2m^role.name=admin^ENDRLQUERY

:param str related_table: The table with the relationship -- the other table
:param str related_field: The field to use to relate from the other table to the table we are querying on
:param str operator_condition: The operator to use to relate the two tables, as in `=0` or `>=1` -- this is not validated by pysnc
:param bool stop_at_relationship: if we have a subquery (a query condition ON the RLQUERY) AND it dot walks, this must be True. Default is False.
"""
return self.__query.add_rl_query(related_table, related_field, operator_condition, stop_at_relationship)

def add_encoded_query(self, encoded_query):
"""
Adds a raw query. Appends (comes after) all other defined queries e.g. :func:`add_query`
Expand Down
23 changes: 0 additions & 23 deletions test/test_snc_api_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,29 +56,6 @@ def test_get_query_two(self):
self.assertEqual(enc_query, '')
client.session.close()

def test_join_query(self):
client = ServiceNowClient(self.c.server, self.c.credentials)
gr = client.GlideRecord('sys_user')
join_query = gr.add_join_query('sys_user_group', join_table_field='manager')
join_query.add_query('active','true')
self.assertEqual(gr.get_encoded_query(), 'JOINsys_user.sys_id=sys_user_group.manager!active=true')
gr.query()
self.assertGreater(gr.get_row_count(), 1)
client.session.close()

''' need to validate join query actually works right...
def test_join_query_2(self):
client = ServiceNowClient(self.c.server, self.c.credentials)
gr = client.GlideRecord('sys_user')
join_query = gr.add_join_query('sys_user_has_role', join_table_field='user')
join_query.add_query('role.name','admin')
self.assertEqual(gr.get_encoded_query(), 'JOINsys_user.sys_id=sys_user_has_role.user!role.name=admin')
gr.query()
gr.next()
print(gr.serialize())
self.assertEqual(gr.get_row_count(), 2)
'''

def test_null_query(self):
client = ServiceNowClient(self.c.server, self.c.credentials)
gr_first = client.GlideRecord('sys_user')
Expand Down
59 changes: 59 additions & 0 deletions test/test_snc_api_query_advanced.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from unittest import TestCase

from pysnc import ServiceNowClient, exceptions
from constants import Constants

class TestRecordQueryAdvanced(TestCase):
c = Constants()


def test_join_query(self):
client = ServiceNowClient(self.c.server, self.c.credentials)
gr = client.GlideRecord('sys_user')
join_query = gr.add_join_query('sys_user_group', join_table_field='manager')
join_query.add_query('active','true')
self.assertEqual(gr.get_encoded_query(), 'JOINsys_user.sys_id=sys_user_group.manager!active=true')
gr.query()
self.assertGreater(gr.get_row_count(), 1)
client.session.close()

def test_join_query_2(self):
client = ServiceNowClient(self.c.server, self.c.credentials)
gr = client.GlideRecord('sys_user')
join_query = gr.add_join_query('sys_user_has_role', join_table_field='user')
join_query.add_query('role', '2831a114c611228501d4ea6c309d626d')
self.assertEqual(gr.get_encoded_query(), 'JOINsys_user.sys_id=sys_user_has_role.user!role=2831a114c611228501d4ea6c309d626d')
gr.query()
gr.next()
self.assertGreater(len(gr), 10) # demo data has a lot of admins
self.assertLess(len(gr), 25) # but not THAT many

def test_rl_query_manual(self):
# simulate a left outter join by finding users with no roles
client = ServiceNowClient(self.c.server, self.c.credentials)
gr = client.GlideRecord('sys_user')
gr.add_encoded_query('RLQUERYsys_user_has_role.user,=0^ENDRLQUERY')
gr.query()
self.assertGreater(gr.get_row_count(), 1)
self.assertLess(gr.get_row_count(), 10)

def test_rl_query_basic(self):
# simulate a left outter join by finding users with no roles
client = ServiceNowClient(self.c.server, self.c.credentials)
gr = client.GlideRecord('sys_user')
gr.add_rl_query('sys_user_has_role', 'user', '=0')
self.assertEqual(gr.get_encoded_query(), 'RLQUERYsys_user_has_role.user,=0^ENDRLQUERY')
gr.query()
self.assertGreater(gr.get_row_count(), 1)
self.assertLess(gr.get_row_count(), 10)

def test_rl_query_advanced(self):
client = ServiceNowClient(self.c.server, self.c.credentials)
gr = client.GlideRecord('sys_user')
qc = gr.add_rl_query('sys_user_has_role', 'user', '>0', True)
qc.add_query('role.name', 'admin')
self.assertEqual(gr.get_encoded_query(), 'RLQUERYsys_user_has_role.user,>0,m2m^role.name=admin^ENDRLQUERY')
gr.query()
self.assertGreater(gr.get_row_count(), 10)
self.assertLess(gr.get_row_count(), 25)

3 changes: 3 additions & 0 deletions test/test_snc_api_write.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,12 @@ def test_insert(self):
res = gr.insert()
self.assertIsNotNone(res)
self.assertIsNotNone(gr.sys_id)
self.assertEqual(res, gr.sys_id)
self.assertIsNotNone(gr.number)
# make sure it exists
gr2 = client.GlideRecord('problem')
self.assertTrue(gr2.get(res))
self.assertEqual(gr2.number, gr.number)

gr.delete()

Expand Down