Skip to content

Feature/typeof#265

Draft
stackasaur wants to merge 13 commits intobeyond-the-cloud-dev:release/v6.8.0from
stackasaur:feature/typeof
Draft

Feature/typeof#265
stackasaur wants to merge 13 commits intobeyond-the-cloud-dev:release/v6.8.0from
stackasaur:feature/typeof

Conversation

@stackasaur
Copy link
Contributor

Description

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • [?] Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring
  • Test improvements
  • CI/CD improvements

Changes Made

  • Added support for TypeOf
  • Potentially breaking for user implementations as Queryable interface was modified

Related Issues

Fixes #235
Closes #

Testing

  • All existing tests pass (npm test)
  • Added new tests for new functionality
  • Tested in scratch org
  • Linting passes (npm run lint)
  • Code formatting is correct (npm run prettier:verify)

Screenshots

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code where necessary
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings or errors
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published

Additional Notes

String query = SOQL.of(Event.sObjectType)
.with(
  SOQL.type.of(Event.WhatId)
  .when(Account.sObjectType).then(Account.Name)
  .when(Opportunity.sObjectType).then(Opportunity.Name)
  .whenElse('Name')
)
.toString();
System.debug(query);

// outputs
// SELECT TYPEOF What WHEN Account THEN Name WHEN Opportunity THEN Name ELSE Name END FROM Event

@vercel
Copy link

vercel bot commented Feb 4, 2026

@stackasaur is attempting to deploy a commit to the Beyond The Cloud Team on Vercel.

A member of the Team first needs to authorize it.

@stackasaur
Copy link
Contributor Author

stackasaur commented Feb 4, 2026

Haven't written unit tests yet.
Wanted some feedback on the code first as it's my first feature addition to this repo.

I added the neccesary classes to add the TypeOf fields as a component of SoqlFields as that seemed like the most logical place for the logic.

@pgajek2

@pgajek2 pgajek2 changed the base branch from main to release/v6.8.0 February 4, 2026 07:16
@pgajek2
Copy link
Member

pgajek2 commented Feb 4, 2026

Haven't written unit tests yet. Wanted some feedback on the code first as it's my first feature addition to this repo.

I added the neccesary classes to add the TypeOf fields as a component of SoqlFields as that seemed like the most logical place for the logic.

@pgajek2

@stackasaur perfect!

I'll review it as soon I'll have time!


// WHEN
Typeof when(SObjectType whenObject);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we remove that additional spaces? To have it similar to the other interfaces?

Interface itself looks great!

