Mapper and Primary Keys

59 views
Skip to first unread message

Peter Robinett

unread,
Jul 20, 2009, 9:30:32 PM7/20/09
to Lift
Hi all,

I'm using Mapper with MAC addresses as my primary keys:
class Node extends KeyedMapper[String, Node] {
def getSingleton = Node
/* MAC address as primary key */
def primaryKeyField = mac
object mac extends MappedStringIndex(this, 17) {
override def dbPrimaryKey_? = true
override def dbDisplay_? = true
}
}

Unfortunately, if I try to create a new Node and assign a MAC address,
I get an error saying that I don't have permission:
scala> Node.create.mac("00:1d:c9:00:04:9f")
java.lang.Exception: Do not have permissions to set this field
at net.liftweb.mapper.MappedField$class.set(MappedField.scala:425)
at net.liftweb.mapper.MappedString.set(MappedString.scala:38)
at net.liftweb.mapper.MappedString$$anonfun$apply$6.apply
(MappedString.scala:121)
at net.liftweb.mapper.MappedString$$anonfun$apply$6.apply
(MappedString.scala:121)
at net.liftweb.util.Full.f...

How do I go about creating a new Node with the mac address – the
primary key – of my choosing?

Thanks,
Peter Robinett

Derek Chen-Becker

unread,
Jul 21, 2009, 10:58:43 AM7/21/09
to lif...@googlegroups.com
I think that this should work, but I haven't tested it:


class Node extends KeyedMapper[String, Node] {
 def getSingleton = Node
 /* MAC address as primary key */
 def primaryKeyField = mac
 object mac extends MappedStringIndex(this, 17) {
        override def writePermission_? = true

        override def dbDisplay_? = true
 }
}

Note that primaryKey_? is already set to true in MappedStringIndex. writePermission_? is set to false by default...

Derek

Peter Robinett

unread,
Jul 21, 2009, 3:30:20 PM7/21/09
to Lift
Thanks Derek, but unfortunately it only works halfway: I am able to
set MAC address of my new Node and save it (returning true) and my
MySQL database row is created, but the mac column is null.

Peter

Peter Robinett

unread,
Jul 21, 2009, 4:19:02 PM7/21/09
to Lift
I should add that I believe this is because the field isn't being
marked as dirty and so isn't saved. This is in MappedField:
def dirty_? = !dbPrimaryKey_? && _dirty_?

How would I overload it in my object mac definition?

Thanks,
Peter

jon

unread,
Jul 21, 2009, 10:20:07 PM7/21/09
to Lift
This doesn't address your question, so I'll apologize in advance, but
why do you want to use a mac address as a primary key? Also, a mac
address represents six bytes, why not pack into a long? This probably
isn't the most efficient or prettiest way:

def macToLong(mac: String) = java.lang.Long.parseLong(mac.replace
(":",""),16)

def longToMac(mac: Long) = (0 to 5).reverse.map( i => (mac & ((0xff)
<< (i * 8).asInstanceOf[Long])) >> (i * 8)).
map( l => String.format("%02x",l.asInstanceOf[Object])).mkString
(":")


println( longToMac(macToLong("00:1d:c9:00:04:9f")))

Peter Robinett

unread,
Jul 22, 2009, 12:19:17 PM7/22/09
to Lift
Because it's unique across systems and maps directly to hardware I'm
tracking. But thanks for the equations!

Peter

Peter Robinett

unread,
Jul 28, 2009, 5:09:44 PM7/28/09
to Lift
Sorry to bump this, but does anyone have any idea why my mac column is
not being saved to the database, despite the save method returning
true?

Derek Chen-Becker

unread,
Jul 28, 2009, 7:19:20 PM7/28/09
to lif...@googlegroups.com
Did you override the dirty_? def?

Derek

David Pollak

unread,
Jul 29, 2009, 2:58:32 PM7/29/09
to lif...@googlegroups.com
Peter,

Please fork http://github.com/dpp/lift_1_1_sample/tree/master and create an app that's failing in your GitHub repo.  Once we have code to work with, we can solve the problem.

Thanks,

David
--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890
Follow me: http://twitter.com/dpp
Git some: http://github.com/dpp

Peter Robinett

unread,
Jul 29, 2009, 3:37:57 PM7/29/09
to Lift
Hi Derek,

I'm afraid I'm not sure how to do this, since _dirty_? is a private
var in MappedField:
/**
* Is the field dirty
*/
private var _dirty_? = false

/**
* Is the field dirty (has it been changed since the record was loaded
from the database
*/
def dirty_? = !dbPrimaryKey_? && _dirty_?

/**
* Make the field dirty
*/
protected def dirty_?(b: Boolean) = _dirty_? = b


Trying various things I still cannot get it to work:
override def dirty_? = _dirty_? leads to the compiler error 'not
found: value _dirty_?'
override def dirty_? = super._dirty_? leads to the compiler error
'variable _dirty_? cannot be accessed in
net.liftweb.mapper.MappedStringIndex[com.equalnetworks.model.Node]
with ScalaObject'

If I redefine _dirty_?:
private var _dirty_? = false
override def dirty_? = _dirty_?
I am able to compile but I still get blank rows being created in the
database.

What do you suggest?

David, I'll have some sample code soon.

Peter

Naftoli Gugenheim

unread,
Jul 29, 2009, 3:42:52 PM7/29/09
to pe...@bubblefoundry.com, lif...@googlegroups.com
Did you try to override def dirty_? and def dirty_?(b: Boolean), and in the latter set your own private variable and read it in dirty_? (the getter)?

-------------------------------------

Peter Robinett

unread,
Jul 29, 2009, 4:26:43 PM7/29/09
to Lift
Hi all,

My sample code is here: http://github.com/pr1001/lift_1_1_sample/tree/master

I hadn't been overriding def dirty_?(b: Boolean) but I see why I need
to. With everyone's suggestions, here is how I try to set the MAC
address:
$ mvn scala:console

scala> new bootstrap.liftweb.Boot().boot

scala> import com.liftcode.model._
import com.liftcode.model._

scala> val c = Cat.create.mac("00:1d:c9:00:04:9f")
c: com.liftcode.model.Cat = com.liftcode.model.Cat=
{name=,mac=00:1d:c9:00:04:9f,weight=0}

scala> c.dirty_?
res1: Boolean = true

scala> c.mac.dirty_?
res2: Boolean = true

scala> c.save
res3: Boolean = true

scala> Cat.findAll()
res4: List[com.liftcode.model.Cat] = List(com.liftcode.model.Cat=
{name=,mac=,weight=0})

Peter

Derek Chen-Becker

unread,
Jul 29, 2009, 4:26:45 PM7/29/09
to lif...@googlegroups.com
Right. Something like:

private var myDirty = false

override def dirty_? = myDirty

override def dirty_?(b : Boolean) = { myDirty = b; super.dirty_?(b) }

I think that that should work.

Derek

David Pollak

unread,
Jul 29, 2009, 6:36:13 PM7/29/09
to lif...@googlegroups.com
I had to add a property on MappedField for dbGenerated_? which has to be set to false.

Here's the change set and the revised, working (wait for an hour for Hudson to build the new code) version.
diff.txt
prim_key.tgz

Peter Robinett

unread,
Jul 29, 2009, 6:56:05 PM7/29/09
to Lift
Thanks Derek, but I'm afraid it doesn't.

Peter

On Jul 29, 1:26 pm, Derek Chen-Becker <dchenbec...@gmail.com> wrote:
> Right. Something like:
>
> private var myDirty = false
>
> override def dirty_? = myDirty
>
> override def dirty_?(b : Boolean) = { myDirty = b; super.dirty_?(b) }
>
> I think that that should work.
>
> Derek
>

Peter Robinett

unread,
Jul 29, 2009, 7:35:22 PM7/29/09
to Lift
Thanks, David, I am now able to save the mac address. I am, however,
able to create multiple rows in the database with the same mac
address, suggesting that that the uniqueness of the primary key is not
being enforced. This surprised me, as MappedStringIndex extends
MappedUniqueId. Does something else need to be changed?

Thanks all for your help!

Peter

On Jul 29, 3:36 pm, David Pollak <feeder.of.the.be...@gmail.com>
wrote:
> Beginning Scalahttp://www.apress.com/book/view/1430219890
>  diff.txt
> 10KViewDownload
>
>  prim_key.tgz
> 63KViewDownload

David Pollak

unread,
Jul 29, 2009, 7:51:22 PM7/29/09
to lif...@googlegroups.com
On Wed, Jul 29, 2009 at 4:35 PM, Peter Robinett <pe...@bubblefoundry.com> wrote:

Thanks, David, I am now able to save the mac address. I am, however,
able to create multiple rows in the database with the same mac
address, suggesting that that the uniqueness of the primary key is not
being enforced. This surprised me, as MappedStringIndex extends
MappedUniqueId. Does something else need to be changed?

Please send me the output of what Schemifier does when it creates the database
 



--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890

Peter Robinett

unread,
Jul 29, 2009, 8:24:55 PM7/29/09
to Lift
Ahh, here we go:
INFO - CREATE TABLE users (id BIGINT NOT NULL GENERATED ALWAYS AS
IDENTITY , firstname VARCHAR(32) , lastname VARCHAR(32) , email VARCHAR
(48) , locale VARCHAR(16) , timezone VARCHAR(32) , password_pw VARCHAR
(48) , password_slt VARCHAR(20) , textarea VARCHAR(2048) , superuser
SMALLINT , validated SMALLINT , uniqueid VARCHAR(32))
INFO - ALTER TABLE users ADD CONSTRAINT users_PK PRIMARY KEY(id)
INFO - CREATE TABLE cats (name VARCHAR(128) , mac VARCHAR(17) , weight
INTEGER)
INFO - ALTER TABLE cats ADD CONSTRAINT cats_PK PRIMARY KEY(mac)
ERROR 42831: 'MAC' cannot be a column of a primary key or unique key
because it can contain null values.
at org.apache.derby.iapi.error.StandardException.newException(Unknown
Source)
at
org.apache.derby.impl.sql.compile.TableElementList.checkForNullColumns
(Unknown Source)
at org.apache.derby.impl.sql.compile.TableElementList.validate
(Unknown Source)
at org.apache.derby.impl.sql.co...

I have a defaultValue, so I'm not sure why it thinks the column can be
null. Perhaps because it's lazy?
override lazy val defaultValue = randomString(maxLen)

Peter

On Jul 29, 4:51 pm, David Pollak <feeder.of.the.be...@gmail.com>

David Pollak

unread,
Jul 29, 2009, 10:44:33 PM7/29/09
to lif...@googlegroups.com
Did you look at all the overridden methods on the Cat primary key in the example?  You have to override the method that defines the column in the RDBMS to define the column as UNIQUE NOT NULL.
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890

Peter Robinett

unread,
Jul 30, 2009, 11:16:19 AM7/30/09
to Lift
Oops, you're right, I just glossed over that. Using that I now get an
error when I try to use a pre-existing primary key:
scala> Cat.create.mac("00:1d:c9:00:04:9f").save
ERROR 23505: The statement was aborted because it would have caused a
duplicate key value in a unique or primary key constraint or unique
index identified by 'CATS_PK' defined on 'CATS'.
at org.apache.derby.iapi.error.StandardException.newException(Unknown
Source)
at org.apache.derby.impl.sql.execute.IndexChanger.insertAndCheckDups
(Unknown Source)
at org.apache.derby.impl.sql.execu...

I have just one more question: is this normal for save to throw an
exception here? I understand its benefits but I find the situation
less 'exceptional' than just a failure to save. I guess this was a
choice Derby made, not Lift...

Peter

PS I updated the fork to reflect the working code:
http://github.com/pr1001/lift_1_1_sample/tree/master

On Jul 29, 7:44 pm, David Pollak <feeder.of.the.be...@gmail.com>
> ...
>
> read more »

David Pollak

unread,
Jul 30, 2009, 1:47:34 PM7/30/09
to lif...@googlegroups.com
On Thu, Jul 30, 2009 at 8:16 AM, Peter Robinett <pe...@bubblefoundry.com> wrote:

Oops, you're right, I just glossed over that. Using that I now get an
error when I try to use a pre-existing primary key:
scala> Cat.create.mac("00:1d:c9:00:04:9f").save
ERROR 23505: The statement was aborted because it would have caused a
duplicate key value in a unique or primary key constraint or unique
index identified by 'CATS_PK' defined on 'CATS'.
       at org.apache.derby.iapi.error.StandardException.newException(Unknown
Source)
       at org.apache.derby.impl.sql.execute.IndexChanger.insertAndCheckDups
(Unknown Source)
       at org.apache.derby.impl.sql.execu...

I have just one more question: is this normal for save to throw an
exception here? I understand its benefits but I find the situation
less 'exceptional' than just a failure to save. I guess this was a
choice Derby made, not Lift...

This is a correct choice.  If you are trying to insert a second record with an identical primary key should generate an exception.

You could try:

Cat.find("foo") openOr Cat.create.mac("foo")

This will find an existing record or create a new one.

Peter Robinett

unread,
Aug 26, 2009, 5:37:52 PM8/26/09
to Lift
Just to follow up on this, using CRUDify's edit page to update a model
entry using this primary key causes this exception to be thrown. Is
there any way to give Mapper and/or CRUDify a clue that it should
update the existing entry, not try to create a new one?

Thanks,
Peter

On Jul 30, 10:47 am, David Pollak <feeder.of.the.be...@gmail.com>
> ...
>
> read more »

Somindra Bhattacharya

unread,
Sep 2, 2009, 5:24:59 AM9/2/09
to Lift
On Jul 30, 3:36 am, David Pollak <feeder.of.the.be...@gmail.com>
wrote:
> I had to add a property on MappedField for dbGenerated_? which has to be set
> to false.
>
> Here's the change set and the revised, working (wait for an hour for Hudson
> to build the new code) version.
>
> On Wed, Jul 29, 2009 at 1:26 PM, Peter Robinett <pe...@bubblefoundry.com>wrote:
>

Hi,

I am new to programming for the Web and Lift is the first web
framework I am using. Therefore, please excuse me if my questions seem
naive.

I have the same problem that Peter was facing. David, I looked through
your diff and tried making similar changes to my model class. However,
the primary key is still empty in the database.

Here is the class that I have defined:

class MJData extends KeyedMapper[String,MJData]
{
def getSingleton = MJData

override def primaryKeyField = uid

object uid extends MappedStringIndex[MJData](this,128)
{
override def writePermission_? = true
override def dbDisplay_? = true
// override def dbAutoGenerated_? = false //commented because it
does not compile

override lazy val defaultValue = "11232312" // any string for
now...

private var myDirty_? = false
override protected def dirty_?(b: Boolean) = myDirty_? = b
override def dirty_? = myDirty_?
}

After looking at the pom.xml that Peter had posted, I upgraded to the
1.1-SNAPSHOT. Is there something else that I need to do?

Another question:

> <snip> (wait for an hour for Hudson
> to build the new code) version.

What does this mean? I am using Maven and I am assuming that it will
"pull" any new changes to the framework.

Thanks for reading.

Regards,
Som

Somindra Bhattacharya

unread,
Sep 2, 2009, 10:29:52 AM9/2/09
to Lift
Hi again,

I just wanted to mention that I cannot override dbAutogenerated_?. I
get the following error:

error: method dbAutogenerated_? overrides nothing
override def dbAutogenerated_? = false

Looks to me that I am not using the latest framework code. How do I
verify this?

Thanks,
Som

Indrajit Raychaudhuri

unread,
Sep 2, 2009, 11:59:14 AM9/2/09
to Lift
Som,

1. Your source code had dbAutoGenerated_?. The actual function is
dbAutogenerated_? (g is in lower case).
Hope you have the right case for 'g' one :)

2. If your project model (pom.xml) has lift versions set to 1.1-
SNAPSHOT, you must be on the master and thus on the latest code.
FWIW, dbAutoGenerated_? is part of the trait BaseMappedField in lift-
mapper

Verify if both things are in order at give it another spin :)

Cheers, Indrajit

Somindra Bhattacharya

unread,
Sep 3, 2009, 5:55:44 AM9/3/09
to Lift
Indrajit,

Thanks! The problem was in the pom.xml. I fixed that to 1.1-SNAPSHOT
and the problem went away.

Regards,
Som
Reply all
Reply to author
Forward
0 new messages