Oracle Data Source trent  2011-06-20 22:54
Status: Closed
 
Hi,

First of all - apologies for any Java noobiness.

So I have been having a crack at getting this going. I'm upto a point I am kind of stuck - can't seem to figure out what is causing the error. Have been stepping over the lines, but not getting any indication. But I am no doubt overlooking something very basic.

The stack trace doesn't appear until after the finally block of the waitForResult function in the AysncQueryResult function, when I'm stepping line by line.

So, what i've done thus far:

Created two new classes:

(I was basing these from the MySqlDialect, and adding overridden functions where required - so it may be a case of not having all the required functions? So the reserved words are likely way wrong at the moment, but at the moment im just trying to get it up and running displaying data - which when I debug it does seem to do (adds the data to a list somewhere along the line))

OracleDialect and
OracleDialectFactory (attached)

Modified the BigIron class to register the new dialect

Modified SqlDialectManager - had to modify the function: getFromMetaData, as the getProductVersion from the jdbc oracle driver returns more than a version number i.e. text too. atm, im compiling this from getMajorVersion and getMinorVersion, but i was thinking today it may be better just to use the jdbc driver version as that seems to have the correct version number, e.g.: 11.2.0.1.0

Well, I believe that's the main stuff I've done. Theres also the fact its only returning the SID at the moment, instead of the database name, as the database name isn't in the connection string. But I guess it's somehow possible to fetch the properties from the Resource (i.e. username is schema name is database name) - but I don't think that's a big deal anyway.

OK, so I am able to add an external data source, it fetches all the column names and everything, the issue is when I go to view the data, I am getting an unexpected error. Basically:

java.sql.SQLRecoverableException: Closed Statement
    at oracle.jdbc.driver.OracleStatement.ensureOpen(OracleStatement.java:4007)
    at oracle.jdbc.driver.OracleStatement.getNumberOfColumns(OracleStatement.java:1901)
    at oracle.jdbc.driver.OracleResultSetMetaData.getColumnCount(OracleResultSetMetaData.java:105)
    at org.labkey.api.collections.ResultSetRowMapFactory.<init>(ResultSetRowMapFactory.java:62)
<snipped>

But I have attached the full stack trace in the attachment.

Appreciate anyone that can help or point me in the right direction.

Thx,
Trent
 
 
adam responded:  2011-06-21 07:22
No apologies necessary… writing a new dialect is an ambitious undertaking. (When I suggested earlier that writing one "wouldn't be hard" I meant for someone very familiar with LabKey internals.)

I haven't run your code since I don't have Oracle running locally, but it looks like LabKey is attempting to read result set meta data after the underlying statement is closed; apparently the other four databases we support allow this. This comes up because LabKey caches result sets in many cases to free up JDBC connections. The solution is likely invasive… we probably need to cache the meta data as well in CachedResultSetImpl for Oracle.

As for the other things you mentioned in your message and code:

  • You should probably try to get / parse the actual product version instead of using the driver version. JDBC drivers are typically backward (and often forward) compatible with different server versions; the dialect is most concerned with actual server version, since server version (not driver version) determines SQL syntax, capabilities supported, reserved words, etc.
  • I believe the database name is used only for logging and error reporting… if a LabKey Server is connecting to many databases it's helpful to point admins to the problematic one. But not a critical thing right now.
  • Oracle doesn't support LIMIT or OFFSET? That's terrible...
Adam
 
trent responded:  2011-06-21 15:54
"ambitious undertaking"

haha, yeah. been getting there slowly!

"The solution is likely invasive… we probably need to cache the meta data as well in CachedResultSetImpl for Oracle."

Oh right. Sounds a bit over my head. See if I can figure out what's going on.

"no LIMIT or OFFSET"

Yeah, I didn't even know such clauses existed until I used LabKey.

 
marki responded:  2011-06-21 16:58
This seems to be the accepted way to do LIMIT & OFFSET:

http://stackoverflow.com/questions/241622/paging-with-oracle

It might be possible to use this technique in your dialect limitRows implementation.

 
kevink responded:  2011-06-21 17:16
We do something similar in MicrosoftSqlServer2005Dialect._limitRows() so it should be pretty easy to adapt for Oracle.
 
adam responded:  2011-06-26 13:00
I modified one of our existing ResultSetMetaData implementations to cache a ResultSet's meta data and added a shouldCacheMetaData() method to SqlDialect to cause it to be used. Default setting is false, but your Oracle dialect implementation should set it to true… and let's see what happens.

Adam

 
trent responded:  2011-06-26 23:15
Awesome stuff! Its working. :-) Thank you muchly.

So aside from this needing more testing and addressing the above mentioned issues, what's the best way forward? It's probably impractical to be building this from source (i.e. adding the changes needed), in a prod scenario. I was reading the doco about finding a comitter and submitting a patch to them or something.I suppose other things to think about are version support. i.e. Im only having tested this with 11gR2, so I guess it would be best to limit to that release to start with.... (that is assuming this change would be wanted to be added into labkey)

 
adam responded:  2011-06-27 01:31
This would be a great addition to LabKey, Trent. I can work with you to get it reviewed, added to the code base, and available for release in 11.2 (given the core changes necessary, it's not practical to integrate with previous versions).

I'll email you separately to work through the details.

Adam

 
trent responded:  2011-07-05 23:02
Is there anyway I can enforce the number fields for the defined tables to be Integers?

I'm not certain on other databases implementation, but generally I use a NUMBER field on Oracle which is being generated in labkey as a Float. (which makes sense, as a NUMBER field can be a float in oracle - but I know this particular field will never be a float)..

Im thinking that the answer is in the metadata? But am having trouble getting it to work.

I have seen the xml doco: https://www.labkey.org/download/schema-docs/xml-schemas/schemas/tableInfo_xsd/elements/datatype.html

which says: The name of the SQL datatype of this column as would be specified in a CREATE TABLE statement.

but when I try specifying a different datatype it doesn't seem to make any difference. Possibly my syntax is way off?

<ns:tables xmlns:ns="http://labkey.org/data/xml">  <ns:table tableName="specimen" tableDbType="NOT_IN_DB">    <ns:columns>      <ns:column columnName="SPECIMEN_ID">        <ns:datatype>INT</ns:datatype>        <ns:description>dfgdgfgdfgdfgd</ns:description>      </ns:column>    </ns:columns>  </ns:table> </ns:tables>

 
Matthew Bellew responded:  2011-07-06 07:31
When creating a table info from a physical table in the database, we first create the ColumnInfo using the metadata the database provides. In this case there are certain fields of the ColumnInfo that we will not override using the xml schema. "datatype" is one of them.

see ColumnInfo.loadFromXml() and ColumnInfo.createFromDatabaseMetaData(). Also note the "false" parameter in the call to ColumnInfo.loadFromXml() from SchemaTableInfo.loadFromXml() at line 620.

If the field can never be a float, isn't there a way to set the type in the CREATE TABLE statement to enforce that, so that LabKey will know it is really an INT column?

 
trent responded:  2011-07-07 16:18
"datatype" is one of them.

Oh, right. No worries.

If the field can never be a float, isn't there a way to set the type in the CREATE TABLE statement to enforce that, so that LabKey will know it is really an INT column?

Well, the field can be float, in that it's theoretically possible. I'm saying that it would never be the case. In previous roles, I have always specified number columns as 'number' (data type) - whether they be floats or int's. I imagine its a common practice. I don't think its exactly a possibility for me to go changing the datatypes of tables.

OK. I think I saw somewhere that it loads in tables and views. So im going to try creating a view, casting number fields to int's and see if that solves the problem. (just gotta wait for that priv before i can test)

 
trent responded:  2011-07-07 16:53
Ok. finished doing more tests.

create or replace view
v_specimens
as
select cast(specimen_id as INTEGER) specimen_id, date_Drawn, participant_id, vial_type, quantity, measurement, visit_no, location, total_count, available_count
from specimen

desc v_specimens
Name Null Type

SPECIMEN_ID NUMBER(38)
DATE_DRAWN DATE
PARTICIPANT_ID NUMBER
VIAL_TYPE CHAR(5)
QUANTITY NUMBER
MEASUREMENT CHAR(2)
VISIT_NO NUMBER
LOCATION VARCHAR2(4000)
TOTAL_COUNT NUMBER
AVAILABLE_COUNT NUMBER

Specifying the database as Integer doesn't change anything. As the rdbms still just has the data type as NUMBER, with the precision,scale specified - so more of a convenience then specifying precisions/scale yourself.

SO, still comes through as Number (Double) in LabKey.

(off-topic: would be good to have a no format [like pre or code html tags] formatting option)

 
Matthew Bellew responded:  2011-07-07 17:08
If the Oracle JDBC driver is reporting INTEGER the same as it reports NUMERIC(?, 0), we may have to modify ColumnInfo.createFromDatabaseMetaData().

You can put a breakpoint there to see what the metadata is reporting.

 
trent responded:  2011-07-07 22:45
SO, it just always returns as Decimal, where the type is specified.

In that function, i was attempting something like:

ResultSetMetaData md = rsCols.getMetaData(); md.getPrecision(1); md.getScale(1);

but it just returns 0 all the time =/

hmm.. im just thinking now, this may be because this function - rsCols is the column information and not the actual data. Still you (or I would) think it would work it out.

beyond that, my idea was then do:

if(dbmd.getDatabaseProductName().equalsIgnoreCase("Oracle")) {                //todo: doubles (re assign to INTEGER if no decimals.)            }

 
trent responded:  2011-07-10 21:38
Ok.. i've come up with a somewhat hacky solution.

I figured out how to add function support into the datasource (from the Method class). SO I added in support for some basic string, numeric, and date function that I find most useful/common.

One of which is the to_char function. So, create a new query from the data source, adding a new field:

to_char(participant_id) participant_id_hidden

that returns the value without the '.0' at the end of it. Customize the view of this query to not display the new field and save as the default. In the metadata for the original field (participant_id), specify a format mask of # and in the URL (this also remove the '.0' from the display), and so that I can link to the participant view from this query (since the original format with .0 results in a no data found), have the URL like:

/labkey/study/externalData/test1/participant.view?participantId=${participant_id_hidden}