private class SoqlTypeOf implements Typeof {
private String ofField;
private Map<SObjectType, SoqlFields> fieldsByType;
private SObjectType[] whenTypes;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use List<SObjectType> to be consistent with the whole lib?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd argue that the whole lib should use SObjectType[] as that syntax saves a decent number of characters.

But I'll change it to meet the others. Changes to lower character count would be an entirely different issue.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm. It rather depends what you have to do.
Lists size is dynamic, Arrays size is fixed.
The whenTypes list should be dynamic in our case. We don't know the size as items can be added dynamically.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Apex, there is no functional difference between the two though as Account[] accts = new List<Account>() and List<Account> accts = new Account[0]

You can't also denote an empty list as Account[] accts = new Account[]{} which I'd say implies the dynamic sizing while still cutting down on characters

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll check performance, and if it's the same, what can I say: It's time to switch to [ ] 👯


private class SoqlTypeOf implements Typeof {
private String ofField;
private Map<SObjectType, SoqlFields> fieldsByType;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can set the value here:

private Map<SObjectType, SoqlFields> fieldsByType new Map<SObjectType, SoqlFields>();
private List<SObjectType> whenTypes = new List<SObjectType>();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was an intentional decision to answer the question "what should happen if you call of again?"

I figured it should either throw an exception or it should clear out the existing when/then clauses, and I chose the latter since I couldn't find many examples of the framework throwing exceptions for things like this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair!

I would lean a little bit to an exception approach, as the syntax:

SOQL.Type.of(...).of(...)

looks a bit strange and shouldn't be allowed. I'll add another github issue to take care of other places.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there aren't any custom Exceptions in this class yet, how would you like to handle it?

I could probably argue that this is a use case for the built in IllegalArgumentException, but that may also feel a bit strange since it's essentially calling a setter twice which one would normally expect to work

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use the built-in exception QueryException.

throw new QueryException('My message');

We use it already in other places in the SOQL Lib.

return this.of(ofField.getDescribe().getRelationshipName());
}

private SoqlFields getSoqlFields(SObjectType whenObject){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getSoqlFields is private and it's invoked in methods below, could we move it at the end of the class?

There are two reasons:

  1. It's private and doesn't have to be seen.
  2. Read flow - it's invoked later, but defined first. It would have sense to have the definition below invocation.


private SoqlFields getSoqlFields(SObjectType whenObject){
SoqlFields ret = this.fieldsByType.get(whenObject);
if (ret == null){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we change the name from ret to something more descriptive?

private SoqlFields getElseFields(){
SoqlFields ret = this.elseFields;
if (ret == null){
ret = new SoqlFields(null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets overload the SoqlFields constuctor so we don't have to pass SoqlFields.

Just create

public SoqlFields() {
   // default constructor
}


public SoqlTypeOf when(SObjectType whenObject) {
if (!fieldsByType.containsKey(whenObject)){
this.fieldsByType.put(whenObject,null);
Copy link
Member

@pgajek2 pgajek2 Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not like that?

this.fieldsByType.put(whenObject, new SoqlFields(whenObject.toString()));

so you don't have to do if (ret == null){ + you won't need this.getSoqlFields

	public Typeof then(SObjectField field) {
			this.latestWhen().plainFields.add(field.toString());
			return this;
		}

String suffix = ' END';

for (SObjectType t : this.whenTypes){
SoqlFields fields = this.fieldsByType.get(t);
Copy link
Member

@pgajek2 pgajek2 Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe let's create a new inner class TypeOfWhenThenFields?

I would like to avoid fieldStr = fieldStr.substring(7); // remove the SELECT
Another reason is that SoqlFields has a lot of logic that we doesn't need.

We will duplicate the logic a bit, but it's not a bad duplication.

The whole WHEN - THEN build can be hidden inside TypeOfWhenThenFields, so this one (SoqlTypeOf) will be just a wrapper that will add 'TYPEOF ' + this.ofField + ' '; and ' END';

public class TypeOfWhenThenFields
		public PlainFields plainFields = new PlainFields();
		public RelationshipFields relationshipFields = new RelationshipFields();
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fully agree.

I was hesitant to create a new inner class for that as SoqlFields did meet the requirements with very little workaround needed, and I wanted to avoid adding what would be a significant number of characters.

Adding this will address several of your above comments.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries, when everything is ready we will chance spaces to tabs:

image

which significantly reduce number of characters. At the end of the day it won't be that bad, but I got you point.

Copy link
Member

@pgajek2 pgajek2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it! Nice work!

If you disagree with anything, feel free to raise it.

@stackasaur
Copy link
Contributor Author

@pgajek2

What is the expected behavior of situations like this:

TYPEOF can’t be used in queries with GROUP BY, GROUP BY ROLLUP, GROUP BY CUBE, and HAVING.

Throw Exceptions?
Let SFDC throw it when executed?
Clean in up for the user?

@stackasaur
Copy link
Contributor Author

@pgajek2

I've addressed all of your comments

@pgajek2
Copy link
Member

pgajek2 commented Feb 4, 2026

@pgajek2

What is the expected behavior of situations like this:

TYPEOF can’t be used in queries with GROUP BY, GROUP BY ROLLUP, GROUP BY CUBE, and HAVING.

Throw Exceptions? Let SFDC throw it when executed? Clean in up for the user?

I would say: Let SFDC throw it when executed - as it's default behaviour, let's keep it in this way.

@pgajek2
Copy link
Member

pgajek2 commented Feb 4, 2026

@stackasaur all looks good, you can continue with the unit tests.

@stackasaur
Copy link
Contributor Author

@pgajek2 ,

It doesn't seem that any relationship fields are valid in the Else clause, so I'm going to remove all of those

@stackasaur
Copy link
Contributor Author

Tests completed. Updating documentation now

Assert.areEqual(fromDate, binding.get('v5'), 'The binding variable should match the expected value.');
}

@IsTest
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you adjust the test to be similar to the existing one?

I mean:

  1. Add // Setup // Test // Verify comments to be consistent with the existing ones.
  2. If possible, use chaining without a variable.
  3. Use the clause in context of the whole SOQL Lib.
String query = SOQL.of(...)
   .with(SOQL.Type.of(Event.WhatId)
      .when(Account.sObjectType).then(Account.Id, Account.Name, Account.BillingCity, Account.BillingState, Account.BillingPostalCode)
      .when(Opportunity.sObjectType).then('Account',Account.Id, Account.Name, Account.BillingCity, Account.BillingState, Account.BillingPostalCode)
      .whenElse(new List<String>{'Name'});
     .toString();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So do asserts in the context of the whole query rather than just test the individual typeof clause here?


@IsTest
static void typeOfClassRelationshipFields(){
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it work? You have brackets in weird places ;>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage all works.

I did the brackets to split the various overloads for the same method into different scopes. I could alternatively make separate methods.

I just normally do this when testing methods of slightly different signatures like this since apex lacks a better way to group together "sub tests"

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.

TYPEOF Support

3 participants