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

Lightweight Query Factory #108

Merged
merged 2 commits into from
Feb 18, 2017
Merged

Lightweight Query Factory #108

merged 2 commits into from
Feb 18, 2017

Conversation

tfuda
Copy link
Contributor

@tfuda tfuda commented Feb 24, 2016

The intent of this pull request is to implement what I've termed a "lightweight" Query Factory. The lightweight implementation is optimized for speed, when it comes to constructing the query and generating the corresponding SOQL string. Here is a brief summary of the changes at the core of the lightweight implementation:

  • The internal collection of QueryField objects (fflib_QueryFactory.fields) is maintained in a List, as opposed to a Set. The reason for this change is documented in detail here: Inconsistent performance when constructing QueryFactory #79. Note that there are a couple of side-effects caused by this switch to a List:
    • Field de-duplication is now performed late instead of early. The Set-based implementation provided de-duplication at the time fields were added to the query factory (but not without penalty, due to Inconsistent performance when constructing QueryFactory #79). Now, since I'm using a List, it is possible that duplicate fields can exist in this List. De-duplication is performed at the time you call the fflib_QueryFactory.toSOQL() method, so that duplicate fields do not end up in the generated SOQL.
    • The de-duplication process basically involves sorting the fflib_QueryFactory.fields List, and then iterating through the sorted list, removing any duplicates. Since I need to sort the List anyway, I felt that the setSortSelectedFields method should now be deprecated... sorting ALWAYS occurs.
  • The inner QueryField class has been declared "virtual", and a new subclass of QueryField called LightweightQueryField has been created. This class is basically just wraps a String that stores the field path of a field to be queried. It provides overrides for the QueryField toString, hashCode, equals, compareTo methods.
  • New constructors have been added to fflib_QueryFactory, that accept a Boolean parameter called "lightweight". Invoking the constructor with lightweight = true will cause LightweightQueryField instances to be constructed, rather than instances of the base QueryField class.
  • The fflib_SObjectSelector class has been modified to add a "newLightweightQueryFactory" method, that ultimately invokes the fflib_QueryFactory constructor with the "lightweight" parameter set to true.

The lightweight implementation does sacrifice some functionality in order to achieve a significant reduction in query build time. Here's a summary of what you lose when you use the lightweight implementation:

  • Since we've scrapped most of the SObject Describe stuff, there's no up-front field integrity checking going on. If you build your query passing in a list of Strings, you won't know you've specified a bad field path until you actually try to execute the query.
  • The lightweight version does not enforce FLS. Since the goal here is speed, I bypass the code that converts the fields into their corresponding Schema.SObjectField tokens so they can then be run through the FLS checks in fflib_SecurityUtils.

Using the SelectorPerformance class developed by @daveespo (with slight modification to allow me to easily switch between the lightweight and regular implementations) I came up with the following benchmark numbers for 10 query "build" operations:

With lightweight = false:

11:27:55.52 (1673553331)|USER_DEBUG|[42]|DEBUG|Preload Elapsed:29
11:27:55.52 (1673623737)|USER_DEBUG|[43]|DEBUG|Parent Elapsed:314
11:27:55.52 (1673691877)|USER_DEBUG|[44]|DEBUG|Grandparent Elapsed:436
11:27:55.52 (1673766529)|USER_DEBUG|[45]|DEBUG|QF Elapsed (includes parent & grandparent):813
11:27:55.52 (1673837762)|USER_DEBUG|[46]|DEBUG|SOQL Elapsed:799
11:27:55.52 (1673908107)|USER_DEBUG|[47]|DEBUG|Total Elapsed:1612

With lightweight = true:

11:29:35.14 (416745051)|USER_DEBUG|[42]|DEBUG|Preload Elapsed:15
11:29:35.14 (416811125)|USER_DEBUG|[43]|DEBUG|Parent Elapsed:22
11:29:35.14 (416874242)|USER_DEBUG|[44]|DEBUG|Grandparent Elapsed:22
11:29:35.14 (416942885)|USER_DEBUG|[45]|DEBUG|QF Elapsed (includes parent & grandparent):124
11:29:35.14 (417009114)|USER_DEBUG|[46]|DEBUG|SOQL Elapsed:263
11:29:35.14 (417075267)|USER_DEBUG|[47]|DEBUG|Total Elapsed:387

This represents approximately a 75% reduction in the time required to construct the query factory, set the query fields and generate the SOQL string.

Tom Fuda added 2 commits February 23, 2016 15:36
…tionality is done. This implement some equivalent unit tests for LightweightQueryField.
@capeterson
Copy link
Contributor

(standard disclaimer about no longer being an FF employee and not an official voice for this repo)

I'll take a look over the code in the next few days, but from your abstract this sounds awesome. Having multiple implementations depending on your use case was something I'd long though about, especially once the selector layer adopted queryfactory.

@afawcett
Copy link
Contributor

Thanks @capeterson i'm more than happy to get your views on this, thanks for jumping in.

@tfuda It looks like you have this covered, but to confirm do your changes maintain API backwards compatibility? Also wasn't sure if FLS checking is just done at a different time in light weight mode or not at all, can you clarify?

@afawcett afawcett merged commit 4e5f8c2 into apex-enterprise-patterns:master Feb 18, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants