Skip to content
Permalink
Browse files
fix: improve DatabaseMetaData.getSQLKeywords() (#940)
Fetch keywords from pg_catalog.pg_get_keywords()
  • Loading branch information
jorsol authored and vlsi committed Mar 11, 2018
1 parent 4204f09 commit 7a586b6e492e8911a928d50113a68569981fa731
Showing with 166 additions and 15 deletions.
  1. +1 −0 CHANGELOG.md
  2. +92 −15 pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java
  3. +73 −0 pgjdbc/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataTest.java
@@ -26,6 +26,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
- Package scram:client classes, so SCRAM works when using a shaded jar [PR 1091](https://github.com/pgjdbc/pgjdbc/pull/1091) [1a89290e](https://github.com/pgjdbc/pgjdbc/commit/1a89290e110d5863b35e0a2ccf79e4292c1056f8)
- reWriteBatchedInserts=true causes syntax error with ON CONFLICT [Issue 1045](https://github.com/pgjdbc/pgjdbc/issues/1045) [PR 1082](https://github.com/pgjdbc/pgjdbc/pull/1082)
- Avoid failure in getPGArrayType when stringType=unspecified [PR 1036](https://github.com/pgjdbc/pgjdbc/pull/1036)
- For PostgreSQL 9.0+ return a complete list of keywords in DatabaseMetadata.getSQLKeywords() from pg_catalog.pg_get_keywords(). [PR 940](https://github.com/pgjdbc/pgjdbc/pull/940)

## [42.2.0] (2018-01-17)
### Known issues
@@ -36,14 +36,7 @@ public PgDatabaseMetaData(PgConnection conn) {
this.connection = conn;
}

private static final String keywords = "abort,acl,add,aggregate,append,archive,"
+ "arch_store,backward,binary,boolean,change,cluster,"
+ "copy,database,delimiter,delimiters,do,extend,"
+ "explain,forward,heavy,index,inherits,isnull,"
+ "light,listen,load,merge,nothing,notify,"
+ "notnull,oids,purge,rename,replace,retrieve,"
+ "returns,rule,recipe,setof,stdin,stdout,store,"
+ "vacuum,verbose,version";
private String keywords;

protected final PgConnection connection; // The connection association

@@ -252,18 +245,102 @@ public String getIdentifierQuoteString() throws SQLException {
* {@inheritDoc}
*
* <p>
* Within PostgreSQL, the keywords are found in src/backend/parser/keywords.c
*
* <p>
* For SQL Keywords, I took the list provided at
* <a href="http://web.dementia.org/~shadow/sql/sql3bnf.sep93.txt"> http://web.dementia.org/~
* shadow/sql/sql3bnf.sep93.txt</a> which is for SQL3, not SQL-92, but it is close enough for this
* purpose.
* From PostgreSQL 9.0+ return the keywords from pg_catalog.pg_get_keywords()
*
* @return a comma separated list of keywords we use
* @throws SQLException if a database access error occurs
*/
@Override
public String getSQLKeywords() throws SQLException {
connection.checkClosed();
if (keywords == null) {
if (connection.haveMinimumServerVersion(ServerVersion.v9_0)) {
// Exclude SQL:2003 keywords (https://github.com/ronsavage/SQL/blob/master/sql-2003-2.bnf)
// from the returned list, ugly but required by jdbc spec.
String sql = "select string_agg(word, ',') from pg_catalog.pg_get_keywords() "
+ "where word <> ALL ('{a,abs,absolute,action,ada,add,admin,after,all,allocate,alter,"
+ "always,and,any,are,array,as,asc,asensitive,assertion,assignment,asymmetric,at,atomic,"
+ "attribute,attributes,authorization,avg,before,begin,bernoulli,between,bigint,binary,"
+ "blob,boolean,both,breadth,by,c,call,called,cardinality,cascade,cascaded,case,cast,"
+ "catalog,catalog_name,ceil,ceiling,chain,char,char_length,character,character_length,"
+ "character_set_catalog,character_set_name,character_set_schema,characteristics,"
+ "characters,check,checked,class_origin,clob,close,coalesce,cobol,code_units,collate,"
+ "collation,collation_catalog,collation_name,collation_schema,collect,column,"
+ "column_name,command_function,command_function_code,commit,committed,condition,"
+ "condition_number,connect,connection_name,constraint,constraint_catalog,constraint_name,"
+ "constraint_schema,constraints,constructors,contains,continue,convert,corr,"
+ "corresponding,count,covar_pop,covar_samp,create,cross,cube,cume_dist,current,"
+ "current_collation,current_date,current_default_transform_group,current_path,"
+ "current_role,current_time,current_timestamp,current_transform_group_for_type,current_user,"
+ "cursor,cursor_name,cycle,data,date,datetime_interval_code,datetime_interval_precision,"
+ "day,deallocate,dec,decimal,declare,default,defaults,deferrable,deferred,defined,definer,"
+ "degree,delete,dense_rank,depth,deref,derived,desc,describe,descriptor,deterministic,"
+ "diagnostics,disconnect,dispatch,distinct,domain,double,drop,dynamic,dynamic_function,"
+ "dynamic_function_code,each,element,else,end,end-exec,equals,escape,every,except,"
+ "exception,exclude,excluding,exec,execute,exists,exp,external,extract,false,fetch,filter,"
+ "final,first,float,floor,following,for,foreign,fortran,found,free,from,full,function,"
+ "fusion,g,general,get,global,go,goto,grant,granted,group,grouping,having,hierarchy,hold,"
+ "hour,identity,immediate,implementation,in,including,increment,indicator,initially,"
+ "inner,inout,input,insensitive,insert,instance,instantiable,int,integer,intersect,"
+ "intersection,interval,into,invoker,is,isolation,join,k,key,key_member,key_type,language,"
+ "large,last,lateral,leading,left,length,level,like,ln,local,localtime,localtimestamp,"
+ "locator,lower,m,map,match,matched,max,maxvalue,member,merge,message_length,"
+ "message_octet_length,message_text,method,min,minute,minvalue,mod,modifies,module,month,"
+ "more,multiset,mumps,name,names,national,natural,nchar,nclob,nesting,new,next,no,none,"
+ "normalize,normalized,not,\"null\",nullable,nullif,nulls,number,numeric,object,"
+ "octet_length,octets,of,old,on,only,open,option,options,or,order,ordering,ordinality,"
+ "others,out,outer,output,over,overlaps,overlay,overriding,pad,parameter,parameter_mode,"
+ "parameter_name,parameter_ordinal_position,parameter_specific_catalog,"
+ "parameter_specific_name,parameter_specific_schema,partial,partition,pascal,path,"
+ "percent_rank,percentile_cont,percentile_disc,placing,pli,position,power,preceding,"
+ "precision,prepare,preserve,primary,prior,privileges,procedure,public,range,rank,read,"
+ "reads,real,recursive,ref,references,referencing,regr_avgx,regr_avgy,regr_count,"
+ "regr_intercept,regr_r2,regr_slope,regr_sxx,regr_sxy,regr_syy,relative,release,"
+ "repeatable,restart,result,return,returned_cardinality,returned_length,"
+ "returned_octet_length,returned_sqlstate,returns,revoke,right,role,rollback,rollup,"
+ "routine,routine_catalog,routine_name,routine_schema,row,row_count,row_number,rows,"
+ "savepoint,scale,schema,schema_name,scope_catalog,scope_name,scope_schema,scroll,"
+ "search,second,section,security,select,self,sensitive,sequence,serializable,server_name,"
+ "session,session_user,set,sets,similar,simple,size,smallint,some,source,space,specific,"
+ "specific_name,specifictype,sql,sqlexception,sqlstate,sqlwarning,sqrt,start,state,"
+ "statement,static,stddev_pop,stddev_samp,structure,style,subclass_origin,submultiset,"
+ "substring,sum,symmetric,system,system_user,table,table_name,tablesample,temporary,then,"
+ "ties,time,timestamp,timezone_hour,timezone_minute,to,top_level_count,trailing,"
+ "transaction,transaction_active,transactions_committed,transactions_rolled_back,"
+ "transform,transforms,translate,translation,treat,trigger,trigger_catalog,trigger_name,"
+ "trigger_schema,trim,true,type,uescape,unbounded,uncommitted,under,union,unique,unknown,"
+ "unnamed,unnest,update,upper,usage,user,user_defined_type_catalog,user_defined_type_code,"
+ "user_defined_type_name,user_defined_type_schema,using,value,values,var_pop,var_samp,"
+ "varchar,varying,view,when,whenever,where,width_bucket,window,with,within,without,work,"
+ "write,year,zone}'::text[])";

Statement stmt = null;
ResultSet rs = null;
try {
stmt = connection.createStatement();
rs = stmt.executeQuery(sql);
if (!rs.next()) {
throw new PSQLException(GT.tr("Unable to find keywords in the system catalogs."),
PSQLState.UNEXPECTED_ERROR);
}
keywords = rs.getString(1);
} finally {
JdbcBlackHole.close(rs);
JdbcBlackHole.close(stmt);
}
} else {
// Static list from PG8.2 src/backend/parser/keywords.c with SQL:2003 excluded.
keywords = "abort,access,aggregate,also,analyse,analyze,backward,bit,cache,checkpoint,class,"
+ "cluster,comment,concurrently,connection,conversion,copy,csv,database,delimiter,"
+ "delimiters,disable,do,enable,encoding,encrypted,exclusive,explain,force,forward,freeze,"
+ "greatest,handler,header,if,ilike,immutable,implicit,index,indexes,inherit,inherits,"
+ "instead,isnull,least,limit,listen,load,location,lock,mode,move,nothing,notify,notnull,"
+ "nowait,off,offset,oids,operator,owned,owner,password,prepared,procedural,quote,reassign,"
+ "recheck,reindex,rename,replace,reset,restrict,returning,rule,setof,share,show,stable,"
+ "statistics,stdin,stdout,storage,strict,sysid,tablespace,temp,template,truncate,trusted,"
+ "unencrypted,unlisten,until,vacuum,valid,validator,verbose,volatile";
}
}
return keywords;
}

@@ -15,6 +15,7 @@
import org.postgresql.test.TestUtil;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

@@ -27,7 +28,9 @@
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/*
* TestCase to test the internal functionality of org.postgresql.jdbc2.DatabaseMetaData
@@ -1244,4 +1247,74 @@ public void testIdentityColumns() throws SQLException {

}

@Test
public void testGetSQLKeywords() throws SQLException {
DatabaseMetaData dbmd = con.getMetaData();
String keywords = dbmd.getSQLKeywords();

// We don't want SQL:2003 keywords returned, so check for that.
String sql2003 = "a,abs,absolute,action,ada,add,admin,after,all,allocate,alter,always,and,any,are,"
+ "array,as,asc,asensitive,assertion,assignment,asymmetric,at,atomic,attribute,attributes,"
+ "authorization,avg,before,begin,bernoulli,between,bigint,binary,blob,boolean,both,breadth,by,"
+ "c,call,called,cardinality,cascade,cascaded,case,cast,catalog,catalog_name,ceil,ceiling,chain,"
+ "char,char_length,character,character_length,character_set_catalog,character_set_name,"
+ "character_set_schema,characteristics,characters,check,checked,class_origin,clob,close,"
+ "coalesce,cobol,code_units,collate,collation,collation_catalog,collation_name,collation_schema,"
+ "collect,column,column_name,command_function,command_function_code,commit,committed,condition,"
+ "condition_number,connect,connection_name,constraint,constraint_catalog,constraint_name,"
+ "constraint_schema,constraints,constructors,contains,continue,convert,corr,corresponding,count,"
+ "covar_pop,covar_samp,create,cross,cube,cume_dist,current,current_collation,current_date,"
+ "current_default_transform_group,current_path,current_role,current_time,current_timestamp,"
+ "current_transform_group_for_type,current_user,cursor,cursor_name,cycle,data,date,datetime_interval_code,"
+ "datetime_interval_precision,day,deallocate,dec,decimal,declare,default,defaults,deferrable,"
+ "deferred,defined,definer,degree,delete,dense_rank,depth,deref,derived,desc,describe,"
+ "descriptor,deterministic,diagnostics,disconnect,dispatch,distinct,domain,double,drop,dynamic,"
+ "dynamic_function,dynamic_function_code,each,element,else,end,end-exec,equals,escape,every,"
+ "except,exception,exclude,excluding,exec,execute,exists,exp,external,extract,false,fetch,filter,"
+ "final,first,float,floor,following,for,foreign,fortran,found,free,from,full,function,fusion,"
+ "g,general,get,global,go,goto,grant,granted,group,grouping,having,hierarchy,hold,hour,identity,"
+ "immediate,implementation,in,including,increment,indicator,initially,inner,inout,input,"
+ "insensitive,insert,instance,instantiable,int,integer,intersect,intersection,interval,into,"
+ "invoker,is,isolation,join,k,key,key_member,key_type,language,large,last,lateral,leading,left,"
+ "length,level,like,ln,local,localtime,localtimestamp,locator,lower,m,map,match,matched,max,"
+ "maxvalue,member,merge,message_length,message_octet_length,message_text,method,min,minute,"
+ "minvalue,mod,modifies,module,month,more,multiset,mumps,name,names,national,natural,nchar,"
+ "nclob,nesting,new,next,no,none,normalize,normalized,not,null,nullable,nullif,nulls,number,"
+ "numeric,object,octet_length,octets,of,old,on,only,open,option,options,or,order,ordering,"
+ "ordinality,others,out,outer,output,over,overlaps,overlay,overriding,pad,parameter,parameter_mode,"
+ "parameter_name,parameter_ordinal_position,parameter_specific_catalog,parameter_specific_name,"
+ "parameter_specific_schema,partial,partition,pascal,path,percent_rank,percentile_cont,"
+ "percentile_disc,placing,pli,position,power,preceding,precision,prepare,preserve,primary,"
+ "prior,privileges,procedure,public,range,rank,read,reads,real,recursive,ref,references,"
+ "referencing,regr_avgx,regr_avgy,regr_count,regr_intercept,regr_r2,regr_slope,regr_sxx,"
+ "regr_sxy,regr_syy,relative,release,repeatable,restart,result,return,returned_cardinality,"
+ "returned_length,returned_octet_length,returned_sqlstate,returns,revoke,right,role,rollback,"
+ "rollup,routine,routine_catalog,routine_name,routine_schema,row,row_count,row_number,rows,"
+ "savepoint,scale,schema,schema_name,scope_catalog,scope_name,scope_schema,scroll,search,second,"
+ "section,security,select,self,sensitive,sequence,serializable,server_name,session,session_user,"
+ "set,sets,similar,simple,size,smallint,some,source,space,specific,specific_name,specifictype,sql,"
+ "sqlexception,sqlstate,sqlwarning,sqrt,start,state,statement,static,stddev_pop,stddev_samp,"
+ "structure,style,subclass_origin,submultiset,substring,sum,symmetric,system,system_user,table,"
+ "table_name,tablesample,temporary,then,ties,time,timestamp,timezone_hour,timezone_minute,to,"
+ "top_level_count,trailing,transaction,transaction_active,transactions_committed,"
+ "transactions_rolled_back,transform,transforms,translate,translation,treat,trigger,trigger_catalog,"
+ "trigger_name,trigger_schema,trim,true,type,uescape,unbounded,uncommitted,under,union,unique,"
+ "unknown,unnamed,unnest,update,upper,usage,user,user_defined_type_catalog,user_defined_type_code,"
+ "user_defined_type_name,user_defined_type_schema,using,value,values,var_pop,var_samp,varchar,"
+ "varying,view,when,whenever,where,width_bucket,window,with,within,without,work,write,year,zone";

String[] excludeSQL2003 = sql2003.split(",");
String[] returned = keywords.split(",");
Set<String> returnedSet = new HashSet<String>(Arrays.asList(returned));
Assert.assertEquals("Returned keywords should be unique", returnedSet.size(), returned.length);

for (String s : excludeSQL2003) {
assertFalse("Keyword from SQL:2003 \"" + s + "\" found", returnedSet.contains(s));
}

if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v9_0)) {
Assert.assertTrue("reindex should be in keywords", returnedSet.contains("reindex"));
}
}

}

0 comments on commit 7a586b6

Please sign in to comment.