summaryrefslogtreecommitdiff
path: root/src/connectors/src/data/io/sql/SQLRelDataReader.java
blob: b6355e9ad506a86df8778b48f51d8bd28679ae39 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/*
 * SSSync, a Simple and Stupid Synchronizer for data with multi-valued attributes
 * Copyright (C) 2014  Ludovic Pouzenc <ludovic@pouzenc.fr>
 *  
 * This file is part of SSSync.
 *
 *  SSSync 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.
 *
 *  SSSync 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 SSSync.  If not, see <http://www.gnu.org/licenses/>
 */

package data.io.sql;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Iterator;

import data.MVDataEntry;
import data.io.AbstractMVDataReader;

/**
 * Stream-oriented reader from a particular RDBMS source.
 * 
 * @author lpouzenc
 */
public class SQLRelDataReader extends AbstractMVDataReader {

	private final Connection conn;
	private final String request;
	
	private transient String columnNames[];
	private transient ResultSet rs;
	private transient boolean didNext;
	private transient boolean hasNext;

	/**
	 * Build a new reader from an existing connection and a File containing a SELECT statement.
	 * @param dataSourceName A short string representing this reader (for logging) 
	 * @param conn A pre-established SQL data connection
	 * @param queryFile An SQL file containing an SQL SELECT statement
	 * @throws IOException 
	 */
	public SQLRelDataReader(String dataSourceName, Connection conn, File queryFile) throws IOException  {
		this.dataSourceName = dataSourceName;
		this.conn = conn;
		this.request = readEntireFile(queryFile);
	}
	
	/**
	 * Build a new reader from an existing connection and a String containing a SELECT statement.
	 * @param dataSourceName A short string representing this reader (for logging)
	 * @param conn A pre-established SQL data connection
	 * @param query A String containing an SQL SELECT statement
	 * @throws IOException 
	 */
	public SQLRelDataReader(String dataSourceName, Connection conn, String query) {
		this.dataSourceName = dataSourceName;
		this.conn = conn;
		this.request = query;
	}

	/**
	 * {@inheritDoc}
	 * Note : multiple iterators on the same instance are not supported
	 */
	@Override
	public Iterator<MVDataEntry> iterator() {
		try {
			// Reset iterator-related attributes
			hasNext = false;
			didNext = false;
			
			// Close and free any previous request result
			if ( rs != null ) {
				rs.close();
			}
			// (Re-)Execute the SQL request
			Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
			rs = stmt.executeQuery(request);
			
			// Get the column names
			ResultSetMetaData rsmd = rs.getMetaData();
			columnNames = new String[rsmd.getColumnCount()];
			for (int i = 0; i < columnNames.length ; i++) {
				// Java SQL : all indices starts at 1 (it sucks !)
				columnNames[i] = rsmd.getColumnName(i+1); 
			}
		} catch (SQLException e) {
			throw new RuntimeException("Could not execute query : " + e.getMessage() + "\n" + request );
		}
		
		return this;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean hasNext() {
		// java.sql.ResultSet don't implement Iterable interface at all
		// It's next() don't return anything except hasNext() result but it moves the cursor !  
        if (!didNext) {
            try {
				hasNext = rs.next();
			} catch (SQLException e) {
				throw new RuntimeException(e);
			}
            didNext = true;
        }
        return hasNext;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public MVDataEntry next() {
		MVDataEntry result = null;
		try {
	        if (!didNext) {
	            rs.next();
	        }
	        didNext = false;
	        //TODO Instead of always use the first col, user could choose a specific columnName like in LDAP
			String key = rs.getString(1); 
			result = new MVDataEntry(key);
			for (int i = 0; i < columnNames.length ; i++) {
				// Java SQL : all indices starts at 1 (it sucks !)
				result.splitAndPut(columnNames[i], rs.getString(i+1), ";"); // TODO regex should be an option
			}
			
		} catch (SQLException e) {
			throw new RuntimeException("Exception while reading next line in SQL resultset", e);
		}		
		
		return result;
	}
	
	/**
	 * Helper function to load and entire file as a String.
	 * @param file
	 * @return
	 * @throws IOException
	 */
    private static String readEntireFile(File file) throws IOException {
        FileReader input = new FileReader(file);
        StringBuilder contents = new StringBuilder();
        char[] buffer = new char[4096];
        int read = 0;
        do {
            contents.append(buffer, 0, read);
            read = input.read(buffer);
        } while (read >= 0);
        input.close();
        
        return contents.toString();
    }
}