Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate ID on Entity.save() #8

Closed
stapel opened this issue Jan 14, 2019 · 15 comments
Closed

Generate ID on Entity.save() #8

stapel opened this issue Jan 14, 2019 · 15 comments
Assignees
Labels
bug Something isn't working

Comments

@stapel
Copy link
Contributor

stapel commented Jan 14, 2019

Currently Entity depends on the database the create a value for id. This is nice for Longs or Ints as most databases provide autoincrement or something like that.
Now I'd "want" to use UUIDs as id, this is hard to make work with e.g. MySQL in a convenient or simple way.
I'd really like to see the functionality to provide a generator-method or something like this to make it into Entity.
Any ideas or comments appreciated.

@mvysny mvysny self-assigned this Jan 14, 2019
@mvysny mvysny added the enhancement New feature or request label Jan 14, 2019
@mvysny
Copy link
Owner

mvysny commented Jan 14, 2019

Currently the Entity.save() does SQL INSERT if the ID is null, or SQL UPDATE if the ID is not null. Which is kinda incompatible with app-generated as you found out :(

The generator method sounds great. I'm thinking of having a Entity.generateID() function which would return null by default but you could override it to return UUID.create(); the save() method could then consult the generateID() function when the ID is null, and do an INSERT with ID pre-provided.

However, there is the problem of pre-provided IDs (e.g. if the ID is pre-known, say, a state person identifier). The pre-provided ID kinda rules out the current save() algorithm, since it would always do insert. Yet this could be solved by UPSERT maybe...

@mvysny
Copy link
Owner

mvysny commented Jan 14, 2019

I wonder if it's simply easier to replace save() with two other methods, create() and update(), then let the vok-orm user to figure out the correct operation to call. Or, we keep save() and pile up UPSERT and generateID() on top of that. I can't kinda decide.

In my simple apps I am always able to tell whether I'm creating or updating, and the first option sounds a lot simpler, both to explain in the API and to implement (no need to figure out how to do UPSERT on multiple databases), so I'm a bit biased that way. However, I'd love to draw from your experience @stapel - what do you think? Which way would work best for your app?

@mvysny mvysny added the question Further information is requested label Jan 14, 2019
@mvysny
Copy link
Owner

mvysny commented Jan 14, 2019

Alternatively, we can perhaps keep all three methods. That way, you can use save() with IDs generated by the database (the common case), and create()/update() with both app-generated and pre-provided IDs... However that would require a proper explanation both in kdoc and in the documentation.

@stapel
Copy link
Contributor Author

stapel commented Jan 17, 2019

Personally I'd like the generateId()-method (I fiddled with it locally, I still need to find a way to add a custom UUID-Converter for sql2o).

If going the create/update-way, I'd keep all three, because, as you said, save() will probably be perfect for most use cases.

I wouldn't go in the UPSERT-direction, it sounds cumbersome to check for different databases with different versions.. and IIRC there is very little database-dependency (maybe a little pgsql-preference) in the vok-subprojects.

What are your thoughs on always generating UUIDs on any Entity<UUID> with id = null?

@mvysny
Copy link
Owner

mvysny commented Jan 18, 2019

What are your thoughs on always generating UUIDs on any Entity with id = null?

Hmm, it could only work if the type of the ID column was UUID. I feel it's too magicky, in a sense that the behavior suddenly changes for UUID-typed column. Therefore I'd advise against it, I'd rather make it explicit.

Since generateId() doesn't work with pre-provided IDs, I'd rather go with create() which works for both cases. Let me prototype this a bit :)

I wouldn't go in the UPSERT-direction, it sounds cumbersome to check for different databases with different versions..

I agree, I'll avoid UPSERT.

I still need to find a way to add a custom UUID-Converter for sql2o).

I thought it was natively supported... Let me test that as well.

@stapel
Copy link
Contributor Author

stapel commented Jan 18, 2019

Great!

Regarding UUIDs, in mysql you'd have to use binary(16) to properly save a UUID, this will be mapped to ByteArray in sql2o. And the default UUIDConverter does not check for a bytearray. So, writing works, reading doesn't.

@mvysny
Copy link
Owner

mvysny commented Jan 18, 2019

Yup, binary(16) is the way to go. However, sql2o doesn't map UUID to ByteArray for me automatically, since mysql fails with Caused by: com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'id' at row 1. I'm guessing it's trying to use toString and insert the UUID as a String, which takes 36 characters. So writing doesn't work for me. I'll investigate a bit more.

mvysny added a commit that referenced this issue Jan 18, 2019
@mvysny
Copy link
Owner

mvysny commented Jan 18, 2019

Fixed in vok-orm 0.17

@mvysny mvysny closed this as completed Jan 18, 2019
@mvysny
Copy link
Owner

mvysny commented Jan 18, 2019

Hmm, not fixed yet. Sql2o sometimes picks up the ID type as Object instead of UUID, doesn't activate the Converter and then fails that it can't set ByteArray value to a UUID field.

@mvysny mvysny reopened this Jan 18, 2019
@mvysny mvysny closed this as completed in fb9fb71 Jan 18, 2019
@stapel
Copy link
Contributor Author

stapel commented Jan 18, 2019

This is a really nice solution, having only save() and create(). A generating wrapper-class is easily created and more expressive then the generator-variant!

@mvysny
Copy link
Owner

mvysny commented Jan 20, 2019

@stapel you don't even need to create the wrapper class perhaps; you can simply override the create() function as follows:

override fun create(validate: Boolean) {
    id = UUID.randomUUID()
    super.create(validate)
}

then you can either call create() directly, or you can perhaps simply continue using save() as before.

Yet I'd love to hear about your wrapper-class solution - could you paste a bit of info please?

@mvysny
Copy link
Owner

mvysny commented Jan 21, 2019

Reopened: rather than a nasty workaround in vok-orm it should be fixed in Sql2o: aaberg/sql2o#314

@mvysny mvysny added bug Something isn't working and removed enhancement New feature or request question Further information is requested labels Jan 21, 2019
@mvysny
Copy link
Owner

mvysny commented Jan 21, 2019

This bug report is about generating IDs; i've created a separate bug ticket regarding the failing converters: #10

@mvysny mvysny closed this as completed Jan 21, 2019
@stapel
Copy link
Contributor Author

stapel commented Jan 21, 2019

@mvysny: It's nothing really, and I am not sure that this is the final form, but it keeps me from too much refactoring. ;) (And we probably want to use UUIDs for all Entities anyhow) (Edit: I probably used the wrong name with wrapper-class)

interface UUIDEntity : Entity<UUID> {
    override fun save(validate: Boolean) {
        if (id == null) {
            id = UUID.randomUUID()
            super.create(validate)
        } else {
            super.save(validate)
        }
    }
}

(Good catch with sql2o!)

@mvysny
Copy link
Owner

mvysny commented Jan 21, 2019

@stapel Hey, that's a really awesome idea, thanks! I've updated the docs to mention this possibility; we can probably simplify it to:

interface UuidEntity : Entity<UUID> {
    override fun create(validate: Boolean) {
        id = UUID.randomUUID()
        super.create(validate)
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants