# HG changeset patch # User Tomas Zeman # Date 1328863984 -3600 # Node ID 98d9c92a726fa5fb8a9de948a8003261bf22f70a # Parent 993582ca8d2e90bb22ec1ab380710f4a36ae683c aa0667047f9451b1 Global object id sequence generator (H2, pgsql) diff -r 993582ca8d2e -r 98d9c92a726f project/build/FisProject.scala --- a/project/build/FisProject.scala Fri Feb 10 09:53:04 2012 +0100 +++ b/project/build/FisProject.scala Fri Feb 10 09:53:04 2012 +0100 @@ -30,6 +30,9 @@ val servlet = "javax.servlet" % "servlet-api" % "2.5" % "provided" val jetty6 = "org.mortbay.jetty" % "jetty" % "6.1.23" % "test" + // runtime + val pgsql = "postgresql" % "postgresql" % "8.4-702.jdbc4" + // testing val scalatest = "org.scalatest" % "scalatest" % "1.3" % "test" val h2 = "com.h2database" % "h2" % "1.2.142" % "test" diff -r 993582ca8d2e -r 98d9c92a726f src/main/scala/fis/base/model/BaseSchema.scala --- a/src/main/scala/fis/base/model/BaseSchema.scala Fri Feb 10 09:53:04 2012 +0100 +++ b/src/main/scala/fis/base/model/BaseSchema.scala Fri Feb 10 09:53:04 2012 +0100 @@ -20,6 +20,7 @@ trait BaseSchema extends Schema { val codeListItems = table[CodeListItem] + on(codeListItems) { t => declare(t.id.is(autoIncremented("entity_id_seq"))) } } object BaseSchema extends BaseSchema diff -r 993582ca8d2e -r 98d9c92a726f src/main/scala/fis/base/model/SeqIdH2Adapter.scala --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/scala/fis/base/model/SeqIdH2Adapter.scala Fri Feb 10 09:53:04 2012 +0100 @@ -0,0 +1,83 @@ +/* + * Copyright 2011 Tomas Zeman + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package fis.base.model + +import java.sql.{SQLException, ResultSet} +import org.squeryl.{Session, Table} +import org.squeryl.adapters.H2Adapter +import org.squeryl.internals.{StatementWriter, FieldMetaData} + +/** + * H2 adapter which takes entity IDs from db sequences instead of + * autoincrements. Sequence names can be reused (multiple entities can use + * the same sequence as ID generator). + * + * The implementation is taken mostly from Postgres adapter which is + * + * Copyright 2010 Maxime Lévesque + * + * and licensed under the same license (Apache 2). + */ +class SeqIdH2Adapter extends H2Adapter { + override def supportsAutoIncrementInColumnDeclaration = false + + override def postCreateTable(t: Table[_], + printSinkWhenWriteOnlyMode: Option[String => Unit]) = { + + val autoIncrementedFields = t.posoMetaData.fieldsMetaData.filter( + _.isAutoIncremented) + + for(fmd <-autoIncrementedFields) { + val sw = new StatementWriter(false, this) + sw.write("create sequence if not exists ", quoteName(fmd.sequenceName)) + + if(printSinkWhenWriteOnlyMode == None) { + val st = Session.currentSession.connection.createStatement + st.execute(sw.statement) + } + else + printSinkWhenWriteOnlyMode.get.apply(sw.statement + ";") + } + } + + override def writeInsert[T](o: T, t: Table[T], sw: StatementWriter): Unit = { + + val o_ = o.asInstanceOf[AnyRef] + + val autoIncPK = t.posoMetaData.fieldsMetaData.find(fmd => + fmd.isAutoIncremented) + + if(autoIncPK == None) { + super.writeInsert(o, t, sw) + return + } + + val f = t.posoMetaData.fieldsMetaData.filter(fmd => fmd != autoIncPK.get) + + val colNames = List(autoIncPK.get) ::: f.toList + val colVals = List("nextval('" + quoteName(autoIncPK.get.sequenceName) + + "')") ::: f.map(fmd => writeValue(o_, fmd, sw)).toList + + sw.write("insert into "); + sw.write(quoteName(t.prefixedName)); + sw.write(" ("); + sw.write(colNames.map(fmd => quoteName(fmd.columnName)).mkString(", ")); + sw.write(") values "); + sw.write(colVals.mkString("(",",",")")); + } +} + +// vim: set ts=2 sw=2 et: diff -r 993582ca8d2e -r 98d9c92a726f src/main/scala/fis/base/model/SeqIdPostgreSqlAdapter.scala --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/scala/fis/base/model/SeqIdPostgreSqlAdapter.scala Fri Feb 10 09:53:04 2012 +0100 @@ -0,0 +1,58 @@ +/* + * Copyright 2011 Tomas Zeman + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package fis.base.model + +import org.squeryl.{Session, Table} +import org.squeryl.adapters.PostgreSqlAdapter +import org.squeryl.internals.StatementWriter +import scala.collection.mutable.HashSet + +/** + * Postgres adapter which enables multiple entities to share the same + * sequence. + * + * The implementation is based on Postgres adapter which is + * + * Copyright 2010 Maxime Lévesque + * + * and licensed under the same license (Apache 2). + */ +class SeqIdPostgreSqlAdapter extends PostgreSqlAdapter { + private val seqs = new HashSet[String] + + override def postCreateTable(t: Table[_], + printSinkWhenWriteOnlyMode: Option[String => Unit]) = { + + val autoIncrementedFields = t.posoMetaData.fieldsMetaData. + filter(_.isAutoIncremented). + filter(f => !seqs.contains(f.sequenceName)) + + for(fmd <-autoIncrementedFields) { + val sw = new StatementWriter(false, this) + sw.write("create sequence ", quoteName(fmd.sequenceName)) + seqs += fmd.sequenceName + + if(printSinkWhenWriteOnlyMode == None) { + val st = Session.currentSession.connection.createStatement + st.execute(sw.statement) + } + else + printSinkWhenWriteOnlyMode.get.apply(sw.statement + ";") + } + } +} + +// vim: set ts=2 sw=2 et: diff -r 993582ca8d2e -r 98d9c92a726f src/test/scala/fis/base/model/CodeListSpec.scala --- a/src/test/scala/fis/base/model/CodeListSpec.scala Fri Feb 10 09:53:04 2012 +0100 +++ b/src/test/scala/fis/base/model/CodeListSpec.scala Fri Feb 10 09:53:04 2012 +0100 @@ -25,13 +25,12 @@ import net.liftweb.mapper.{DB, DefaultConnectionIdentifier, StandardDBVendor} import net.liftweb.squerylrecord.RecordTypeMode._ import net.liftweb.squerylrecord.SquerylRecord - import org.squeryl.adapters.H2Adapter import TestBaseSchema._ val dbVendor = new StandardDBVendor("org.h2.Driver", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", Empty, Empty) DB.defineConnectionManager(DefaultConnectionIdentifier, dbVendor) - SquerylRecord.init(() => new H2Adapter) + SquerylRecord.init(() => new SeqIdH2Adapter) beforeAll { doInDB { dropAndCreate } @@ -41,6 +40,7 @@ doInDB { val cli1 = CodeListItem.createRecord.name("cli1").codeListType(1) codeListItems.insert(cli1) + cli1.id should equal (1) val l1 = from(codeListItems)(cli => select(cli)).toList l1.size should equal (1) val cl = new CodeListImpl(1, { i => i } ) diff -r 993582ca8d2e -r 98d9c92a726f src/test/scala/fis/base/model/SeqIdPostgreSqlAdapterSpec.scala --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/test/scala/fis/base/model/SeqIdPostgreSqlAdapterSpec.scala Fri Feb 10 09:53:04 2012 +0100 @@ -0,0 +1,106 @@ +/* + * Copyright 2011 Tomas Zeman + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package fis.base.model + +import net.liftweb.common._ +import org.scalatest._ +import org.scalatest.matchers.ShouldMatchers + +class SeqIdPostgreSqlAdapterSpec extends FlatSpec with ShouldMatchers + with Logger with BeforeAndAfterAllFunctions { + + import java.sql.Connection + import net.liftweb.mapper.{DB, DefaultConnectionIdentifier} + import net.liftweb.squerylrecord.SquerylRecord + import net.tz.lift.util.StandardDBVendor + import org.squeryl.PrimitiveTypeMode._ + import TestSchema._ + + val dbVendor = new StandardDBVendor("org.postgresql.Driver", + "jdbc:postgresql://localhost:7902/fis_test", Empty, Empty) { + val testQuery = "SELECT version()" + override protected def testConnection(c: Connection) = { + c.prepareStatement(testQuery).executeQuery + } + } + DB.defineConnectionManager(DefaultConnectionIdentifier, dbVendor) + SquerylRecord.init(() => new SeqIdPostgreSqlAdapter) + + beforeAll { + //doInDB { dropAndCreate } + } + + afterAll { + dbVendor.closeAllConnections_! + } + + "PostgreSqlAdapter" should "create schema" in { + doInDB { dropAndCreate } + } + + it should "create entities" in { + val a1 = Author(0, "f1", "l1", None) + authors.insert(a1) + books.insert(Book(0, "b1", a1.id, None)) + } + + it should "use global sequence" in { + val cnt: Long = from(authors)(a => compute(count)) + info("Authors count: " + cnt) + cnt should be (1) + authors.lookup(1L) should be ('defined) + val bookCnt: Long = from(books)(b => compute(count)) + bookCnt should be (1) + val b = books.lookup(2L) + info("Book: " + b) + b should be ('defined) + } + + def doInDB(block: => Any) { + DB.use(DefaultConnectionIdentifier) { c => + block + c.commit + } + } +} + +import org.squeryl.{KeyedEntity, Schema} +import org.squeryl.PrimitiveTypeMode._ + +case class Author(val id: Long, val firstName: String, val lastName: String, + val email: Option[String]) extends KeyedEntity[Long] { + def this() = this(0,"","",Some("")) +} + +case class Book(val id: Long, var title: String, var authorId: Long, + var coAuthorId: Option[Long]) extends KeyedEntity[Long] { + def this() = this(0,"",0,Some(0L)) +} + +object TestSchema extends Schema { + val authors = table[Author] + val books = table[Book] + + def dropAndCreate { + drop + create + } + + on(authors) { t => declare(t.id.is(autoIncremented("entity_id_seq"))) } + on(books) { t => declare(t.id.is(autoIncremented("entity_id_seq"))) } +} + +// vim: set ts=2 sw=2 et: