Kotlin, Pre-packaged database has an invalid schema: Column order is wrong? - kotlin

I exported a copy of the database from the emulator and moved the test database to an external file. This has been working well for sometime, however, today something changed and this error appeared.
Pre-packaged database has an invalid schema: tableLinkUserToPassword
Expected:
TableInfo
{
name='tableLinkUserToPassword',
columns = {
userId = Column { name='userId', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null' },
password = Column { name='password', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null' }
},
foreignKeys = [ForeignKey
{
referenceTable='tableUser', onDelete='CASCADE', onUpdate='NO ACTION', columnNames=[userId], referenceColumnNames=[userId]
}
],
indices = [Index { name='index_tableLinkUserToPassword_password', unique=false, columns=[password], orders=[ASC] },
Index { name='index_tableLinkUserToPassword_userId', unique=false, columns=[userId], orders=[ASC]}
]
}
Found:
TableInfo{name='tableLinkUserToPassword', columns={password=Column{name='password', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, userId=Column{name='userId', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[ForeignKey{referenceTable='tableUser', onDelete='CASCADE', onUpdate='NO ACTION', columnNames=[userId], referenceColumnNames=[userId]}], indices=[Index{name='index_tableLinkUserToPassword_userId', unique=false, columns=[userId], orders=[ASC]}, Index{name='index_tableLinkUserToPassword_password', unique=false, columns=[password], orders=[ASC]}]}
The UserId and the Password columns are switched in order. And notNull=false seems not to match.
I have DB Browser but how would I change the order or columns?
Entity:
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
#Entity(
tableName = "tableLinkUserToPassword",
foreignKeys = [
ForeignKey(
entity = EntityUser::class,
parentColumns = ["userId"],
childColumns = ["userId"],
onDelete = ForeignKey.CASCADE
)
]
)
data class EntityLinkUserToPassword(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(index = true)
val userId: Int,
val password: String,
)
Thanks for any assistance

The order in which the columns appear is not an issue, it's the order in which they are extracted (I think). It is the reported values that matter.
However, what can be seen, as an example, is that the password column EXPECTED (what is extracted from the #Entity annotated class) has the NOT NULL constraint (i.e. it must not be null) as per notNull=true, whilst it found that there is no NOT NULL constraint coded for the password column in the FOUND (i.e. the pre-packaged database) as per notNull=false.
So you either have to change the EntityLinkUserToPassword class to allow null, or change the pre-packaged database to have NOT NULL coded on the password column.
e.g. val password: String?,
You need to check ALL columns for discrepancies between the found and expected.
P.S. a second index on the userId column is a waste and inefficient. The PrimaryKey is an index. So there is no need for the #ColumnInfo annotation.
However, again there is another discrepancy the second index on the pre-packaged database is on the password column. So you should have the #ColumnInfo annotation moved to apply to the password val/column e.g. I believe that you want :-
data class EntityLinkUserToPassword(
#PrimaryKey(autoGenerate = true)
val userId: Int,
#ColumnInfo(index = true)
val password: String?,
)
Note the above is based upon observation, the suggested code has not been tested and is not necessarily full comprehensive, so may contain omissions and or errors.
I have DB Broswer but how would I chnage the order or columns?
You would/could :-
Rename the EntityLinkUserToPassword table e.g. ALTER TABLE EntityLinkUserToPassword RENAME TO renamed_EntityLinkUserToPassword;
Use UPDATE renamed_EntityLinkUserToPassword SET password = 'a suitable default value' WHERE password IS NULL;
This so that you don't get NOT NULL constraint conflicts when copying the data.
Create the new table with the correct schema (see later)
Use INSERT INTO EntityLinkUserToPassword SELECT * FROM renamed_EntityLinkUserToPassword ORDER BY userId ASC;
DROP TABLE IF EXISTS renamed_EntityLinkUserToPassword'
Getting the correct schema
With the classes annotated with #Entity coded as required AND defined in the entities parameter of the #Database annotation, compile the project.
using the Android View in Android Studio look at the Java(generated) for a class the same name as the #Database annotated class but suffixed with _Impl.
Look for the createAllTables method. The SQL for the creation of the tables is hard coded. Copy it and this will be the EXACT SQL.

Related

#Index annotation leads to "This annotation is not applicable to target 'member property with backing field'"

I am trying to create an index on a foreign key using the #Index annotation. Unfortunately, the compiler complains with the following message:
This annotation is not applicable to target 'member property with backing field'
What am I doing wrong here?
#Entity
#Table(name = "my_entity")
class MyEntity(someValue: Long) : BaseEntity(someValue) {
// .. some fields
#OneToOne
#JoinColumn(name = "another_entity")
#Index(name = "ix_another_entity")
var anotherEntity: AnotherEntity? = null
}
The #Index cannot be used like this (neither in java nor in kotlin), instead you can use it e.g. as part of the #Table annotation:
#Table(name= "my_entity", indexes = [ Index(columnList = "another_entity") ])
Specifying an Index (Non-Unique Key) Using JPA
(note that e.g. MySQL auto creates an index for foreign keys: Does MySQL index foreign key columns automatically? )

Spring Boot Autoimport import.sql file - Getting a "Column Not found" Error

So I have a table in postgresql and I want to fill it with some values on application start. Here is the Entity (in Kotlin):
#Entity
class CalculationType(
#Id #GeneratedValue(strategy = GenerationType.AUTO)
var id: Long = 0,
var guid: String = "",
var title: String = "",
var modified: Date,
var creation: Date
)
my import.sqlfile is located under ..\.\resources. it looks like this:
insert into calculation_type (guid, title, modified, creation) VALUES ("test", "title", "2013-12-12", "2013-12-12");
I am getting an error in German, traslated it says Column »test« does not exist.
Thats quite strange because test is in my values, not in my column definitions.
Does anyone know whats going on?
Character constants need single quotes
insert into calculation_type (guid, title, modified, creation) VALUES ('test", 'title', '2013-12-12', '2013-12-12');

micronaut-data and composite key mapping

I have an entity with a composite key
#Entity
data class Page(
#EmbeddedId
val pageId : PageId,
...
)
#Embeddable
data class PageId (
#Column(name = "id")
val id: UUID,
#Column(name = "is_published")
val isPublished: Boolean
)
But I need to respect the existing column names in the db table, which are 'id' and 'is_published'
But querying the db with a JDBCRepository I get the error:
SQL Error executing Query: ERROR: column page_.page_id_published does
not exist
Is there any way that I can map the columns correctly?
Try and error led me to the answer, somehow Micronaut does not like a Boolean to be named 'isPublished', when I rename it to 'published' it works fine:
data class PageId (
#MappedProperty(value = "id")
val id: UUID,
#MappedProperty(value = "is_published")
val published: Boolean)

GORM Domain Mapping Issue

I've got a bit of a complicated domain model I'm trying to implement and I'm having some trouble. (On top of that, I'm quite new to all this!)
I have a User domain which has multiple roles and multiple tests. The Role domain works great. The Test domain is a bit more compilciated though because it requires two foreign keys instead of just 1 like in the Role domain. The first foreign key is the user_id and the second is a uni_id (university ID).
The User domain model contains the following
class User {
static hasMany = [roles:Role, tests:Test]
Integer userId
...
static mapping = {
table 'user_data'
id generator: 'assigned', name: 'userId', type: 'long'
userId column: 'user_id'
version false
roles joinTable:[name:'user_role', key:'user_id']
tests joinTable:[name:'user_test', key:'user_id'] // Here is where I run into trouble
}
static constraints = {
}
}
The Test domain contains
class Test {
static belongsTo = User
static hasMany = [users:User]
static hasOne = [uni:Uni]
Integer testId // primary key
String testType
static mapping = {
table 'test'
id generator: 'assigned', name: 'testId', type: 'long'
testId column: 'test_id'
users joinTable:[name:'user_test', key:'test_id']
uni joinTable:[name:'user_test', key:'test_id'] // If I leave this out, everything is groovy
version false
}
static constraints = {
}
}
and the Uni domain contains
class Uni {
static belongsTo = Test
static hasMany = [tests:Test]
Integer uniId // primary key
String shortName
String fullName
static mapping = {
table 'uni'
id generator: 'assigned', name: 'uniId', type: 'long'
uniId column: 'uni_id'
version false
tests joinTable:[name:'user_test', key:'uni_id']
}
static constraints = {
}
}
If its not clear, what I'm trying to do is pull in the University ID, Test ID, and User ID to a table user_test to find based on the User ID which tests they have taken. Is there a simple way to do this?
The kinds of errors I'm getting lead me to believe that for some reason it is trying to perform all actions on the table test instead of user_test. For example,
Unsuccessful: alter table test add uni_id int not null
I'd like to be able to access the test and university information corresonding to the specific user via user.tests.testType and user.tests.uni.fullName or something to that extent. What am I doing wrong? More importantly, is there a better way to do this?! Thanks in advance!
Edit 1: something interesting I just thought of.. a user can have multiple tests, but the inverse isn’t true. A given test will never be shared among multiple people. So I think that changes things a bit.. I'll do some reading and post if I come up with anything new.
Edit 2: Here's the Role domain
class Role {
static belongsTo = User
static hasMany = [users:User]
Integer roleId
String shortName
String roleName
Integer roleLevel
static mapping = {
table 'role'
id generator: 'assigned', name: 'roleId', type: 'long'
roleId column: 'role_id'
users joinTable:[name:'user_role', column:'user_id', key:'role_id']
version false
}
static constraints = {
}
}
Edit 3: I am now trying to store all test information in the Test domain model and simply choose the Uni name to store as a field in Test, but am getting weird errors when I try this. My new files look like this
class User {
static hasMany = [roles:Role, tests:Test]
Integer userId
static mapping = {
table 'user_data'
id generator: 'assigned', name: 'userId', type: 'long'
userId column: 'user_id'
version false
roles joinTable:[name:'user_role', key:'user_id']
}
static constraints = {
}
}
and
class Test {
static belongsTo = User
Integer testId // primary key
Integer testTypeId
String testTypeName
String testUni
Date testDate
static mapping = {
table 'test'
id generator: 'assigned', name: 'testId', type: 'long'
testId column: 'test_id'
version false
}
static constraints = {
}
}
but now I'm getting the following error when I try to run it Caused by: org.hibernate.MappingException: Missing type or column for column[tests_test] on domain[User] referencing[Test]
Any idea what that's about?
Ok, one issue you have is that you're trying to share the User-to-Test association join table with the Test-to-Unit association. That's not going to work.
Lets look at it in database terms. I'm not an ASCII art expert, so I hope this diagram doesn't make your eyes bleed.
user_data (userId) |---|< (user_id) user_test (test_id) >|---| (testId) test
The diagram above shows the database implementation of the many-to-many association between the User and Test domain classes. You can see that the user_data.userId links to user_test.user_id and user_test.test_id links to test.testId.
Now here's where it starts to get weird. There are two different associations between Test and Uni: a bidirectional one-to-one and a one-to-many. I just don't understand that. But I want to illustrate an important issue with your join tables, so here it is.
test (testId) |---|< (test_id) user_test (uni_id) >|---| (uniId) uni
Because you're using the same join table (user_test) for two different associations you're asking GORM to create a table like this:
USER_TEST
- USER_ID
- TEST_ID
- UNIT_ID
GORM won't do that because join tables are supposed to have only two fields. Not only that, but also you're defining a many-to-many in database terms, and yet a bidirectional one-to-one and a one-to-many in GORM terms. Ouch!
TODO
The first change I recommend is to use a different join table for the Test-Uni association.
Finally got everything working (after a bit of modification in terms of the domain model)
class User {
static hasMany = [roles:Role, tests:Test]
Integer userId
static mapping = {
table 'user_data'
id generator: 'assigned', name: 'userId', type: 'long'
userId column: 'user_id'
version false
roles joinTable:[name:'user_role', column:'role_id', key:'user_id']
}
static constraints = {
}
}
and
class Test {
User user
Integer testId // primary key
String testType
String testUni
Date testDate
static mapping = {
table 'test'
id generator: 'assigned', name: 'testId', type: 'long'
testId column: 'test_id'
version false
}
static constraints = {
}
}
with
class Uni {
Integer uniId // primary key
String shortName
String fullName
static mapping = {
table 'uni'
id generator: 'assigned', name: 'uniId', type: 'long'
uniId column: 'uni_id'
version false
}
static constraints = {
}
}
So now what I'm doing is selecting the university from a drop down tab in my GSP and just saving it in Test as the string testUni. Then, the big change was removing all joinTables between the three and adding User user to Test. I'm still a little fuzzy on why what I was doing before didn't work, but I won't complain about a working app!

How to avoid ImprovedNamingStrategy in joinTable in Grails

I have a legacy database which I can't change and I have this setup
class Foo {
static hasMany = [bars:Bar]
static mapping = {
version false
columns {
id column: "FooId"
color column: "FooColor"
bars joinTable: [name: "FooBar", key: 'FooId', column: 'BarId']
}
transient
def getBarName(){
((Bar)this.bars.toArray()[0]).name
}
}
class Bar {
static hasMany = [foos:Foo]
static belongsTo = [Foo, Baz]
static mapping = {
version false
columns {
id column: "BarId"
name column: "BarName"
}
}
When i try to access the method getBarName() in a controller Hibernate translates the inverse column name to "bar_id". Is there some way to set up a mapping like the one for the id and property columns?
And on a side note. How do i correctly implement getBarName()? Thacan't possibly be the correct implementation...
*EDIT*
----------------------------------------------------------------------
Apparently I was unclear above. The thing is that i already have a join column which has the form
-------------------
|RowId|FooId|BarId|
-------------------
| 1 | abc | 123 |
-------------------
Benoit's answer isn't really applicable in this situation since I want to avoid having a domain object for the joinTable.
*EDIT 2*
----------------------------------------------------------------------
Solved it. Dont understand it though... But split the join table information between the two domain classes and it works...
class Foo {
static hasMany = [bars:Bar]
static mapping = {
version false
columns {
id column: "FooId"
color column: "FooColor"
bars joinTable: [name: "FooBar", key: 'FooId']
}
transient
def getBarName(){
((Bar)this.bars.toArray()[0]).name
}
}
class Bar {
static hasMany = [foos:Foo]
static belongsTo = [Foo, Baz]
static mapping = {
version false
columns {
id column: "BarId"
name column: "BarName"
bars joinTable: [name: "FooBar", key: 'BarId']
}
}
As stated in the documentation Many-to-One/One-to-One Mappings and One-to-Many Mapping :
With a bidirectional one-to-many you can change the foreign key column
used by changing the column name on the many side of the association
as per the example in the previous section on one-to-one associations.
However, with unidirectional associations the foreign key needs to be
specified on the association itself.
Thye given example is:
class Person {
String firstName
static hasMany = [addresses: Address]
static mapping = {
table 'people'
firstName column: 'First_Name'
addresses column: 'Person_Address_Id'
}
}