view src/main/java/nl/cwi/monetdb/embedded/EmbeddedQueryResult.java @ 31:787a4fdba56e embedded

More cleaning
author Pedro Ferreira <pedro.ferreira@monetdbsolutions.com>
date Thu, 27 Oct 2016 18:33:42 +0200 (2016-10-27)
parents src/main/java/nl/cwi/monetdb/mcl/embedded/result/EmbeddedQueryResult.java@7e0d71a22677
children
line wrap: on
line source
/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0.  If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright 2008-2015 MonetDB B.V.
 */

package nl.cwi.monetdb.embedded;

import java.io.Closeable;
import java.sql.SQLException;
import java.util.Iterator;

import nl.cwi.monetdb.embedded.column.Column;

/**
 * Embedded MonetDB query result.
 * The query result columns are not eagerly copied from the native code to Java.
 * Instead, they are kept around at MonetDB native C-level, materialised in Java 
 * on demand and freed on {@code super.close()}.
 *
 */
public class EmbeddedQueryResult implements Closeable, Iterable<Column<?>> {
	/**
	 * The names of the columns in the query result.
	 */
	protected final String[] columnNames;
	/**
	 * The types of the columns in the query result.
	 */
    protected final String[] columnTypes;
    /**
     * The sizes of the columns in the query result.
     */
    protected final int[] columnSizes;
	/**
	 * The number of columns in the query result.
	 */
    protected final int numberOfColumns;
	/**
	 * The number of rows in the query result.
	 */
    protected final int numberOfRows;
	/**
	 * Pointer to the native result set.
	 * We need to keep it around for getting columns.
	 * The native result set is kept until the {@link super.close()} is called.
	 */
    protected long resultPointer;
	/**
	 * To avoid reconstructing the columns a second time, we will use this cache
	 */
    protected final Column<?>[] columnsCache;

	public EmbeddedQueryResult(String[] columnNames, String[] columnTypes, int[] columnSizes, int numberOfColumns, int numberOfRows, long resultPointer) {
		this.columnNames = columnNames;
		this.columnTypes = columnTypes;
        this.columnSizes = columnSizes;
		this.numberOfColumns = numberOfColumns;
		this.numberOfRows = numberOfRows;
		this.resultPointer = resultPointer;
		this.columnsCache = new Column<?>[numberOfColumns];
	}

	/**
	 * Get the column names as a string array.
	 * 
	 * @return The column names array
	 */
	public String[] getColumnNames() {
		return columnNames;
	}

	/**
	 * Get the column types as a string array.
	 * 
	 * @return The column types array
	 */
	public String[] getColumnTypes() {
		return columnTypes;
	}

    /**
     * Get the column sizes as a int array.
     *
     * @return The column sizes array
     */
    public int[] getColumnSizes() {
        return columnSizes;
    }

	/**
	 * Returns the number of columns in the result set.
	 *
	 * @return Number of columns
	 */
	public int getNumberOfColumns() {
		return numberOfColumns;
	}

	/**
	 * Returns the number of rows in the result set.
	 *
	 * @return Number of rows
	 */
	public int getNumberOfRows() {
		return numberOfRows;
	}

	/**
	 * Get a column from the result set by index.
	 *
	 * @param index Column index (starting from 0)
	 * @return The column, {@code null} if index not in bounds
	 */
	public Column<?> getColumn(int index) throws SQLException {
		if (index < 0) {
			throw new ArrayIndexOutOfBoundsException("The index must be larger than 0!");
		} else if (index >= this.numberOfColumns) {
			throw new ArrayIndexOutOfBoundsException("The index must be smaller than the number of columns");
		}
		if(this.columnsCache[index] != null) {
			return this.columnsCache[index];
		}
		if (this.resultPointer == 0) {
			// The object was closed and result was cleaned-up. Calling the can produce a native Segfault (and crash the JVM)
			throw new NullPointerException("The result set has been already cleaned!");
		}
		Column<?> result = this.getColumnWrapper(index, this.resultPointer);
		this.columnsCache[index] = result;
		return result;
	}

	/**
	 * Get a column from the result set by name.
	 *
	 * @param name Column name
	 * @return The column, {@code null} if not found
	 */
	public Column<?> getColumn(String name) throws SQLException {
		int index = 0;
		for (String columnName : this.columnNames) {
			if (name.equals(columnName)) {
				return this.getColumn(index);
			}
			index++;
		}
		throw new ArrayIndexOutOfBoundsException("The column is not present in the result set!");
	}

	/**
	 * A native C function that returns a {@code Column} object.
	 *
	 * @param index Column index (starting from 0) 
	 * @return
	 */
	private native Column<?> getColumnWrapper(int index, long resultPointer) throws SQLException;

	@Override
	public Iterator<Column<?>> iterator() {
		return new Iterator<Column<?>>() {
			private int currentIndex = 0;

			@Override
			public boolean hasNext() {
				return (currentIndex < getNumberOfColumns());
			}

			@Override
			public Column<?> next() {
				try {
					return getColumn(currentIndex++);
				} catch (SQLException ex) {
					return null;
				}
			}
		};
	}

	/**
	 * Get a matrix of objects representing the rows and columns of the query
	 *
	 * @return The rows as {@code  Object[][]}
	 */
	public Object[][] getRows() throws SQLException {
		Object[][] result = new Object[this.numberOfRows][this.numberOfColumns];
		Column<?> column;
		for (int i = 0 ; i < this.numberOfColumns; i++) {
			column = this.getColumn(i);
			for (int j = 0 ; j < this.numberOfRows; j++) {
				result[j][i] = column.getValue(i);
			}
		}
		return result;
	}

	@Override
	public void close() {
		if(this.resultPointer > 0) {
			this.cleanupResult(this.resultPointer);
			this.resultPointer = 0;
		}
	}

	/** 
	 * Free the C-level result structure.
	 */
	private native void cleanupResult(long resultPointer);
}