Issue 80 - Composite PK Entity Relations

33 views
Skip to first unread message

Markus Kahl

unread,
Jan 16, 2011, 9:51:30 AM1/16/11
to squeryl-contributors
Hey,

I've looked through the code and still try to wrap my head around it.

I'm far away from understanding how the whole relation thing works
(what happens when you declare the relation). But I have a hunch what
the EqualityExpression in the via block is used for.

Given the following code:

val parentToChildren = oneToManyRelation(nodes, nodes).via
{ (parent, child) =>
parent.id === child.parentId
}

The EqualityExpression resulting from the "===" is basically
responsible for creating the
SQL clause which selects all the children?

So if the nodes had a Long PK "parent.id === child.parentId" it would
write:
"Node.id = ?" to get the one-side and
"? = Node.parentId" to get the many-side?

So if I want to make this work with CompositeKeys "all there is to do"
is to make
an according EqualityExpression (although it has to evaluate to one
first) that writes:
"Node.ckPartOne = ? AND Node.ckPartTwo = ?" to get the one-side and
"? = Node.parentCkPartOne AND ? = Node.parentCkPartTwo" to get the
many-side?

Maxime Lévesque

unread,
Jan 16, 2011, 10:11:05 AM1/16/11
to squeryl-co...@googlegroups.com
Given the following code:

   val parentToChildren = oneToManyRelation(nodes, nodes).via
{ (parent, child) =>
       parent.id === child.parentId
   }

The EqualityExpression is really just a BinaryOperatorNodeLogicalBoolean (it extends it)

the difference between the two is that BinaryOperatorNodeLogicalBoolean can represent any
binary expression that evaluated to a boolean, ex :

- x < 1
- x in (subselect...)
- x ===4

while EqualityExpression is strictly for
- x ===4

So if 'via' took a BinaryOperatorNodeLogicalBoolean, one could give it something like  x < 1
and the compiler wouldn't prevent it.
 
The EqualityExpression resulting from the "===" is basically
responsible for creating the
SQL clause which selects all the children?

Yes, but just the equality part of the WHERE clause

with any query you can do : println(yourQuery.dumpAst), it's very usefull for
seeing the syntactic tree.

As you probably noticed, most of what Squeryl does falls under 3 categories :

1) build the AST
2) translate the AST into SQL
3) Map the result set into objects

what you're trying to do here in issue 80 is in (1)


So if the nodes had a Long PK "parent.id === child.parentId" it would
write:
 "Node.id = ?" to get the one-side and
 "? = Node.parentId" to get the many-side?

So if I want to make this work with CompositeKeys "all there is to do"
is to make
an according EqualityExpression (although it has to evaluate to one
first) that writes:
 "Node.ckPartOne = ? AND Node.ckPartTwo = ?" to get the one-side and
 "? = Node.parentCkPartOne AND ? = Node.parentCkPartTwo" to get the
many-side?

Exactly


Cheers !

Markus Kahl

unread,
Jan 19, 2011, 6:10:01 AM1/19/11
to squeryl-contributors
Alright I think I've made some progress.
Now there is one point where I'm stuck at for now,
since I don't really understand, what is actually happening there:

https://github.com/max-l/Squeryl/blob/master/src/main/scala/org/squeryl/dsl/QueryDsl.scala#L512

Firstly: Isn't this just the same as writing

(ee_.left._fieldMetaData, ee_.right._fieldMetaData)

right there (left and right being TypedExpressionNodes)?

Also when is a node a SelectElementReference?
How do I know that? I mean can I assume any ExpressionNode that is
used
as part of WHERE etc. to be one?


-- Markus

Markus Kahl

unread,
Jan 19, 2011, 6:25:46 AM1/19/11
to squeryl-contributors
Another questions:

There is BinaryOperatorNode for expressions such as "x = y".
Is there also already one for expressions such as "x = y AND y = z
AND ...",
which I would need for the CompositeKey equality?

-- Markus

On 19 Jan., 12:10, Markus Kahl <machis...@googlemail.com> wrote:
> Alright I think I've made some progress.
> Now there is one point where I'm stuck at for now,
> since I don't really understand, what is actually happening there:
>
> https://github.com/max-l/Squeryl/blob/master/src/main/scala/org/squer...

Markus Kahl

unread,
Jan 19, 2011, 6:29:05 AM1/19/11
to squeryl-contributors
Or can this just done by nesting binary Nodes?

(x = y AND y = z) AND x = z

Maxime Lévesque

unread,
Jan 19, 2011, 7:07:18 AM1/19/11
to squeryl-co...@googlegroups.com

Markus, I went ahead and commented the code directly :

   https://github.com/max-l/Squeryl/commit/fba30ee928dd3ead5ff3fbc5bbb4f0522ba556e1

On Wed, Jan 19, 2011 at 6:29 AM, Markus Kahl <mach...@googlemail.com> wrote:
Or can this just done by nesting binary Nodes?

   (x = y AND y = z) AND x = z

Yes, the AST looks something like this :

BinaryOperatorNode(
  BinaryOperatorNode(
    BinaryOperatorNode(x,y,"="),
    BinaryOperatorNode(y,z,"="), "AND"),
  BinaryOperatorNode(x,z,"="), "AND")
 

in detalis the BinaryOperatorNode(x,y,"=") would be :

BinaryOperatorNode(
  SelectElementReference(selectElement=FieldSelectElement(fieldMataData=FieldMataData(x))),
  SelectElementReference(selectElement=FieldSelectElement(fieldMataData=FieldMataData(y))), "=")

every node in the AST can print itself (toString), doing  print(ast)
can be very informative.

Cheers

Markus Kahl

unread,
Jan 20, 2011, 5:45:45 AM1/20/11
to squeryl-contributors
Ok CompositeKeys in the via part at least compile now.
Theoretically they should work too, but they don't.

Because in the via the a NPE occurs:

https://github.com/machisuji/Squeryl/blob/issue80/src/test/scala/org/squeryl/tests/issues/Issue80.scala#L122

->

https://github.com/machisuji/Squeryl/blob/master/src/main/scala/org/squeryl/internals/FieldReferenceLinker.scala#L290

Right there. I thought it's because of personId actually being null
and
assigned it a default value in UniqueAddress, but that hasn't changed
anything.

Any ideas?

On 19 Jan., 13:07, Maxime Lévesque <maxime.leves...@gmail.com> wrote:
> Markus, I went ahead and commented the code directly :
>
> https://github.com/max-l/Squeryl/commit/fba30ee928dd3ead5ff3fbc5bbb4f...
>
> On Wed, Jan 19, 2011 at 6:29 AM, Markus Kahl <machis...@googlemail.com>wrote:
>
> > Or can this just done by nesting binary Nodes?
>
> >    (x = y AND y = z) AND x = z
>
> Yes, the AST looks something like this :
>
> BinaryOperatorNode(
>   BinaryOperatorNode(
>     BinaryOperatorNode(x,y,"="),
>     BinaryOperatorNode(y,z,"="), "AND"),
>   BinaryOperatorNode(x,z,"="), "AND")
>
> in detalis the BinaryOperatorNode(x,y,"=") would be :
>
> BinaryOperatorNode(
>
> SelectElementReference(selectElement=FieldSelectElement(fieldMataData=Field­MataData(x))),
>
> SelectElementReference(selectElement=FieldSelectElement(fieldMataData=Field­MataData(y))),

Markus Kahl

unread,
Jan 20, 2011, 5:46:53 AM1/20/11
to squeryl-contributors
Correction: The NPE is thrown only at line 294, but I mean it's
because res, which
is supposed to be assigned at line 290 is null.

On 20 Jan., 11:45, Markus Kahl <machis...@googlemail.com> wrote:
> Ok CompositeKeys in the via part at least compile now.
> Theoretically they should work too, but they don't.
>
> Because in the via the a NPE occurs:
>
> https://github.com/machisuji/Squeryl/blob/issue80/src/test/scala/org/...
>
> ->
>
> https://github.com/machisuji/Squeryl/blob/master/src/main/scala/org/s...

elem

unread,
Jan 20, 2011, 9:14:42 AM1/20/11
to squeryl-contributors

I see one problem, a composite key cannot be a 'val', it must be a
'def' :

class UniquesAddress(
street: String,
postalCode: String,
val personId: CompositeKey2[String, String]
) extends Address(street, postalCode) {
val person: ManyToOne[Person with UniqueName] =
Population.uniquePersonToAddress.right(this)
}

There is an issue about this :

https://github.com/max-l/Squeryl/issues#issue/57

I tries running your test in SBT and the 'test' command won't pick it
up,
do you have a clue ?

Markus Kahl

unread,
Jan 20, 2011, 5:42:11 PM1/20/11
to squeryl-contributors
Hey, I'm gonna try that.

Frankly I just can't stand the specs framework.
So I used scalatest instead.
Though for some reason I haven't committed the changed sbt project.

It needs a:

val scalatest = "org.scalatest" % "scalatest" % "1.2.1-SNAPSHOT" %
"test"

Then it should work.
Actually this isn't in my current project file either.
But it was there at some point.
I must have reverted it by mistake.

Markus Kahl

unread,
Jan 21, 2011, 4:34:54 PM1/21/11
to squeryl-contributors
Mh no that's not the problem here.
I think the problem rather is that composite keys were apparently
never meant to be foreign keys.
That's become especially obvious when I saw the ForeignKeyDeclaration.
I had to extend it for it to work with composite keys.

Well, still it does not work.
Unfortunately I don't really understand what's happening in the
PosoPropertyAccessInterceptor.
It obviously can't cope with the composite key being null.
Primitive foreign keys get default values (0 for long, false for
boolean etc.) I guess.
How is it with Strings then?

There must be code somewhere that provides a default value for that to
construct the
relation.

Maxime?

Maxime Lévesque

unread,
Jan 21, 2011, 5:17:55 PM1/21/11
to squeryl-co...@googlegroups.com

You're right, the use of compositeKeys along with relations was not forseen,
they (composite keys) only serve as syntactic sugar at this time.
That is the main difficulty of this problem, since the relation mechanism
expects only simple types.
I'm not sure about String keys as foreign key in relations, it might very
well be that they are untested. If you find out that it is broken, I'll
treat it as high priority.

Max

Markus Kahl

unread,
Jan 22, 2011, 6:35:46 AM1/22/11
to squeryl-contributors
It works with Strings. Only composite keys seem to be the problem.
I might have a vague idea why.
We will see, I will keep at it!

On 21 Jan., 23:17, Maxime Lévesque <maxime.leves...@gmail.com> wrote:
> You're right, the use of compositeKeys along with relations was not forseen,
> they (composite keys) only serve as syntactic sugar at this time.
> That is the main difficulty of this problem, since the relation mechanism
> expects only simple types.
> I'm not sure about String keys as foreign key in relations, it might very
> well be that they are untested. If you find out that it is broken, I'll
> treat it as high priority.
>
> Max
>

Markus Kahl

unread,
Jan 26, 2011, 3:01:05 AM1/26/11
to squeryl-contributors
My guess was right, it's simply null because there is no sample
created for CompositeKeys.
I have added new code accordingly to FieldMetaData.scala, more
precisely to the _defaultValueFactory.
The sample is simply a >> new CompositeKey2("", "") <<. Now the
NullPointerException is gone.
However, it still does not work.

Given the following relation:

val uniquePersonToAddress = {
val rel = oneToManyRelation(uniquePeople, uniquesAddresses)
rel.via((person, address) => person.id === address.personId)
}

Where person.id is

def id = compositeKey(firstName, lastName)

and address.personId is

val personId: CompositeKey2[String, String]

Now I have added some printf stuff to FieldReferenceLinker.scala:
https://gist.github.com/796382

When I run my test and Squeryl tries to create the relation from above
I get the following output:

set members to empty ArrayBuffer
members before: None
members afterwards: Some(ArrayBuffer('SelectElementReference:cannot
evaluate:java.lang.String, 'SelectElementReference:cannot
evaluate:java.lang.String))

set members to empty ArrayBuffer
members before: None
members afterwards: Some(ArrayBuffer())

Can someone explain that to me?
As it seems (to me at the moment) the members get magically filled
with two SelectElementReferences.
Although only those of person.id, not those of address.personId,
which is the problem in the end, since the Equality cannot be built
like that:

private [squeryl] def buildEquality(ck: CompositeKey):
EqualityExpression = {
val it1 = members.toList
val it2 = ck.members.toList
EqualityExpression(it1 zip it2)
}

I mean the _compositeKeyMembers is an empty ArrayBuffer in both cases
apparently,
how can it be mapped to two SelectElementReferences in one case?

Real debugging would maybe help in this case,
but I haven't managed to set up my IDE for this.
Although there is some kind of cli debugging possible with Scala 2.8.
I will try that next.

Maxime Lévesque

unread,
Jan 26, 2011, 8:45:23 AM1/26/11
to squeryl-co...@googlegroups.com

 I recommend very strongly you try IDEA intellij ... it simply rocks... you just set a breakpoint and you inspect the
whole stack, local vars, etc...  And for Squeryl the project files are all there, you should get it up and
running in no time...
Reply all
Reply to author
Forward
0 new messages