The state produced in some child clause by the sheathLeaf
function is essentially "consumed" by the
elaborateSheath
function in the parent.
Due to the introduction of null checks in map
, flatMap
, and exists
, in
FlattenOptionOperation
in order to resolve #1053, as well as to support non-ansi
compliant string concatenation as outlined in #1295, large conditional composites
became common.
Due to the introduction of null checks in map
, flatMap
, and exists
, in
FlattenOptionOperation
in order to resolve #1053, as well as to support non-ansi
compliant string concatenation as outlined in #1295, large conditional composites
became common. For example:
Now, let's add a
case class Holder(value:Option[String])
// The following statement
query[Holder].map(h => h.value.map(_ + "foo"))
// Will yield the following result
SELECT CASE WHEN h.value IS NOT NULL THEN h.value || 'foo' ELSE null END FROM Holder h
getOrElse
statement to the clause that requires an additional
wrapped null check. We cannot rely on there being a map
call beforehand
since we could be reading value
as a nullable field directly from the database).
This of course is highly redundant and can be reduced to simply:
// The following statement
query[Holder].map(h => h.value.map(_ + "foo").getOrElse("bar"))
// Yields the following result:
SELECT CASE WHEN
CASE WHEN h.value IS NOT NULL THEN h.value || 'foo' ELSE null END
IS NOT NULL THEN
CASE WHEN h.value IS NOT NULL THEN h.value || 'foo' ELSE null END
ELSE 'bar' END FROM Holder h
This reduction is done by the "Center Rule." There are some other simplification
rules as well. Note how we are force to null-check both
SELECT CASE WHEN h.value IS NOT NULL AND (h.value || 'foo') IS NOT NULL THEN h.value || 'foo' ELSE 'bar' END FROM Holder h
h.value
as well as (h.value || 'foo')
because
a user may use Option[T].flatMap
and explicitly transform a particular value to null
.
How do we want beta reduction to treat Quats? Typically the right answer is when any variable x type X is reduced to t type T we check that T is a subtype of X and replace it e.g:
x.foo reduce v:V -> t:T where V is CC(foo:V) and T is CC(foo:V, bar:V)(NOTE: see the notes on Quat Shorthand Syntax in Quats.scala if unfamiliar with the syntax above) However if T is not a subtype of X, then we need to throw an error.
How do we want beta reduction to treat Quats? Typically the right answer is when any variable x type X is reduced to t type T we check that T is a subtype of X and replace it e.g:
x.foo reduce v:V -> t:T where V is CC(foo:V) and T is CC(foo:V, bar:V)(NOTE: see the notes on Quat Shorthand Syntax in Quats.scala if unfamiliar with the syntax above) However if T is not a subtype of X, then we need to throw an error. The exception to this is in the case where we are substutiting a real type for a Quat.Null or Quat.Generic (roughly speaking, a 'Bottom Type'). In that case, just do the substitution. This general behavior we call
SubstituteSubtypes
, it is also considered the default.The behavior with variable-renaming in PropagateRenames
is and ReifyLiftings
slightly different.
In these cases, it is a carte-blanche replacement of properties that is necessary. In this case
we are either plugging in a Generic type that is being specialized (e.g. X is Quat.Generic) or
reducing some type CC(foo:V) to the corresponding renamed type CC(foo:V)[foo->renameFoo].
This general behavior we call ReplaceWithReduction
i.e. Quat types are replaced with whatever
varaibles are being beta-reduced irregardless of subtyping.
Notes for the conceptual examples below.
Notes for the conceptual examples below. Gin and Tonic were used as prototypical examples of things that "are joined". In the table form, they are alude to the following tonics is Query[Tonic], tonic is Tonic gins is Query[Gin], is Gin waters is Query[Water], water is Water
ginifySpirit is some f:Spirit => Gin tonicfyWater is some f:Tonic => Water bottleGin is some f:Gin => Bottle Additionally Map(a,b,c).quat is the same as c.quat. The former is used in most examples with DetachableMap
Take renames propogated to the quats and apply them to properties
Find the innermost clause that contains an entity and attach some other kind of clause into it, e.g.
Find the innermost clause that contains an entity and attach some other kind of clause into it, e.g. a Filter. For example if you have an AST that looks like this:
FlatMap(Map(Entity(A), a, B), b, C)
Then AttachToEntity(Filter(_, _, Cond)
will result in this:
FlatMap(Map(Filter(Entity(A), {tmp}, Cond), a, B), b, C)
Note how the inner ident {tmp}
needs to be unique and not conflict with any ident
higher in the AST that is used inside the Cond clause, otherwise, the
various kinds of ASTs will be irreversibly corrupted.
Here is an example:
Take:
FlatMap(A, a, Entity(C))
Attached to the clause:
Filter(_, {dangerous_tmp}, If(a == x, foo, bar))
Which results in:
FlatMap(A, a, Filter(Entity(C), {dangerous_tmp}, If(a == x, foo, bar))
If {dangerious_tmp}
is the Ident 'a' then the following happens:
(I have added curly braces {} around this Ident just to distinguish it)
FlatMap(A, a, Filter(Entity(C), {a}, If(b == x, foo, bar))
At that point the 'a' inside the attached Filter and the outside FlatMap are indistinguishable
and indeed, the next phase of AvoidAliasConflict
will likely turn this expression
into the following:
FlatMap(A, a, Filter(Entity(C), {a1}, If(a1 == x, foo, bar))
This is of course completely incorrect because the ident {a1} should actually be {a} referring to the ident of the outer FlatMap.
Take the .returning
part in a query that contains it and return the array of columns
representing of the returning seccovtion with any other operations etc...
Take the .returning
part in a query that contains it and return the array of columns
representing of the returning seccovtion with any other operations etc... that they might contain.
A problem occurred in the original way infixes were done in that it was assumed that infix clauses represented pure functions.
A problem occurred in the original way infixes were done in that it was assumed that infix
clauses represented pure functions. While this is true of many UDFs (e.g. CONCAT
, GETDATE
)
it is certainly not true of many others e.g. RAND()
, and most importantly RANK()
. For this reason,
the operations that are done in ApplyMap
on standard AST Map
clauses cannot be done therefore additional
safety checks were introduced there in order to assure this does not happen. In addition to this however,
it is necessary to add this normalization step which inserts Nested
AST elements in every map
that contains impure infix. See more information and examples in #1534.
When actions are used with a .returning
clause, remove the columns used in the returning clause from the action.
When actions are used with a .returning
clause, remove the columns used in the returning clause from the action.
E.g. for insert(Person(id, name)).returning(_.id)
remove the id
column from the original insert.
Rename properties now relies on the Quats themselves to propagate field renames.
Rename properties now relies on the Quats themselves to propagate field renames. The previous itreations of this phase relied on schema propagation via stateful transforms holding field-renames which were then compared to Property AST elements. This was a painstakingly complex and highly error-prone especially when embedded objects were used requiring computation of sub-schemas in a process called 'schema protraction'. The new variation of this phase relies on the Quats directly since the Quats of every Identity, Lift, etc... now know what the field-names contained therein as well as the sub-Quats of any embedded property. This is fairly simple process:
The entire process above can be done with a series of stateless transformations with straighforward operations since the majority of the logic actually lives within the Quats themselves.
This stage represents Normalization Stage1: Symbolic Reduction in Philip Wadler's Paper "A Practical Theory of Language Integrated Query", given in Figure 11.
This stage represents Normalization Stage1: Symbolic Reduction in Philip Wadler's Paper "A Practical Theory of Language Integrated Query", given in Figure 11. http://homepages.inf.ed.ac.uk/slindley/papers/practical-theory-of-linq.pdf
It represents foundational normalizations done to sequences that represents queries.
In Wadler's paper, he characterizes them as for x in P ...
whereas in Quill they are
characterized as list comprehensions i.e.
P.flatMap(x => ...).
The state produced in some child clause by the
sheathLeaf
function is essentially "consumed" by theelaborateSheath
function in the parent.Note that in the documentation is use a couple of shorthands:
M - means Map Fm - means FlatMap ent - means a Ast Query. Typically just a Ast Entity e.v - this dot-shorthand means Property(e, v) where e is an Ast Ident. This is essentially a scalar-projection from the entity e. leaf - Typically this is a query-ast clause that results in a scalar type. It could be M(ent,e,e.v) or an
infix"stuff".as[Query[Int/String/Boolean/etc...] ]