All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.h2gis.drivers.dbf.DBFDriverFunction Maven / Gradle / Ivy

There is a newer version: 1.2.4
Show newest version
/**
 * H2GIS is a library that brings spatial support to the H2 Database Engine
 * .
 *
 * H2GIS is distributed under GPL 3 license. It is produced by CNRS
 * .
 *
 * H2GIS is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * H2GIS is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * H2GIS. If not, see .
 *
 * For more information, please consult: 
 * or contact directly: info_at_h2gis.org
 */
package org.h2gis.drivers.dbf;

import org.h2.table.Column;
import org.h2gis.drivers.dbf.internal.DBFDriver;
import org.h2gis.drivers.dbf.internal.DbaseFileException;
import org.h2gis.drivers.dbf.internal.DbaseFileHeader;
import org.h2gis.drivers.file_table.FileEngine;
import org.h2gis.drivers.file_table.H2TableIndex;
import org.h2gis.h2spatialapi.DriverFunction;
import org.h2gis.h2spatialapi.EmptyProgressVisitor;
import org.h2gis.h2spatialapi.ProgressVisitor;
import org.h2gis.utilities.JDBCUtilities;
import org.h2gis.utilities.TableLocation;

import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
import org.h2gis.drivers.utility.FileUtil;

/**
 * @author Nicolas Fortin
 */
public class DBFDriverFunction implements DriverFunction {
    public static String DESCRIPTION = "dBase III format";
    private static final int BATCH_MAX_SIZE = 100;
    @Override
    public void exportTable(Connection connection, String tableReference, File fileName, ProgressVisitor progress) throws SQLException, IOException {
        exportTable(connection, tableReference, fileName, progress, null);
    }

    public void exportTable(Connection connection, String tableReference, File fileName, ProgressVisitor progress,String encoding) throws SQLException, IOException {
        if (FileUtil.isExtensionWellFormated(fileName, "dbf")) {
            int recordCount = JDBCUtilities.getRowCount(connection, tableReference);
            final boolean isH2 = JDBCUtilities.isH2DataBase(connection.getMetaData());
            // Read table content
            Statement st = connection.createStatement();
            ProgressVisitor lineProgress = null;
            if (!(progress instanceof EmptyProgressVisitor)) {
                ResultSet rs = st.executeQuery(String.format("select count(*) from %s", TableLocation.parse(tableReference, isH2).toString(isH2)));
                try {
                    if (rs.next()) {
                        lineProgress = progress.subProcess(rs.getInt(1));
                    }
                } finally {
                    rs.close();
                }
            }
            try {
                ResultSet rs = st.executeQuery(String.format("select * from %s", TableLocation.parse(tableReference, isH2).toString(isH2)));
                try {
                    ResultSetMetaData resultSetMetaData = rs.getMetaData();
                    ArrayList columnIndexes = new ArrayList();
                    DbaseFileHeader header = dBaseHeaderFromMetaData(resultSetMetaData, columnIndexes);
                    if (encoding != null) {
                        header.setEncoding(encoding);
                    }
                    header.setNumRecords(recordCount);
                    DBFDriver dbfDriver = new DBFDriver();
                    dbfDriver.initDriver(fileName, header);
                    Object[] row = new Object[header.getNumFields()];
                    while (rs.next()) {
                        int i = 0;
                        for (Integer index : columnIndexes) {
                            row[i++] = rs.getObject(index);
                        }
                        dbfDriver.insertRow(row);
                        if (lineProgress != null) {
                            lineProgress.endStep();
                        }
                    }
                    dbfDriver.close();
                } finally {
                    rs.close();
                }
            } finally {
                st.close();
            }
        } else {
            throw new SQLException("Only .dbf extension is supported");
        }
    }

    @Override
    public String getFormatDescription(String format) {
        if(format.equalsIgnoreCase("dbf")) {
            return DESCRIPTION;
        } else {
            return "";
        }
    }

    @Override
    public IMPORT_DRIVER_TYPE getImportDriverType() {
        return IMPORT_DRIVER_TYPE.COPY;
    }

    @Override
    public String[] getImportFormats() {
        return new String[] {"dbf"};
    }

    @Override
    public String[] getExportFormats() {
        return new String[] {"dbf"};
    }

    @Override
    public void importFile(Connection connection, String tableReference, File fileName, ProgressVisitor progress) throws SQLException, IOException {
        importFile(connection, tableReference, fileName, progress, null);
    }

    /**
     * @param connection Active connection, do not close this connection.
     * @param tableReference [[catalog.]schema.]table reference
     * @param fileName File path to read
     * @param progress monitor
     * @param forceFileEncoding File encoding to use, null will use the provided file encoding in file header.
     * @throws SQLException Table write error
     * @throws IOException File read error
     */
    public void importFile(Connection connection, String tableReference, File fileName, ProgressVisitor progress,String forceFileEncoding) throws SQLException, IOException {
        if (FileUtil.isFileImportable(fileName, "dbf")) {
            DBFDriver dbfDriver = new DBFDriver();
            dbfDriver.initDriverFromFile(fileName, forceFileEncoding);
            final boolean isH2 = JDBCUtilities.isH2DataBase(connection.getMetaData());
            String parsedTable = TableLocation.parse(tableReference, isH2).toString(isH2);
            try {
                DbaseFileHeader dbfHeader = dbfDriver.getDbaseFileHeader();
                // Build CREATE TABLE sql request
                Statement st = connection.createStatement();
                List otherCols = new ArrayList(dbfHeader.getNumFields() + 1);
                for (int idColumn = 0; idColumn < dbfHeader.getNumFields(); idColumn++) {
                    otherCols.add(new Column(dbfHeader.getFieldName(idColumn), 0));
                }
                String pkColName = FileEngine.getUniqueColumnName(H2TableIndex.PK_COLUMN_NAME, otherCols);
                st.execute(String.format("CREATE TABLE %s (" + pkColName + " SERIAL PRIMARY KEY, %s)", parsedTable,
                        getSQLColumnTypes(dbfHeader, isH2)));
                st.close();
                try {
                    PreparedStatement preparedStatement = connection.prepareStatement(
                            String.format("INSERT INTO %s VALUES (null, %s )", parsedTable,
                                    getQuestionMark(dbfHeader.getNumFields())));
                    try {
                        long batchSize = 0;
                        for (int rowId = 0; rowId < dbfDriver.getRowCount(); rowId++) {
                            Object[] values = dbfDriver.getRow(rowId);
                            for (int columnId = 0; columnId < values.length; columnId++) {
                                preparedStatement.setObject(columnId + 1, values[columnId]);
                            }
                            preparedStatement.addBatch();
                            batchSize++;
                            if (batchSize >= BATCH_MAX_SIZE) {
                                preparedStatement.executeBatch();
                                preparedStatement.clearBatch();
                                batchSize = 0;
                            }
                        }
                        if (batchSize > 0) {
                            preparedStatement.executeBatch();
                        }
                    } finally {
                        preparedStatement.close();
                    }
                    //TODO create spatial index on the_geom ?
                } catch (Exception ex) {
                    connection.createStatement().execute("DROP TABLE IF EXISTS " + parsedTable);
                    throw new SQLException(ex.getLocalizedMessage(), ex);
                }
            } finally {
                dbfDriver.close();
            }
        }
    }

    private static class DBFType {

        char type;
        int fieldLength;
        int decimalCount;

        DBFType(char type, int fieldLength, int decimalCount) {
            super();
            this.type = type;
            this.fieldLength = fieldLength;
            this.decimalCount = decimalCount;
        }
    }

    /**
     * Generate the concatenation of ? characters. Used by PreparedStatement.
     * @param count Number of ? character to generation
     * @return Value ex: "?, ?, ?"
     */
    public static String getQuestionMark(int count) {
        StringBuilder qMark = new StringBuilder();
        for (int i = 0; i < count; i++) {
            if(i > 0) {
                qMark.append(", ");
            }
            qMark.append("?");
        }
        return qMark.toString();
    }

    /**
     * Return SQL Columns declaration
     * @param header DBAse file header
     * @param isH2Database true if H2 database
     * @return Array of columns ex: ["id INTEGER", "len DOUBLE"]
     * @throws IOException
     */
    public static String getSQLColumnTypes(DbaseFileHeader header, boolean isH2Database) throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
        for(int idColumn = 0; idColumn < header.getNumFields(); idColumn++) {
            if(idColumn > 0) {
                stringBuilder.append(", ");
            }
            String fieldName = TableLocation.capsIdentifier(header.getFieldName(idColumn), isH2Database);
            stringBuilder.append(TableLocation.quoteIdentifier(fieldName,isH2Database));
            stringBuilder.append(" ");
            switch (header.getFieldType(idColumn)) {
                // (L)logical (T,t,F,f,Y,y,N,n)
                case 'l':
                case 'L':
                    stringBuilder.append("BOOLEAN");
                    break;
                // (C)character (String)
                case 'c':
                case 'C':
                    stringBuilder.append("VARCHAR(");
                    // Append size
                    int length = header.getFieldLength(idColumn);
                    stringBuilder.append(String.valueOf(length));
                    stringBuilder.append(")");
                    break;
                // (D)date (Date)
                case 'd':
                case 'D':
                    stringBuilder.append("DATE");
                    break;
                // (F)floating (Double)
                case 'n':
                case 'N':
                    if ((header.getFieldDecimalCount(idColumn) == 0)) {
                        if ((header.getFieldLength(idColumn) >= 0)
                                && (header.getFieldLength(idColumn) < 10)) {
                            stringBuilder.append("INT4");
                        } else {
                            stringBuilder.append("INT8");
                        }
                    } else {
                        stringBuilder.append("FLOAT8");
                    }
                    break;
                case 'f':
                case 'F': // floating point number
                case 'o':
                case 'O': // floating point number
                    stringBuilder.append("FLOAT8");
                    break;
                default:
                    throw new IOException("Unknown DBF field type " + header.getFieldType(idColumn));
            }
        }
        return stringBuilder.toString();
    }

    /**
     * Create a DBF header from the columns specified in parameter.
     * @param metaData SQL ResultSetMetadata
     * @param retainedColumns list of column indexes
     * @return DbfaseFileHeader instance.
     * @throws SQLException If one or more type are not supported by DBF
     */
    public static DbaseFileHeader dBaseHeaderFromMetaData(ResultSetMetaData metaData, List retainedColumns) throws SQLException {
        DbaseFileHeader dbaseFileHeader = new DbaseFileHeader();
        for(int fieldId= 1; fieldId <= metaData.getColumnCount(); fieldId++) {
            final String fieldTypeName = metaData.getColumnTypeName(fieldId);
            // TODO postgis check field type
            if(!fieldTypeName.equalsIgnoreCase("geometry")) {
                DBFType dbfType = getDBFType(metaData.getColumnType(fieldId), fieldTypeName, metaData.getColumnDisplaySize(fieldId), metaData.getPrecision(fieldId));
                try {
                    dbaseFileHeader.addColumn(metaData.getColumnName(fieldId),dbfType.type, dbfType.fieldLength, dbfType.decimalCount);
                    retainedColumns.add(fieldId);
                } catch (DbaseFileException ex) {
                    throw new SQLException(ex.getLocalizedMessage(), ex);
                }
            }
        }
        return dbaseFileHeader;
    }


    private static DBFType getDBFType(int sqlTypeId, String sqlTypeName,int length, int precision) throws SQLException {
        switch (sqlTypeId) {
            case Types.BOOLEAN:
                return new DBFType('l', 1, 0);
            case Types.BIT:
                return new DBFType('n', Math.min(3, length), 0);
            case Types.DATE:
                return new DBFType('d', 8, 0);
            case Types.DOUBLE:
            case Types.FLOAT:
            case Types.NUMERIC:
            case Types.DECIMAL:
            case Types.REAL:
                // +1 because Field length is including the decimal separator
                return new DBFType('f', Math.min(20, length + 1), Math.min(18,
                        precision));
            case Types.INTEGER:
                return new DBFType('n', Math.min(10, length), 0);
            case Types.BIGINT:
                return new DBFType('n', Math.min(18, length), 0);
            case Types.SMALLINT:
                return new DBFType('n', Math.min(5, length), 0);
            case Types.VARCHAR:
            case Types.NCHAR:
            case Types.CHAR:
                return new DBFType('c', Math.min(254, length), 0);
            default:
                throw new SQLException("Field type not supported by DBF : " + sqlTypeName);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy