Feature #1911
Feature #1582: add conversion and runtime support for sequences
add runtime support for sequences
0%
Subtasks
Related issues
History
#1 Updated by Eric Faulhaber over 11 years ago
- Target version set to Milestone 7
#2 Updated by Eric Faulhaber over 11 years ago
- Start date deleted (
11/05/2012)
#3 Updated by Eric Faulhaber over 11 years ago
- Due date deleted (
11/13/2012) - Assignee set to Ovidiu Maxiniuc
- Start date deleted (
11/13/2012)
See parent issue for requirements.
Runtime implementations of the statement/functions should leverage dialect infrastructure in Hibernate if possible (if we find it is feasible to use native sequences).
#4 Updated by Ovidiu Maxiniuc over 11 years ago
I have done some investigations and here is what I found out:
The Hibernate Dialect
base class defines the following sequence-related methods:
1. boolean supportsSequences() 2. boolean supportsPooledSequences() 3. String getSequenceNextValString(String sequenceName) 4. String getSelectSequenceNextValString(String sequenceName) 5. String getQuerySequencesString() 6. String getDropSequenceString(String sequenceName) 7. String getCreateSequenceString(String sequenceName, int initialValue, int incrementSize)
Of those, only 1st, 3rd, 5th, 6th and 7th are of interest to us.
The derived classes
H2Dialect
, PostgreSQLDialect
and ProgressDialect
override these methods as needed.As you can see, there is only basic support for sequences, no direct support for creating cycling sequences, nor (re-)settings their value. A first approach would be to write SQL specific queries.
PostgreSQL
- offers full support for sequences (min and max values, variable increment and cycling property).
- errors in query the sequence's value should be interpreted as unknown values.
- there are statements to create/drop the sequences.
- there are 3 functions that handle them:
currvalue(seq)
,nextvalue(seq)
,setvalue(seq, val)
. There is a catch, however. Thecurrvalue
is relative to current session and will fail if called before a call tonextvalue
orsetvalue
.
- only offers basic support, sequences only features a current value and an increment.
currvalue(seq)
andnextvalue(seq)
are available but to reset the sequence anALTER SEQUENCE seq RESTART WITH n
statement is to be used.- I don't know how can we emulate cycling sequences or min/max boundries.
The last approach would be to manage sequences ourselves using a dedicated table and implement the entire sequence functionality using SQL statements / triggers.
#5 Updated by Eric Faulhaber over 11 years ago
Ovidiu Maxiniuc wrote:
[...]
PostgreSQL
- offers full support for sequences (min and max values, variable increment and cycling property).
- errors in query the sequence's value should be interpreted as unknown values.
- there are statements to create/drop the sequences.
- there are 3 functions that handle them:
currvalue(seq)
,nextvalue(seq)
,setvalue(seq, val)
. There is a catch, however. Thecurrvalue
is relative to current session and will fail if called before a call tonextvalue
orsetvalue
.
Does this session-specific behavior differ from that of current-value
in Progress?
H2
- only offers basic support, sequences only features a current value and an increment.
currvalue(seq)
andnextvalue(seq)
are available but to reset the sequence anALTER SEQUENCE seq RESTART WITH n
statement is to be used.- I don't know how can we emulate cycling sequences or min/max boundries.
The last approach would be to manage sequences ourselves using a dedicated table and implement the entire sequence functionality using SQL statements / triggers.
Can we go with a hybrid approach, where we use the native sequences to the degree possible, but blend this with the Progress-like features in our P2JXXXDialect classes? For example, enforce the boundaries in the dialect, but use the backing sequence to get the sequence values within those boundaries. And use H2's alter sequence
to simulate cycling when we hit the cycle boundary.
I think there is value in having the sequences there in the database, but we have to weigh this against the value of having a single implementation that works for every database (but only through the P2J APIs). Thoughts?
#6 Updated by Eric Faulhaber over 11 years ago
Please also research sequences in MS SQL Server, which we have to support for this project.
#7 Updated by Ovidiu Maxiniuc over 11 years ago
1. There is no problem for Progress/OE having a simple code like:
display current-value(my-seq-1).
2. This hybrid approach implies storing additional information about sequence's state into a dedicate table (cycling, min / max, if the sequence has passed the limit and should return ?). Maybe using triggers or stored procedures ? I don't know the effort needed nor if H2 supports this type of server-side functionality.
#8 Updated by Ovidiu Maxiniuc over 11 years ago
- Creation of sequences. Native, for example:
CREATE SEQUENCE "p2j_test.my-seq-1" AS INT START WITH 10 INCREMENT BY 3 MINVALUE 1 MAXVALUE 20 CYCLE
- Current value. There is no direct support to query the current value, instead a small hack can be used:
SELECT current_value FROM "sys.Sequences" WHERE name='p2j_test.my-seq-1'
The good part is that all sequence properties can be read from this system table, but write access only by SQL statements. - Next value. Native support:
SELECT NEXT VALUE FOR "p2j_test.my-seq-1"
- Set value. Native, using a small workaround:
ALTER SEQUENCE "p2j_test.my-seq-1" RESTART WITH 5 SELECT NEXT VALUE FOR "p2j_test.my-seq-1"
Note the extraNEXT VALUE
called here. It is needed to emulate the 4GL behavior. As in MSSQL, next value returns 5, but Progress will return 6. The same would be needed in PostgreSQL but there is an extra form ofSETVAL
which accept a 3rd boolean parameter that fits the Progress way of handling Sequences. - Removing. Using native sql statements:
DROP SEQUENCE "p2j_test.my-seq-1"
#9 Updated by Ovidiu Maxiniuc over 11 years ago
I discovered a way to access the current value in PostgreSQL without calling CURRVAL
which fails if it's called before NEXTVAL
.
The solution is similar with MSSQL, ie. access SQL internal structures directly:
SELECT last_value FROM "my-seq-1";
#10 Updated by Eric Faulhaber over 11 years ago
- Start date set to 11/20/2012
#11 Updated by Eric Faulhaber over 11 years ago
- Status changed from New to WIP
#12 Updated by Ovidiu Maxiniuc over 11 years ago
An issue related to PSQL, but not only:
On a finite, non cycling sequences, calling nextVal
will return an error if passing over the upper/lower bound which will be interpreted as unknown (?
). This is fine. However, there is no native way extract this value (?
) afterward using currentValue
as this will always return the last VALID value.
I would go for an implementation where I use a P2J private table, to memorize if the last nextVal
passed the bounds and the actual value is ?
. It would be reseted when setValue
is invoked. However, this also involves transactions / locking and I don't know if it is justified as performance, as for each currentVal
this table is first queried and updated on nextVal
and setVal
.
What do you say about this ?
#13 Updated by Eric Faulhaber over 11 years ago
If we need transaction processing separate from the current (application-defined) database transaction, it sounds like this would have to happen on a dedicated service thread which would manage its own transactions. I don't know what the performance impact would be, but we have to put correctness (i.e., Progress compatibility) above performance.
Let's try this approach. If the performance is simply awful, we will need to figure out another way. Please document this thoroughly in the code, since it is not intuitive.
#14 Updated by Ovidiu Maxiniuc over 11 years ago
The implementation a service thread for handling sequence primitives (current/next/set) like this:
- the current value is directly returned as no modifications are done into database
- the thread is lazy created & started, only when it is truly needed (this posed a small problem because staring a new thread may take a while and the calling thread had to be blocked during this)
- normally the thread sleeps. When a next/set request arrives (code is passed as a Runnable
) it stored into a thread field, then it is notified/waked-up and it will run the code then notify back the caller thread and enter wait()
again.
- the calling thread (the one that handles "client" code) will be blocked while the procedure executes.
I have the following strange problem. I believe I'm doing something wrong. When the Runnable.run()
gets to execute it will take up 100% CPU, blocking the JVM. What I do:
- I obtain a thread-local Persistence
object by calling ConnectionManager.getPersistence(ldbName)
. This should be a 'clean' Persistence
object associated with the new thread/context
- then I start a sequence-specific transaction: persistence.beginTransaction()
I investigated the javadoc of Persistence
and found no obvious issues, except the following:
"Only one transaction per client context may be active at a time."
Can this be the cause of my problem ?
However, does this means that if some P2J code is already in a transaction, I cannot use another transaction to manage the sequences defined into a user-table in database ?
#15 Updated by Eric Faulhaber over 11 years ago
I spoke with Greg about this and he suggested a much more efficient (and simpler) solution:
Add flags to the directory for each database/sequence combination, which indicate whether or not we have crossed the upper/lower boundary of each sequence and we currently are in the "out-of-bounds" state. At server startup, these would be read by a service object in the persist package and stored in memory. Provide an API through this service object that allows these in-memory flags to be checked at each nextVal call. When we change state (i.e., exceed an upper/lower boundary OR set the current value back within the allowed range), the in-memory flags would be changed AND the new value of the appropriate flag would be written to the directory (for persistence across server restarts).
You'll need to think about the best way to design this API to work with the use of sequences. The above is just meant to convey the general idea. This approach avoids the overhead of database access and the complexity of a separate thread.
See com.goldencode.p2j.directory.RemapTestDriver1
for examples of how to make updates to the directory. Basically, the cycle is:
- bind
- update specific values
- unbind
In terms of where in the directory we would store this new information, I would suggest the path server/{site}/database/{database name}/p2j/sequence/{sequence name}/out_of_bounds
.
#16 Updated by Eric Faulhaber over 11 years ago
- % Done changed from 0 to 30
#17 Updated by Ovidiu Maxiniuc over 11 years ago
I have discovered something at least interesting today. The Progress/4GL 9 and OpenEdge/ABL 11 Reference books, both say (pages 223, respectively 294/557) that (DYNAMIC-)CURRENT-VALUE
will return "The Unknown value (?
) if the sequence has exceeded its minimum or maximum and is not cycling".
As both documents uphold this I took it for granted. While I reworked my testcases for sequences I discovered that this is not true. After reaching the upper/lower limit, the current value (both dynamic and static) will never be returned as Unknown value (?
), but as the last valid before invoking the NEXT-VALUE
. The fact is, this is the exact way PostgreSQL handles sequences and this should be a good news as we will need to write special code for sequences only for H2 dialect.
On the other hand, as both reference books affirms the opposite, I wonder if there isn't a bug in the OE version from windev01.
#18 Updated by Eric Faulhaber over 11 years ago
Ovidiu Maxiniuc wrote:
...The fact is, this is the exact way PostgreSQL handles sequences and this should be a good news as we will need to write special code for sequences only for H2 dialect.
Indeed good news! How do you see the H2 implementation working?
On the other hand, as both reference books affirms the opposite, I wonder if there isn't a bug in the OE version from windev01.
More likely a bug in the documentation; we have found many occasions where the documentation does not match the behavior.
#19 Updated by Eric Faulhaber about 11 years ago
- Estimated time changed from 24.00 to 120.00
- % Done changed from 30 to 50
#20 Updated by Eric Faulhaber about 11 years ago
- Due date set to 05/29/2013
#21 Updated by Eric Faulhaber about 11 years ago
- Due date changed from 05/29/2013 to 07/01/2013
#22 Updated by Eric Faulhaber about 11 years ago
- Due date changed from 07/01/2013 to 07/15/2013
#23 Updated by Eric Faulhaber about 11 years ago
- File ecf_upd20130514a.zip added
I've changed the H2 and PostgreSQL dialects to not use quoted sequence names, so that we are not forcing case-sensitive naming. I've also changed the sequence DDL generation rules to honor the end of statement delimiter, rather than hard-coding it in the DDL statements themselves. The attached update has passed regression testing and is committed to bzr rev. 10351. The update also contains a fix for primary key generation using sequences, which is more related to the upgrade to Hibernate 4 (#1580).
#24 Updated by Ovidiu Maxiniuc almost 11 years ago
The actual implementation of sequences does change their name to a SQL-legal name and the old "historical" name is temporarily saved as annotation. When using static sequence calls this is OK, the original name can be swapped with sql-valid at conversion time. The problem is when using dynamic forms of the functions/statements, the sql name is not know at conversion time so some kind of mapping should be maintained.
Example:
For the 4GL sequence name: "my-seq-1" the name will be morphed to "my_seq_1" to be sql-compliant.
When using static calls like
CURRENT-VALUE(my-seq-1)
at the conversion time we can replace the new sequence name directly:
SequenceManager.currentValue("my_seq_1")
However, when using dynamic calls, things change as the name of the sequence may not be known at compile time:
DYNAMIC-CURRENT-VALUE("my-" + "seq-1", "p2j_test")
In this case, in the
SequenceManager.dynamicCurrentValue()
need the mapping from the legacy-name to new sql-compliant name to access the sequence in DB.#25 Updated by Ovidiu Maxiniuc almost 11 years ago
Taking into consideration that some other data are also managed in P2J using the legacy-name (for example the second (optional) parameter - the database name), I let all method (static & dynamic) to be called the same consistent way. And the binding to actual sql-legal name is done at runtime.
Generally, they work now, edge-cases are in tests now.
#26 Updated by Ovidiu Maxiniuc almost 11 years ago
I am having the following issue.
message dynamic-current-VALUE("my-seq-9", "p2j_test1"). display dynamic-current-VALUE("my-seq-9", "p2j_test2"). display "3!".
The first 2 line are essentially the same, as the only open database is "p2j_test2". When I run this on 4GL, I get:
Database p2j_test1 does not exist or is not open. (3141)
After pressing the space bar it prints in status bar:
Database p2j_test2 does not exist or is not open. (3141)
An then it prints
Procedure complete. Press space bar to continue.
and the 3 line is never executed.
Is there a way to detect the message
and use ErrorManeger.recordOrShowError
and use the ErrorManeger.recordOrThrowError
in all other cases ?
#27 Updated by Ovidiu Maxiniuc almost 11 years ago
- File om_upd20130712a.zip added
This update package contains a first stage of runtime implementation. Main points:
- fixed name issues (4GL-legacy vs sql-compatible sequence names)
- added error messages for invalid operations
- enforce H2 bounds and cycling behavior of 4GL sequences.
#28 Updated by Ovidiu Maxiniuc almost 11 years ago
I discovered something strange with sequences.
If you define a sequence with negative increment Progress will automatically create a MAX-VALUE
equals to the initial value. This is the export of a sequence that starts at 0 and goes down (it does not matter if it has a limit or whether it is cycling):
ADD SEQUENCE "seq-desc" INITIAL 0 INCREMENT -10 CYCLE-ON-LIMIT yes MIN-VAL -1000 MAX-VAL 0
This means that you will not be able to set the value to
10
so current-value(seq-desc) = 10
will end with an error.
On the other hand, the 'reversed' sequence is exported like this:
ADD SEQUENCE "seq-asc" INITIAL 0 INCREMENT 10 CYCLE-ON-LIMIT yes MAX-VAL 1000
So,
MIN-VALUE = -9223372036854775808
, writing current-value(seq-asc) = -1000000
is perfectly legal.
Because PSQL is symmetric from this point of view, at this moment, I cannot rely on PSQL to fully handle sequence operations.
When a sequence is cycling PSQL will warp from MAX to MIN values (or the reverse) it does not keep the "start with" value.
Progress, instead will always cycle back to initial value.
#29 Updated by Ovidiu Maxiniuc almost 11 years ago
Note 26 continued.
From my quick tests, it seems that message
is more permissive about errors occurred while evaluating its parameters. It will display the error but will not stop the procedure from running.
The very same will happen with
MESSAGE INT("a"). DISPLAY INT("b") /*no-error*/. MESSAGE "3!".
Two error messages about a and b will be issued, but
3!
will not be printed as the procedure stops with display
statement.If you uncomment the no-error clause, only the error message about a will be issued,
?
will be printed and then 3!
will be finally shown.So probably the message statement should be bracketed somehow and allow error messages to be displayed when evaluating its arguments but not treat them as fatal errors. Note 28
This is important because it proves that neither PSQL is fully compatible with 4GL and some additional code is needed to exactly emulate Progress'
next-value
statement. The true problem is that these calls are no longer atomic and two or more SQL statements are needed for evaluation this 4GL function and the solution from note 13 must cover the PSQL sequences, too. Here are some thoughts:
- I am in doubt that a new service thread is the best approach for atomizing/synchronizing accesses to sequences.
- I don't know if Hibernate allows parallel transactions from a dedicated/separate connection - but I believe this solution is rather complicated.
- If I have a clear understanding of the architecture, there can be multiple clients connected to the server application but only one server application connected to SQL server. This means that we can use java locking mechanism (on server application) to synchronize access to each sequences of the database.
- Any solution we adopt, It will be hard enough to effectively prove it work without dead-locking or any other surprises.
#30 Updated by Eric Faulhaber almost 11 years ago
Ovidiu Maxiniuc wrote:
Note 26 continued.
...
Greg: I need your input here.
Ovidiu: since this is an error/exceptional condition, please continue with all other aspects of the implementation in the meantime.
Note 28
...
Of the 50+ sequences defined for the current project, none have negative increments. Please document (in the code) this behavioral difference as a limitation of the current implementation, which may need to be addressed at a future time, then move on.
#31 Updated by Eric Faulhaber almost 11 years ago
Code review 20130712a:
Generally looks good; nice work. Some comments below.- Please add a WARNING to schema conversion if we encounter a sequence definition with a negative increment, given your findings. This will remind us to deal with the limitation discussed above if we encounter it in a future project.
- In the dialect-specific sequence handlers, please replace the
System.{err/out}.println
calls with runtime logging. Search forLogHelper.getLogger
across the project for examples of use. If some of these printlns are just develop-time debugging, please remove them when you finalize this code. - A minor thing, but in some of the inline comments regarding sequence cycling, you wrote "warps/warping/warped" where I think you meant "wraps/wrapping/wrapped"?
- In the
SequenceManager.getSequenceHandler
factory method, rather than testing for every dialect type usinginstanceof
, let's leverage polymorphism and add agetSequenceHandler
method to theP2JDialect
interface. Each dialect must implement this method to return a handler instance or null. If null, theSequenceManager.getSequenceHandler
method uses its default. We will be adding dialects over time, and I think this approach is cleaner. Also, replace theSystem.err.println
call here with logging.
#32 Updated by Ovidiu Maxiniuc almost 11 years ago
- File test-root.p.cache added
- File om_upd20130801a.zip added
Near final update. Issues from Eric's review were fixed. The PSQL issues were eliminated.
I am putting this update to conversion/runtime regression tests soon.
The second file contains the cached file with more than 100 sequence tests.
#33 Updated by Ovidiu Maxiniuc almost 11 years ago
On Friday, the runtime test failed with an abend in gso_ctrlc_3way, failure in step 11, and about 10 other issues in gso_tests
and tc_tests
:
I re-runned the test with same update and I got no abends, only 3 issues in main part. Constantin had a quick look at the harness result and he said it looks ok.
I am waiting for your review when you have the time.
#34 Updated by Eric Faulhaber almost 11 years ago
Code review 20130801a:
Looks good. There is a TODO comment in the PostgreSQLSequenceHandler c'tor. Is there something left to do, or can it be removed?
#35 Updated by Greg Shah almost 11 years ago
In regard to note 26 and note 30, this is a known flaw in our processing for the MESSAGE statement. We don't need to fix that in this task.
#36 Updated by Ovidiu Maxiniuc almost 11 years ago
I removed the TODO comment that eclipse inserted automatically.
Regarding the desc sequences, I believe they are not an issue for PSQL dialect any more since I changed the implementation in note 32. The second file attached there contains a set of tests dedicated to this PSQL issue.
However, since the latest revisions from bzr repository overlapped some files I need to redo the regression tests this weekend.
#37 Updated by Ovidiu Maxiniuc almost 11 years ago
The regression test went flawlessly for the main part (ignoring the usual 'Unexpected EOF in actual at page # 1.' from tc_job_002).
However, I got 4 fails in gso_ctrlc_3way_tests, of which one abend in step 9 of test_5_ctrlc_11_session3. As the last time, the client ended abruptly with:
java.lang.IllegalStateException: Focused widget does not belong to container at com.goldencode.p2j.ui.client.widget.AbstractContainer.focusWorker(AbstractContainer.java:821) at com.goldencode.p2j.ui.client.widget.AbstractContainer.nextFocus(AbstractContainer.java:463) at com.goldencode.p2j.ui.client.FocusManager.focusChange(FocusManager.java:1168) at com.goldencode.p2j.ui.chui.ThinClient.nextFocus(ThinClient.java:14802) at com.goldencode.p2j.ui.client.Browse.onKeyPressed(Browse.java:1890) at com.goldencode.p2j.ui.client.Browse.processEvent(Browse.java:2379) at com.goldencode.p2j.ui.client.widget.TitledWindow.processEvent(TitledWindow.java:144) at com.goldencode.p2j.ui.chui.ThinClient.processEventsWorker(ThinClient.java:12600) at com.goldencode.p2j.ui.chui.ThinClient.pop(ThinClient.java:11268) at com.goldencode.p2j.ui.chui.ThinClient.eventBracket(ThinClient.java:11251) at com.goldencode.p2j.ui.chui.ThinClient.eventDrawingBracket(ThinClient.java:11177) at com.goldencode.p2j.ui.chui.ThinClient.applyWorker(ThinClient.java:10948) at com.goldencode.p2j.ui.chui.ThinClient.apply(ThinClient.java:3104) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at com.goldencode.p2j.util.MethodInvoker.invoke(MethodInvoker.java:76) at com.goldencode.p2j.net.Dispatcher.processInbound(Dispatcher.java:675) at com.goldencode.p2j.net.Conversation.block(Conversation.java:316) at com.goldencode.p2j.net.Conversation.waitMessage(Conversation.java:254) at com.goldencode.p2j.net.Queue.transactImpl(Queue.java:1107) at com.goldencode.p2j.net.Queue.transact(Queue.java:575) at com.goldencode.p2j.net.BaseSession.transact(BaseSession.java:178) at com.goldencode.p2j.net.HighLevelObject.transact(HighLevelObject.java:163) at com.goldencode.p2j.net.RemoteObject$RemoteAccess.invokeCore(RemoteObject.java:1406) at com.goldencode.p2j.net.InvocationStub.invoke(InvocationStub.java:97) at com.sun.proxy.$Proxy3.standardEntry(Unknown Source) at com.goldencode.p2j.main.ClientCore.start(ClientCore.java:216) at com.goldencode.p2j.main.ClientCore.start(ClientCore.java:76) at com.goldencode.p2j.main.ClientDriver.start(ClientDriver.java:223) at com.goldencode.p2j.main.CommonDriver.process(CommonDriver.java:423) at com.goldencode.p2j.main.ClientDriver.process(ClientDriver.java:115) at com.goldencode.p2j.main.ClientDriver.main(ClientDriver.java:241)
Should I redo the test before committing to bzr? The last time the at the second run, the above issue did not occurred.
#38 Updated by Eric Faulhaber almost 11 years ago
Since this is a merged code base which is different from your last run, please run regression testing one more time (run_ctrlc.sh only). If the same tests do not fail, check it in.
#39 Updated by Eric Faulhaber almost 11 years ago
Correction: I went back through my email and found that these tests are intermittently failing due to a non-P2J problem. Go ahead and check in the update.
#40 Updated by Ovidiu Maxiniuc almost 11 years ago
- File om_upd20130809a.zip added
Final update, that passed the runtime test (notes 37-39), was committed in bzr as rev 10374 and distributed by mail.
#41 Updated by Eric Faulhaber almost 11 years ago
- Status changed from WIP to Closed
- % Done changed from 50 to 100
I am closing this issue, but please open a separate issue (bug) for the remaining, negative increment problem (note 28). We will not need to address it for the current project, but I don't want to lose track of it.
#42 Updated by Greg Shah over 7 years ago
- Target version changed from Milestone 7 to Runtime Support for Server Features