Skip to content

Commit

Permalink
manifold-sql change.
Browse files Browse the repository at this point in the history
- added ValueAccessor SPI and default implementations, replaces TypeMap as a more effective means of resolving Java types corresponding with JDBC types, getting query result values, and setting parameter values
- added schema information for database product name and version to special case sqlite's type-safety issues
xerial/sqlite-jdbc#928
xerial/sqlite-jdbc#933
  • Loading branch information
rsmckinney committed Jul 6, 2023
1 parent 426fdcf commit 84e5553
Show file tree
Hide file tree
Showing 51 changed files with 2,196 additions and 259 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@

package manifold.sql.rt.api;

public interface RawElement
public interface BaseElement
{
String getName();
int getPosition();
int getSize();
boolean isNullable();
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,16 @@
public class Runner<T extends ResultRow>
{
private final Class<T> _queryClass;
private final int[] _jdbcParamTypes;
private final Bindings _params;
private final String _querySource;
private final String _configName;
private final Function<Bindings, T> _makeResult;

public Runner( Class<T> queryClass, Bindings params, String querySource, String configName, Function<Bindings, T> makeResult )
public Runner( Class<T> queryClass, int[] jdbcParamTypes, Bindings params, String querySource, String configName, Function<Bindings, T> makeResult )
{
_queryClass = queryClass;
_jdbcParamTypes = jdbcParamTypes;
_params = params;
_querySource = querySource;
_configName = configName;
Expand All @@ -59,7 +61,8 @@ public Iterable<T> run()
int i = 0;
for( Object param : _params.values() )
{
ps.setObject( ++i, param );
ValueAccessor accessor = ValueAccessor.get( _jdbcParamTypes[i] );
accessor.setParameter( ps, ++i, param );
}
try( ResultSet resultSet = ps.executeQuery() )
{
Expand Down Expand Up @@ -87,25 +90,16 @@ private void rip( ResultSet resultSet )
{
try
{
TypeMap typeMap = TypeMap.findFirst();

ResultSetMetaData metaData = resultSet.getMetaData();
for( boolean isOnRow = resultSet.next(); isOnRow; isOnRow = resultSet.next() )
{
DataBindings row = new DataBindings();
for( int i = 1; i <= metaData.getColumnCount(); i++ )
{
String column = metaData.getColumnLabel( i );
int datatype = metaData.getColumnType( i );
Class<?> type = typeMap.getType( new ResultColumn( metaData, i ), datatype );
if( type.isPrimitive() )
{
row.put( column, resultSet.getObject( i ) );
}
else
{
row.put( column, resultSet.getObject( i, type ) );
}
ValueAccessor accessor = ValueAccessor.get( metaData.getColumnType( i ) );
Object value = accessor.getRowValue( resultSet, new ResultColumn( metaData, i ) );
row.put( column, value );
}
_results.add( _makeResult.apply( row ) );
}
Expand All @@ -123,7 +117,7 @@ public Iterator<T> iterator()
return _results.iterator();
}

private class ResultColumn implements RawElement
private class ResultColumn implements BaseElement
{
private final ResultSetMetaData _metaData;
private final int _i;
Expand All @@ -147,12 +141,31 @@ public String getName()
}
}

@Override
public int getPosition()
{
return _i;
}

@Override
public boolean isNullable()
{
try
{
return _metaData.isNullable( _i ) != ResultSetMetaData.columnNoNulls;
return _metaData.isNullable( _i ) == ResultSetMetaData.columnNullable;
}
catch( SQLException e )
{
throw ManExceptionUtil.unchecked( e );
}
}

@Override
public int getSize()
{
try
{
return _metaData.getPrecision( _i );
}
catch( SQLException e )
{
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Copyright (c) 2023 - Manifold Systems LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package manifold.sql.rt.api;

import manifold.rt.api.util.ServiceUtil;
import manifold.util.concurrent.LocklessLazyVar;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
* Each implementation of this interface handles a JDBC type from {@link java.sql.Types} and must be registered as a Java
* Service Provider. Manifold provides default implementations that are suitable for most use-cases, however these implementations
* can be overridden by returning a higher priority from {@link #getPriority()}.
* <p/>
* This interface performs the following:<br>
* - resolves the Java type corresponding with the JDBC type from {@link java.sql.Types}<br>
* - sets query parameter values<br>
* - gets query result values<br>
* <br>
*/
public interface ValueAccessor
{
LocklessLazyVar<Set<ValueAccessor>> ACCESSORS =
LocklessLazyVar.make( () -> {
Set<ValueAccessor> registered = new HashSet<>();
ServiceUtil.loadRegisteredServices( registered, ValueAccessor.class, ValueAccessor.class.getClassLoader() );
return registered;
} );

LocklessLazyVar<Map<Integer, ValueAccessor>> BY_JDBC_TYPE =
LocklessLazyVar.make( () -> {
Map<Integer, ValueAccessor> map = new HashMap<>();
for( ValueAccessor acc : ACCESSORS.get() )
{
int jdbcType = acc.getJdbcType();
ValueAccessor existing = map.get( jdbcType );
if( existing == null || existing.getPriority() < acc.getPriority() )
{
map.put( jdbcType, acc );
}
}
return map;
} );

static ValueAccessor get( int jdbcType )
{
return BY_JDBC_TYPE.get().get( jdbcType );
}

/**
* @return The {@link java.sql.Types} id this accessor handles.
*/
int getJdbcType();

/**
* Greater = higher priority. Higher priority overrides lower. Default implementations are lowest priority. They can be
* overridden.
*/
default int getPriority()
{
return Integer.MIN_VALUE;
}

/**
* @return The resulting type of the value in Java code. Note this type may not correspond with SQL-to-Java type mappings
* from the JDBC specification. For instance, although {@code java.sql.Types#CLOB} maps to {@code java.sql.CLOB} (appendix
* table B.3 from the JDBC 4.2 specification) the actual type generated for {@code CLOB} is {@code String}.
*/
Class<?> getJavaType( BaseElement elem );

/**
* Returns a query result value corresponding with a {@code elem} from {@code rs}.
* @param rs The result set containing rows of column values
* @param elem The query column from which to find a value
* @return The value corresponding with {@code elem}. Note, the type of the value must match the Java type returned from
* {@code elem.getType()}.
* @throws SQLException
*/
Object getRowValue( ResultSet rs, BaseElement elem ) throws SQLException;

/**
* Sets the query parameter value corresponding with {@code pos}.
* @param ps The prepared statement containing the parameterized query.
* @param pos The index of the parameter, beginning with 1.
* @param value The value of the parameter
* @throws SQLException
*/
void setParameter( PreparedStatement ps, int pos, Object value ) throws SQLException;
}
Loading

0 comments on commit 84e5553

Please sign in to comment.