a PersistenceContext and give it out to all of your repositories and delay
activating the context until you are ready for everyone to start talking to the
database. In this way you can enforce that no one does any database fooling
around before it's OK.
I also introduced DepotRepository.init() where database fooling around is OK.
The general idea is that you register schema migrations in your constructor and
then if you have data migrations to be done (or initialization that requires
reading things from the database), you do that in init().
initialValue=N) for non-zero values of N. This is complicated. Depot used to
"initialize" value generators pretty much every time a table schema was
migrated. For TableValueGenerator it could just NOOP if the proper row was in
the sequence table. IdentityValueGenerator was just broken and ignored any
request to configure an initialValue, so it never did anything.
First I looked into preserving this frequent reinitialization behavior, but it
turns out that it's not possible to read the current value of a sequence in
Postgres without modifying it. So trying to do something like:
SELECT setval('"FooRecord_fooId_seq"', max(currval('"FooRecord_fooId_seq"'),
cast(initialValue as bigint)));
doesn't work because you can't call currval() without first having called
nextval() (and furthermore when you do that, the Postgres client grabs a block
of sequence values for this client so that multiple clients can add rows to a
table without having to coordinate with the server and one another to assign a
new value every time a row is inserted).
So I changed Depot around to not reinitialize value generators unless it really
believes that the column in question is newly added. We can't rely on our own
auto-column creation because it's possible for someone to create a column
through a custom entity migration, so instead we just keep track of what
columns we had before we started all of our migrations (automatic and custom)
and check to see if there are any new columns after all is said and done and if
any of those have value generators, we "create" them.
This allows us to just setval() the sequence to the desired initial value
without first reading it.
This all fails if someone renames a column with a value generator. The column
will appear to Depot to be new and it will try to recreate the value generator
on that column. For TableValueGenerators that will basically NOOP and
everything will be just like it was before. For IdentityValueGenerators,
because of the way that Postgres magically creates a tablename_columnname_seq
sequence for identity value generation, when the column is renamed, the
sequence will retain the old column name and when Depot goes to try to setval()
the sequence based on the new column name, it won't exist and the migration
will fail. So at least we're not silently reinitializing the value generator to
its initial value when a column (with an identity value generator) is renamed.
We also don't try to do anything if the initialValue is not 1 (the default). So
we avoid failing or doing anything wrong if those kinds of columns are renamed.
So we only fail if someone renames a column with an identity value generator
with a non-default initial value. Since one can only put a value generator on a
primary key field and an identity value generator generally means you have
something simple like FooRecord with fooId. This means someone is renaming
their simple auto incrementing primary key field which seems extremely unlikely
because those columns usually have an obvious name that you're never going to
change. No problem!
I also haven't fixed this problem for MySQL yet. I can only handle so much
database befuckaroundery in one go.
table that had field generators on its primary key. The code just blindly
overwrite the supplied primary key and ran the generators anyway.
We now only run the generators if the record has no primary key. This means we
can't use field generators on non-primary-key fields, but I'm not sure we ever
meant to support that in the first place.
when we're doing a query with primary keys provided by a random caller. We have
know idea where they got those keys or where they have been before we got our
filthy mitts on them.
in the cache as well as records loaded from earlier passes in cases where we
split our query up to avoid database breaking in() clause sizes. Now we track
exactly what we got this time around and report that.
server or if it's OK to get them from a (potentially slightly out of date)
replica. If you're going to modify rows based on the results of findAllKeys(),
you should probably get them from the master server, if you're just reading
data, you can talk to a replica.