New Types for the library
This is a guide on how to add a new type to the library
How to develop a new type
Adding a new type to the library will allow conversions from any developed type into the new one and from the new one into any of the others
How it works
There is an ADT (sealed trait) called SqlType that is used as a generic type for any transformation.
It works as a bridge, so any developed type can be transformed into SqlType and SqlType can be converted into specific types.
By doing so, when we add a new type, we don't need to implement conversions between all the types, we only need to implement
Conversion from our type to SqlType and Reverse Conversion from SqlType to our type,
and we will get automatically conversion from / to the rest of the types in the library.
An important note of nomenclature:
- We call
Conversionto anything that convertsinto the geneic SqlType - We call
Reverse Conversionto anything that convertsfrom generic SqlType into specific
SqlType ADT
SqlType is an Algebraic Data Type that aims to generalize all the cases that we can have from different types.
It is used internally for all the conversions.
It consists in a few case class with a Mode (Nullable, Required or Repeated) and one SqlStruct that contains a
map of String and SqlType, being the String the name of the "field"
Conversion / Reverse Conversion
There are 2 type of conversions that we should cover
Conversion= Generic SqlType -> Our TypeReverse Conversion= Our type -> Generic SqlType
Conversion
SqlType -> our type = All types -> our type
For the normal Conversion we have to create two new Type Classes
The Core of the library already have a Type Class that converts Case Classes into SqlTypes so our new Type Classes only need to derive from there.
SqlTypeToOurTypefor only types (converting a Case Class into our new type) -> e.g:Conversion[A].myNewTypeSqlInstanceToOurTypefor convert instances into our new type -> e.g:Conversion[A](myInstance: A).myNewType(Acould be an instance of any implemented type in the library)
One will be based on the other one, so don't worry, one will be really short.
Reverse Conversion
our type -> SqlType = our type -> all types
In order to implement the conversion from our new type to SqlType we have to implement
an existing Type Class called SqlTypeConversion
By doing this, we will get automatically conversion to the rest of the types of the library
How to do it
As covered in Conversion, we have to implement 2 types classes, one for types, another for instances.
Both will derive SqlTypeConversion type class into our specific type and by doing so, we will get automatically all conversions into our new type
Create a new subproject in SBT
For a new type, we need a new submodule in SBT, by doing so, we don't force anyone to import all implemented types in the library, so anyone can pick only the types that is interested in
An example from Spark. This defines a new module big-data-types-spark.
Options can be changed as desired:
- publishSettings seems to be obvious as we want to publish the new type
- crossScalaVersions specify the different Scala versions that the artifact will be created. The more the better
- crossVersionSharedSources is a method to specify different code for Scala 2.13- or 2.13+
- Dependencies, defined in a variable, the ones that we need
- dependsOn for depend on Core for compile and for tests. (There are a few useful classes in Core/Tests that we can use to test our new type)
lazy val spark = (project in file("spark"))
.settings(
name := projectName + "-spark",
publishSettings,
crossScalaVersions := List(scala212),
crossVersionSharedSources,
libraryDependencies ++= sparkDependencies
)
.dependsOn(core % "test->test;compile->compile")
Add the dependency to the root project, inside aggregate:
//Project settings
lazy val root = (project in file("."))
.configs(IntegrationTest)
.settings(noPublishSettings)
.aggregate(
core,
bigquery,
spark,
examples
)
Now, you can create a new root folder with your type name with the typical structure (src/main/scala_ ...)