Skip to main content
Redhat Developers  Logo
  • AI

    Get started with AI

    • Red Hat AI
      Accelerate the development and deployment of enterprise AI solutions.
    • AI learning hub
      Explore learning materials and tools, organized by task.
    • AI interactive demos
      Click through scenarios with Red Hat AI, including training LLMs and more.
    • AI/ML learning paths
      Expand your OpenShift AI knowledge using these learning resources.
    • AI quickstarts
      Focused AI use cases designed for fast deployment on Red Hat AI platforms.
    • No-cost AI training
      Foundational Red Hat AI training.

    Featured resources

    • OpenShift AI learning
    • Open source AI for developers
    • AI product application development
    • Open source-powered AI/ML for hybrid cloud
    • AI and Node.js cheat sheet

    Red Hat AI Factory with NVIDIA

    • Red Hat AI Factory with NVIDIA is a co-engineered, enterprise-grade AI solution for building, deploying, and managing AI at scale across hybrid cloud environments.
    • Explore the solution
  • Learn

    Self-guided

    • Documentation
      Find answers, get step-by-step guidance, and learn how to use Red Hat products.
    • Learning paths
      Explore curated walkthroughs for common development tasks.
    • See all learning

    Hands-on

    • Developer Sandbox
      Spin up Red Hat's products and technologies without setup or configuration.
    • Interactive labs
      Learn by doing in these hands-on, browser-based experiences.
    • Interactive demos
      Click through product features in these guided tours.

    Browse by topic

    • AI/ML
    • Automation
    • Java
    • Kubernetes
    • Linux
    • See all topics

    Training & certifications

    • Courses and exams
    • Certifications
    • Skills assessments
    • Red Hat Academy
    • Learning subscription
    • Explore training
  • Build

    Get started

    • Red Hat build of Podman Desktop
      A downloadable, local development hub to experiment with our products and builds.
    • Developer Sandbox
      Spin up Red Hat's products and technologies without setup or configuration.

    Download products

    • Access product downloads to start building and testing right away.
    • Red Hat Enterprise Linux
    • Red Hat AI
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform
    • See all products

    Featured

    • Red Hat build of OpenJDK
    • Red Hat JBoss Enterprise Application Platform
    • Red Hat OpenShift Dev Spaces
    • Red Hat Developer Toolset

    References

    • E-books
    • Documentation
    • Cheat sheets
    • Architecture center
  • Community

    Get involved

    • Events
    • Live AI events
    • Red Hat Summit
    • Red Hat Accelerators
    • Community discussions

    Follow along

    • Articles & blogs
    • Developer newsletter
    • Videos
    • Github

    Get help

    • Customer service
    • Customer support
    • Regional contacts
    • Find a partner

    Join the Red Hat Developer program

    • Download Red Hat products and project builds, access support documentation, learning content, and more.
    • Explore the benefits

Type safe SalesForce Queries (SOQL) in Scala

June 11, 2015
Adam Kovari
Related topics:
DevOpsJava
Related products:
Developer Toolset
    ScalaThis blog shows how to implement a relatively simple type safe DSL for the SalesForce Object Query Language (SOQL). SalesForce doesn’t provide much tooling specifically for the Scala world, which got me to writing some support myself.
     
    One of the most common ways to interact with the SalesForce API is using the Enterprise or the Partner WSDL. For the purpose of this article, I will be using the Enterprise WSDL. The main difference is that the Enterprise one is type-safe - it contains object definitions for all your data model objects, while the Partner one is more suitable for dynamically typed languages.
     

    Custom DSL in Scala

     
    Given that we have a type safe WSDL, how do we get to a type safe Scala DSL? Let me first show an example of what the goal is.
     
    Ideally I would be able to write some code like this in Scala:
     
    select
         (Case_.caseNumber :: (Case_.account :/ Account_.accountNumber) :: Case_.`engineeringReqNumberC` :: HNil)
    from Case_
    where (
         (Case_.caseNumber :== "5555") and (Case_.`createdDate` :== Calendar.getInstance())
    )
    This would be translated to a SOQL query like this one:
     
    SELECT CaseNumber, Account.AccountNumber, EngineeringReqNumber__c FROM Case WHERE (((CaseNumber = '5555')) AND (CreatedDate = '2015-06-08T10:01:42+02:00'))
    Let’s see what this code actually does step by step:
     
    Before digging deeper into the implementation of this code, let’s take a look at how this actually makes our code safe, instead of just writing a String and sending it to the Salesforce:
     
    First of all, the list of fields in the “select" clause is compile time checked and only specific types are allowed. We basically only want to allow Salesforce fields or sub-selects to comply with the SOQL specs.
     
    Let’s see if we try to add something not allowed to the list of fields in the “select" part:
     
    select(Case_.caseNumber :: 4 :: HNil) from Case_
    The constants are not allowed, so we get:
     
    error: type mismatch;
    [ERROR]  found   : shapeless.::[com.github.akovari.typesafeSalesforce.model.Field[String],shapeless.::[Int,shapeless.HNil]]
    [ERROR]  required: com.github.akovari.typesafeSalesforce.query.ColumnList[?]
    [ERROR]     select(Case_.caseNumber :: 4 :: HNil) from Case_
    This may look a bit cryptic but I’ll get to the implementation details later.
     
    Similarly we need to make sure only actual SalesForce entities are allowed in the “from” section.
     
    The conditions are also interesting. We need to make sure that we are creating conditions from types that match. We know that the caseNumber is of a type String so we shouldn’t be able to compare it with int for example.
     
    This should not compile:
    Case_.caseNumber :== 4
    As we can see, the compiler catches the error as expected:
     
    error: type mismatch;
    [ERROR]  found   : Int(4)
    [ERROR]  required: com.github.akovari.typesafeSalesforce.query.Field[String]
    [ERROR]     Case_.caseNumber :== 4
     

    Implementation

     
    So this is basically where we want to get, now let’s go step by step how to implement all of the above.
     

    Web Serivces and code generation

     

    1. First of all, we are starting off with an WSDL that looks something like this:
     <complexType name="Case">
     <complexContent>
     <extension base="ens:sObject">
     <sequence>
     <element name="Account" nillable="true" minOccurs="0" type="ens:Account"/>
     <element name="AccountId" nillable="true" minOccurs="0" type="tns:ID"/>
     <element name="ActivityHistories" nillable="true" minOccurs="0" type="tns:QueryResult"/>
     <element name="Asset" nillable="true" minOccurs="0" type="ens:Asset"/>
     <element name="AssetId" nillable="true" minOccurs="0" type="tns:ID"/>
    ...
     
    This is from a WSDL obtained via a developer account on Salesforce, it contains definitions of objects and their fields. The very first step is to generate a WS client, I have tried to use ScalaXB which would make a lot of implementation much simpler, unfortunately it is unable to generate a client for the SalesForce WSDL. Therefore I have used CXF codegen which can generate a Java client for us.
     
    2. Next we need to generate a meta model from the client objects, similar to what Hibernate meta model looks like for Database entities. Here we would typically consider at least following options:
    •  since we’re in scala we could use Macros. The problem is that this client is actually Java and not scala code so using Macros doesn’t seem very natural, also writing Macros still looks like a little bit of magic, I know, quasi-qoutes make it much nicer but still - I have no experience with Macros just yet. Maybe in the future together with the ScalaXB this could work.
    •  Java compile time annotation processor combined with a simple template library such as velocity to parse JAXB annotations and generate meta model classes
    •  other approaches to compile time or runtime(reflection) code generation
    Implementation of the annotation processor is available at:
     
    It consists of 2 parts:
     
    • In the binding.xml we annotate all SalesForce object with our custom annotation
     <jxb:bindings node="//xs:complexType">
     <annox:annotate target="class">
     <annox:annotate annox:class="com.github.akovari.typesafeSalesforce.annotations.SalesForceEntity"/>
     </annox:annotate>
    • And then we process it by an annotation processor and generate the meta model classes.
    @SupportedAnnotationTypes({"com.github.akovari.typesafeSalesforce.annotations.SalesForceEntity"})
    @SupportedSourceVersion(SourceVersion.RELEASE_8)
    public class SalesForceAnnotationProcessor extends AbstractProcessor
     

    DSL Construction

     

    3. Let’s jump into the actual DSL construction.
     
    We are only going to implement support for select-s in this blog. Our primary interface is defined this way:
     
    import shapeless._
    
    case class SelectQuery[C <: HList](columns: ColumnList[C],
                           entities: Seq[Entity] = Seq.empty,
                           filter: Option[Filter] = None,
                           orders: Option[OrderList[_ <: HList]],
                           groupBys: Seq[GroupBy[_]] = Seq.empty,
                           limit: Option[Limit] = None)
    This is an underlying model used by the select … from… function on the top. It seems that most of the elements are quite obvious, the reason why columns and orders are defined as Shapeless HList is because we need to allow heterogenous elements in the collection of elements, such as Field[Integer], Field[String] … and also we need to not allow just anything, so we do not allow constants for example. The actual implementation of how this works will be shown later.
     
    The DSL functions themselves:
     
    object SelectQuery {
      type SelectableColumn[T, C <: HList] = Either[SimpleColumn[T], EmbeddedSelectColumn[T, C]]
    
      def select[C <: HList](columns: ColumnList[C]) = SelectQueryStep(columns)
    
      sealed trait QueryStep[C <: HList] {
        val query: SelectQuery[C]
    
        override def toString = query.toString
      }
    
      case class SelectQueryStep[C <: HList](columns: ColumnList[C]) extends QueryStep[C] {
        override val query = SelectQuery(columns = columns, orders = None)
    
        def from(entities: Entity*): FromQueryStep[C] = FromQueryStep(query, entities)
      }
    
      case class FromQueryStep[C <: HList](sq: SelectQuery[C], entities: Seq[Entity]) extends QueryStep[C] {
        override val query = SelectQuery(columns = sq.columns, entities = entities, orders = None)
    
        def where(filter: Filter): WhereQueryStep[C] = WhereQueryStep(query, filter)
    
        def orderBy[O <: HList](orders: OrderList[O]): OrderQueryStep[C, O] = OrderQueryStep(query, orders)
    
        def groupBy(groupBys: GroupBy[_]*): GroupByQueryStep[C] = GroupByQueryStep(query, groupBys)
    
        def limit(limit: Limit): LimitQueryStep[C] = LimitQueryStep(query, limit)
      }
    
      case class WhereQueryStep[C <: HList](sq: SelectQuery[C], filter: Filter) extends QueryStep[C] {
        override val query = SelectQuery(columns = sq.columns, entities = sq.entities, filter = Some(filter), orders = None)
    
        def orderBy[O <: HList](orders: OrderList[O]): OrderQueryStep[C, O] = OrderQueryStep(query, orders)
    
        def groupBy(groupBys: GroupBy[_]*): GroupByQueryStep[C] = GroupByQueryStep(query, groupBys)
    
        def limit(limit: Limit): LimitQueryStep[C] = LimitQueryStep(query, limit)
      }
    
      case class OrderQueryStep[C <: HList, O <: HList](sq: SelectQuery[C], orders: OrderList[O]) extends QueryStep[C] {
        override val query = SelectQuery(columns = sq.columns, entities = sq.entities, filter = sq.filter, orders = Some(orders))
    
        def groupBy(groupBys: GroupBy[_]*): GroupByQueryStep[C] = GroupByQueryStep(query, groupBys)
    
        def limit(limit: Limit): LimitQueryStep[C] = LimitQueryStep(query, limit)
      }
    
      case class GroupByQueryStep[C <: HList](sq: SelectQuery[C], groupBys: Seq[GroupBy[_]]) extends QueryStep[C] {
        override val query = SelectQuery(columns = sq.columns, entities = sq.entities, filter = sq.filter, orders = sq.orders, groupBys = groupBys)
    
        def limit(limit: Limit): LimitQueryStep[C] = LimitQueryStep(query, limit)
      }
    
      case class LimitQueryStep[C <: HList](sq: SelectQuery[C], limit: Limit) extends QueryStep[C] {
        override val query = SelectQuery(columns = sq.columns, entities = sq.entities, filter = sq.filter, orders = sq.orders, groupBys = sq.groupBys, limit = Some(limit))
      }
    }
     
    We define a class for each step of the query creation. It may be worth noting that all of this code is purely immutable.
     
    Let’s finally get to the use of Shapeless HLists for polymorphic lists of fields and order by fields.
     
    For columns or fields this is all we need:
     
    case class ColumnList[L <: HList](l : L)
    
    object ColumnPoly extends Poly1 {
      implicit def caseField[T, S <% ModelField[T]] = at[ModelField[T]](f => fieldToColumn(f).toString)
    
      implicit def caseSelectQueryStep[T, C <: HList, QS <: SelectQueryStep[C], S <% QS] = at[SelectQueryStep[C]](qs => EmbeddedSelectColumn[T, C](qs).toString)
      implicit def caseFromQueryStep[T, C <: HList, QS <: FromQueryStep[C], S <% QS] = at[FromQueryStep[C]](qs => EmbeddedSelectColumn[T, C](qs).toString)
      implicit def caseWhereQueryStep[T, C <: HList, QS <: WhereQueryStep[C], S <% QS] = at[WhereQueryStep[C]](qs => EmbeddedSelectColumn[T, C](qs).toString)
    
      implicit def caseOrderQueryStep[T, C <: HList, O <: HList, QS <: OrderQueryStep[C, O], S <% QS] = at[OrderQueryStep[C, O]](qs => EmbeddedSelectColumn[T, C](qs).toString)
      implicit def caseGroupByQueryStep[T, C <: HList, QS <: GroupByQueryStep[C], S <% QS] = at[GroupByQueryStep[C]](qs => EmbeddedSelectColumn[T, C](qs).toString)
      implicit def caseLimitQueryStep[T, C <: HList, QS <: LimitQueryStep[C], S <% QS] = at[LimitQueryStep[C]](qs => EmbeddedSelectColumn[T, C](qs).toString)
    
      implicit def caseSimpleColumn[T, SC <: SimpleColumn[T], S <% SC] = at[SimpleColumn[T]](_.toString)
      implicit def caseEmbeddedSelectColumn[T, C <: HList, SC <: EmbeddedSelectColumn[T, C], S <% SC] = at[EmbeddedSelectColumn[T, C]](_.toString)
    }
    
    implicit def hlistToColumnList[L <: HList, M <: HList](a: L)(implicit mapper: Mapper.Aux[ColumnPoly.type, L, M]): ColumnList[M] =
      new ColumnList[M]((a map ColumnPoly))
    We are using the Poly1 map compile time transformation of the HList for those very specific types as defined in the ColumnPoly. I’ll not go too deep in the details, that would go beyond the scope of this blog.
     
    Similarly we implement a very similar Poly1 for the OrderList:
     
    object OrderPoly extends Poly1 {
      implicit def caseAsc[T, S <% AscendingOrder[T]] = at[AscendingOrder[T]](_.toString)
    
      implicit def caseDesc[T, S <% DescendingOrder[T]] = at[DescendingOrder[T]](_.toString)
    }
    
    implicit def hlistToOrderList[L <: HList, M <: HList](a: L)(implicit mapper: Mapper.Aux[OrderPoly.type, L, M]): OrderList[M] =
      new OrderList[M]((a map OrderPoly))

     

    Conclusion

    That is more or less all there is to it. We have shown how to enhance a Java only interface and extend it with additional type safety provided by the Scala ecosystem. We have an intuitive interface for writing SOQL queries that also gives us a lots of safety and simplicity thanks to the IDE code completion when typing new queries.

    One last part of this codebase worth mentioning is the actual query interface that hooks this SelectQuery class back to the Java SalesForce client.
     
    I am using Lasius library that handles the reconnections etc and all that was left to do can be found in SalesForceConnection class.
     
    This code is not ready to be used as a library. I have yet to solve how to integrate a third-party WSDLs into this build process. After that, there are still a bunch of things to work on.
     
    Among other things:
     
    • Aggregated columns are not type safe - These should only work for Number fields.
    • Improve Case_.account : / Account_.accountNumber - only Account_ fields should be allowed after Case_.account
    • Generalize the Build process to work with external WSDLs
    • Add support for Insert, Update, Upsert and Delete queries

     

    Link to the source codes.

    Last updated: February 23, 2024

    Recent Posts

    • Confidential virtual machine storage attack scenarios

    • Introducing virtualization platform autopilot

    • Integrate zero trust workload identity manager with Red Hat OpenShift GitOps

    • Best Practice Configuration and Tuning for Linux and Windows VMs

    • Red Hat UBI 8 builders have been promoted to the Paketo Buildpacks organization

    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Platforms

    • Red Hat AI
    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform
    • See all products

    Build

    • Developer Sandbox
    • Developer tools
    • Interactive tutorials
    • API catalog

    Quicklinks

    • Learning resources
    • E-books
    • Cheat sheets
    • Blog
    • Events
    • Newsletter

    Communicate

    • About us
    • Contact sales
    • Find a partner
    • Report a website issue
    • Site status dashboard
    • Report a security problem

    RED HAT DEVELOPER

    Build here. Go anywhere.

    We serve the builders. The problem solvers who create careers with code.

    Join us if you’re a developer, software engineer, web designer, front-end designer, UX designer, computer scientist, architect, tester, product manager, project manager or team lead.

    Sign me up

    Red Hat legal and privacy links

    • About Red Hat
    • Jobs
    • Events
    • Locations
    • Contact Red Hat
    • Red Hat Blog
    • Inclusion at Red Hat
    • Cool Stuff Store
    • Red Hat Summit
    © 2026 Red Hat

    Red Hat legal and privacy links

    • Privacy statement
    • Terms of use
    • All policies and guidelines
    • Digital accessibility