[#2280] Improve supported formats for MockFileDatabase

This commit is contained in:
Lukas Eder 2013-02-27 15:52:03 +01:00
parent 82fa44d98f
commit e648132697
5 changed files with 203 additions and 14 deletions

View File

@ -1590,6 +1590,82 @@ public class Executor implements Configuration {
return fetchLazy(rs, Utils.getDataTypes(types));
}
/**
* Fetch all data from a formatted string.
* <p>
* The supplied string is supposed to be formatted in the following,
* human-readable way: <code><pre>
* COL1 COL2 COL3 containing whitespace
* ----- ---- --------------------------
* val1 1 some text
* val2 2 more text
* </pre></code> This method will decode the above formatted string
* according to the following rules:
* <ul>
* <li>The number of columns is defined by the number of dash groups in the
* second line</li>
* <li>The column types are <code>VARCHAR(N)</code> where
* <code>N = number of dashes per dash group</code></li>
* <li>The column names are defined by the trimmed text contained in the
* first row</li>
* <li>The data is defined by the trimmed text contained in the subsequent
* rows</li>
* </ul>
* <p>
* This is the same as calling {@link #fetchFromTXT(String, String)} with
* <code>"{null}"</code> as <code>nullLiteral</code>
* <p>
* A future version of jOOQ will also support the inverse operation of
* {@link Result#format()} through this method
*
* @param string The formatted string
* @return The transformed result
* @see #fetchFromTXT(String, String)
* @throws DataAccessException If the supplied string does not adhere to the
* above format rules.
*/
@Support
public final Result<Record> fetchFromTXT(String string) throws DataAccessException {
return fetchFromTXT(string, "{null}");
}
/**
* Fetch all data from a formatted string.
* <p>
* The supplied string is supposed to be formatted in the following,
* human-readable way: <code><pre>
* COL1 COL2 COL3 containing whitespace
* ----- ---- --------------------------
* val1 1 some text
* val2 2 more text
* </pre></code> This method will decode the above formatted string
* according to the following rules:
* <ul>
* <li>The number of columns is defined by the number of dash groups in the
* second line</li>
* <li>The column types are <code>VARCHAR(N)</code> where
* <code>N = number of dashes per dash group</code></li>
* <li>The column names are defined by the trimmed text contained in the
* first row</li>
* <li>The data is defined by the trimmed text contained in the subsequent
* rows</li>
* </ul>
* <p>
* A future version of jOOQ will also support the inverse operation of
* {@link Result#format()} through this method
*
* @param string The formatted string
* @param nullLiteral The string literal to be used as <code>null</code>
* value.
* @return The transformed result
* @throws DataAccessException If the supplied string does not adhere to the
* above format rules.
*/
@Support
public final Result<Record> fetchFromTXT(String string, String nullLiteral) throws DataAccessException {
return fetchFromStringData(Utils.parseTXT(string, nullLiteral));
}
/**
* Fetch all data from a CSV string.
* <p>

View File

@ -75,6 +75,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.persistence.Column;
@ -130,7 +131,7 @@ import org.jooq.util.postgres.PostgresUtils;
*/
final class Utils {
static final JooqLogger log = JooqLogger.getLogger(Utils.class);
static final JooqLogger log = JooqLogger.getLogger(Utils.class);
// ------------------------------------------------------------------------
// Some constants for use with Configuration.setData()
@ -141,7 +142,7 @@ final class Utils {
* clause in {@link Executor#batchStore(UpdatableRecord...)} calls for
* {@link SQLDialect#POSTGRES}
*/
static final String DATA_OMIT_RETURNING_CLAUSE = "org.jooq.configuration.omit-returning-clause";
static final String DATA_OMIT_RETURNING_CLAUSE = "org.jooq.configuration.omit-returning-clause";
/**
* [#1905] This constant is used internally by jOOQ to indicate to
@ -161,7 +162,7 @@ final class Utils {
* The default escape character for <code>[a] LIKE [b] ESCAPE [...]</code>
* clauses.
*/
static final char ESCAPE = '!';
static final char ESCAPE = '!';
/**
* Indicating whether JPA (<code>javax.persistence</code>) is on the
@ -169,10 +170,15 @@ final class Utils {
*/
private static Boolean isJPAAvailable;
/**
* A pattern for the dash line syntax
*/
private static final Pattern DASH_PATTERN = Pattern.compile("(-+)"); ;
/**
* A pattern for the JDBC escape syntax
*/
private static final Pattern JDBC_ESCAPE_PATTERN = Pattern.compile("\\{(fn|d|t|ts)\\b.*");
private static final Pattern JDBC_ESCAPE_PATTERN = Pattern.compile("\\{(fn|d|t|ts)\\b.*");
// ------------------------------------------------------------------------
// XXX: Record constructors and related methods
@ -2396,4 +2402,54 @@ final class Utils {
private static final <T> void pgSetValue(UDTRecord<?> record, Field<T> field, String value) throws SQLException {
record.setValue(field, pgFromString(field.getType(), value));
}
static List<String[]> parseTXT(String string, String nullLiteral) {
String[] strings = string.split("[\\r\\n]+");
if (strings.length < 2) {
throw new DataAccessException("String must contain at least two lines");
}
// Find the set of positions defined by the dashed line number two:
// ----- ------- ----
// results in
// [{0,5} {7,14} {15,19}]
List<int[]> positions = new ArrayList<int[]>();
Matcher m = DASH_PATTERN.matcher(strings[1]);
while (m.find()) {
positions.add(new int[] { m.start(1), m.end(1) });
}
// Parse header line and data lines into string arrays
List<String[]> result = new ArrayList<String[]>();
parseTXTLine(positions, result, strings[0], nullLiteral);
for (int j = 2; j < strings.length; j++) {
parseTXTLine(positions, result, strings[j], nullLiteral);
}
return result;
}
private static void parseTXTLine(List<int[]> positions, List<String[]> result, String string, String nullLiteral) {
String[] fields = new String[positions.size()];
result.add(fields);
int length = string.length();
for (int i = 0; i < fields.length; i++) {
int[] position = positions.get(i);
if (position[0] < length) {
fields[i] = string.substring(position[0], Math.min(position[1], length)).trim();
}
else {
fields[i] = null;
}
if (StringUtils.equals(fields[i], nullLiteral)) {
fields[i] = null;
}
}
}
}

View File

@ -128,12 +128,8 @@ public class MockFileDatabase implements MockDataProvider {
// A line of result data
else if (line.startsWith(">")) {
// But ignore visual delimiter
if (!line.startsWith("> -")) {
currentResult.append(line.substring(2));
currentResult.append("\n");
}
currentResult.append(line.substring(2));
currentResult.append("\n");
}
// A result data termination literal
@ -219,7 +215,7 @@ public class MockFileDatabase implements MockDataProvider {
rows = Integer.parseInt(rowString.substring(7).trim());
}
return new MockResult(rows, create.fetchFromCSV(currentResult.toString(), ' '));
return new MockResult(rows, create.fetchFromTXT(currentResult.toString()));
}
private String readLine() throws IOException {

View File

@ -333,5 +333,53 @@ public class MockTest extends AbstractTest {
@Test
public void testFileDatabase_SELECT_ID1_NAME1_FROM_TABLE1() throws Exception {
Result<Record2<Integer, String>> r = MOCK.select(FIELD_ID1, FIELD_NAME1).from(TABLE1).fetch();
assertEquals(2, r.size());
assertEquals("ID1", r.field(0).getName());
assertEquals("NAME1", r.field(1).getName());
assertEquals(asList(1, 2), r.getValues(0));
assertEquals(asList("X", "Y"), r.getValues(1));
}
@Test
public void testFileDatabase_SELECT_COMPLEX_DATA() throws Exception {
List<Result<Record>> results = MOCK.fetchMany("select complex_data");
assertEquals(2, results.size());
Result<Record> r1 = results.get(0);
Result<Record> r2 = results.get(1);
// Result 1
// --------
assertEquals(2, r1.size());
// Header
assertEquals(3, r1.fields().length);
assertEquals("F1", r1.field(0).getName());
assertEquals("F2", r1.field(1).getName());
assertEquals("F3 is a bit more complex", r1.field(2).getName());
// Data
assertEquals("1", r1.getValue(0, 0));
assertEquals("2", r1.getValue(0, 1));
assertEquals("and a string containing data", r1.getValue(0, 2));
assertEquals("1.1", r1.getValue(1, 0));
assertEquals("x", r1.getValue(1, 1));
assertEquals("another string", r1.getValue(1, 2));
// Result 1
// --------
assertEquals(1, r2.size());
// Header
assertEquals(3, r2.fields().length);
assertEquals("A", r2.field(0).getName());
assertEquals("B", r2.field(1).getName());
assertEquals("\"C D\"", r2.field(2).getName());
// Data
assertEquals("x", r2.getValue(0, 0));
assertEquals("y", r2.getValue(0, 1));
assertEquals("z", r2.getValue(0, 2));
}
}

View File

@ -15,6 +15,19 @@ select 'A', 'B' from dual;
select "TABLE1"."ID1", "TABLE1"."NAME1" from "TABLE1";
> ID1 NAME1
> --- -----
> 1 X
> 2 Y
@ rows: 2
> 1 X
> 2 Y
@ rows: 2
# [#2280] Check if "advanced" CSV content can be handled, too
select complex_data;
> F1 F2 F3 is a bit more complex
> --- -- ----------------------------
> 1 2 and a string containing data
> 1.1 x another string
@ rows: 2
> A B "C D"
> - - -----
> x y z
@ rows: 1