Sunday, July 7, 2013

Duplicate columns while using DatabaseMetaData.getColumns()

Want to post a potential pit fall in using JDBC API DatabaseMetaData.getColumns() where most of developers tend to do.

In some of the projects, there might be requirement to fetch the details of a column like its name etc during the run time. And the API in the subject of discussion perfectly does the job.

Below is the code snippet typically used:

String TABLE_NAME = "EMPLOYEE";
DatabaseMetaData meta = connection.getMetaData();
ResultSet columns = meta.getColumns(null, null, TABLE_NAME, null);


With above code, we faced issue of duplicate columns being returned. For example: Multiple times EMP_NAME is being returned by this API. 

When I further debugged the code to the level of DB, found that Connection.getMetaData() returns meta-data for the entire database, not just the schema you happen to be connected to. So when you supply null as the first two arguments to meta.getColumns(), the results are not filtered for your schema. You need to supply the name of the Oracle schema as second parameter of meta.getColumns() e.g., meta.getColumns(null, "DBSCHEMA_01", TABLE_NAME, null). At least this is the case when used with Oracle database (not sure of other providers).

If one carefully looks at the above code snippet, it is clearly visible that the DatabaseMetaData is fetched from the Connection. In short API returns all the columns (if no filtering is specified as above) of the specified table in the database which are visible to this connection.

Also the above issue is due to visibility of the tables provided to other schemas of the database. This may be a mistake or purposely configured by DBAs and unfortunately it was the later in our case. 

On a high level, the SQL used by ORACLE for fetching the results of this API is as below:

SELECT   *
    FROM all_tab_columns t
   WHERE t.owner LIKE '%' ESCAPE '/'
     AND t.table_name LIKE 'EMPLOYEE' ESCAPE '/'
     AND t.column_name LIKE '%' ESCAPE '/'
ORDER BY table_schem, table_name, ordinal_position


To avoid duplicates in the result returned by this API, below are two possible solutions that I could think of:
  1. Provide the schema name while calling this API.
  2. Avoid this API and use a SELECT SQL like select * from EMPLOYEE where rownum < 1. The metadata of the result set contains the details of the column. And the code will be something like: resultSet.getMetaData().getColumnCount() and resultSet.getMetaData().getColumnName(int columnIndex)
       I hope the above was useful. Please add your comments if any

Tuesday, July 2, 2013

Parsing text to create Date in Java

Recently came across a bug where the incorrect date text is getting parsed even though a valid format was specified. Java.text.SimpleDateFormat.java is used for parsing the date text.  

For example: the specified format is yyyyMMdd and the text given as input to parse is of 10252012. In such cases we expect java to through an error saying the specified text is not in expected/specified format.

After going through the java doc, found the mistake that I was doing.  The parser used in SimpleDateFormat.java may use heuristics to interpret inputs that do not precisely match specified format and if the given text fits into one of those formats then the respective Date object is created. To avoid parser to use heuristics and strictly use the format specified by user one need to call setLenient(false) method available in the SimpleDateFormat.java. By default parser is lenient (meaning set to true).


Sample Code:
------------------------------------------------------------------------------------------------------------
            String dateFormat = "yyyyMMdd";
            String correctDateText = "20121025";
            String inCorrectDateText = "10252012";
            SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
           
            Date correctdateObj = sdf.parse(correctDateText);
            System.out.println(" Date :" +correctdateObj);
           
            Date inCorrectDateObj = sdf.parse(inCorrectDateText);
            System.out.println(" Date :" +inCorrectDateObj);
           
            sdf.setLenient(false);
           
            correctdateObj = sdf.parse(correctDateText);
            System.out.println(" Date :" +correctdateObj);
           
            inCorrectDateObj = sdf.parse(inCorrectDateText);
            System.out.println(" Date :" +inCorrectDateObj);
------------------------------------------------------------------------------------------------------------
Output
------------------------------------------------------------------------------------------------------------
Date :Thu Oct 25 00:00:00 IST 2012
Date :Fri Aug 12 00:00:00 IST 1026
Date :Thu Oct 25 00:00:00 IST 2012
Exception in thread "main" java.text.ParseException: Unparseable date: "10252012"
       at java.text.DateFormat.parse(Unknown Source)
       at amdocs.ar.test.DummyTests.main(DummyTests.java:27)
------------------------------------------------------------------------------------------------------------

To continue further this is true for other cases as well.  Consider a leap year example, year 2011 is not a leap year and a date of 29th Feb 2011 should result in error if passed as input to SimpleDateFormat parser but that is not the case. It consider 29th Feb 2011 as 1st Mar 2011 and this may not be the expected business case in all scenarios. Setting the lenient to false stops this behavior.


Sample Code:
------------------------------------------------------------------------------------------------------------
            String dateFormat = "yyyyMMdd";
           
            String inCorrectDateText = "20110229";
            SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
           
            
            Date inCorrectDateObj = sdf.parse(inCorrectDateText);
            System.out.println(" Date :" +inCorrectDateObj);           
            sdf.setLenient(false);
                               
            inCorrectDateObj = sdf.parse(inCorrectDateText);
            System.out.println(" Date :" +inCorrectDateObj);
------------------------------------------------------------------------------------------------------------
Output
------------------------------------------------------------------------------------------------------------
Date :Tue Mar 01 00:00:00 IST 2011
Exception in thread "main" java.text.ParseException: Unparseable date: "20110229"
at java.text.DateFormat.parse(DateFormat.java:357)
at amdocs.test.Test.main(Test.java:19)
------------------------------------------------------------------------------------------------------------