Java tutorial
/** * Licensed to Cloudera, Inc. under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. Cloudera, Inc. licenses this file * to you 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 com.cloudera.sqoop.testutil; import java.io.UnsupportedEncodingException; import java.sql.Blob; import java.sql.ResultSet; import java.sql.SQLException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.io.BytesWritable; import org.junit.Test; /** * Class that implements common tests that should be applied to all jdbc * drivers that we want to interop with. * * The purpose of these tests is to ensure that if a database supports a * given data type, we can import this data type into Sqoop. The test is * not intended to check whether all data types are supported by all * databases, nor that the representation of a given data type has a canonical * representation after being imported. Some databases may not support certain * data types, and the format of the imported data may vary from database to * database. It is not Sqoop's goal to resolve inter-database differences. * However, if a database provides a particular type, we should verify that * we can import this data in some form into HDFS. * * This test battery subjects a database to a variety of import tasks. Many * adapter methods are provided to allow subclasses to modify the exact type * names injected, expected output values, etc., to account for inter-database * discrepencies. * * Each subclass of this class should test a single ConnManager implementation. * Subclasses must implement all abstract methods of this class. They may * also wish to override several members of the class hierarchy above this. * In particular: * * String getConnectString() -- Return the connect string to use to get the db. * void dropTableIfExists(tableName) -- how to drop a table that may not exist. * void createTableWithColTypes() -- how to create a table with a set of cols. * Configuration getConf() -- specifies config properties specific to a test. * SqoopOptions getSqoopOptions(conf) -- Instantiates the SqoopOptions to use. * List<String> getExtraArgs() -- specifies extra argv elements. */ public abstract class ManagerCompatTestCase extends ImportJobTestCase { private Log log; public ManagerCompatTestCase() { this.log = LogFactory.getLog(ManagerCompatTestCase.class.getName()); } /** * @return the Log object to use for reporting during this test */ protected abstract Log getLogger(); /** * @return a "friendly" name for the database. e.g "mysql" or "oracle". */ protected abstract String getDbFriendlyName(); /** Set to true during tearDown() if a test is skipped. */ protected boolean skipped; @Override protected String getTablePrefix() { return "MGR_" + getDbFriendlyName().toUpperCase() + "_"; } @Override protected boolean useHsqldbTestServer() { // Compat tests, by default, do not use hsqldb. return false; } @Override public void setUp() { log = getLogger(); skipped = false; super.setUp(); } @Override public void tearDown() { try { // Clean up the database on our way out. dropTableIfExists(getTableName()); } catch (SQLException e) { log.warn("Error trying to drop table '" + getTableName() + "' on tearDown: " + e); } super.tearDown(); } //////// These methods indicate whether certain datatypes are supported //////// by the underlying database. /** @return true if the database under test has a BOOLEAN type */ protected boolean supportsBoolean() { return true; } /** @return true if the database under test has a BIGINT type */ protected boolean supportsBigInt() { return true; } /** @return true if the database under test has a TINYINT type */ protected boolean supportsTinyInt() { return true; } /** @return true if the database under test has a LONGVARCHAR type */ protected boolean supportsLongVarChar() { return true; } /** @return true if the database under test has a VARBINARY type */ protected boolean supportsVarBinary() { return true; } /** @return true if the database under test has a TIME type */ protected boolean supportsTime() { return true; } /** @return true if the database under test supports CLOB types */ protected boolean supportsClob() { return true; } /** @return true if the database under test supports BLOB types */ protected boolean supportsBlob() { return true; } //////// These methods indicate how to define various datatypes. /** * Define a NUMERIC type that can handle 30 digits total, and 5 * digits to the right of the decimal point. */ protected String getNumericType() { return "NUMERIC(" + getNumericScale() + ", " + getNumericDecPartDigits() + ")"; } protected String getDecimalType() { return "DECIMAL(" + getDecimalScale() + ", " + getDecimalDecPartDigits() + ")"; } /** * Return the number of digits to use in the integral part of a * NUMERIC type. */ protected int getNumericScale() { return 30; } /** * Return the number of digits to use in the decimal part of a * NUMERIC type. */ protected int getNumericDecPartDigits() { return 5; } /** * Return the number of digits to use in the integral part of a * DECIMAL type. */ protected int getDecimalScale() { return 30; } /** * Return the number of digits to use in the decimal part of a * DECIMAL type. */ protected int getDecimalDecPartDigits() { return 5; } /** * Define a DOUBLE column. */ protected String getDoubleType() { return "DOUBLE"; } /** * Define a LONGVARCHAR type that can handle at least 24 characters. */ protected String getLongVarCharType() { return "LONGVARCHAR"; } /** * Define a TIMESTAMP type that can handle null values. */ protected String getTimestampType() { return "TIMESTAMP"; } /** * Define a CLOB column that can contain up to 16 MB of data. */ protected String getClobType() { return "CLOB"; } /** * Define a BLOB column that can contain up to 16 MB of data. */ protected String getBlobType() { return "BLOB"; } /** * Define a VARBINARY column that can contain up to 12 bytes of data. */ protected String getVarBinaryType() { return "VARBINARY(12)"; } /** * Define a TINYINT column that can contain 8-bit signed integers. */ protected String getTinyIntType() { return "TINYINT"; } //////// These methods indicate how databases respond to various datatypes. //////// Since our comparisons are all string-based, these return strings. /** @return How we insert the value TRUE represented as an int. */ protected String getTrueBoolNumericSqlInput() { return "1"; } /** @return How we insert the value FALSE represented as an int. */ protected String getFalseBoolNumericSqlInput() { return "0"; } /** @return How we insert the value TRUE represented as a boolean literal. */ protected String getTrueBoolLiteralSqlInput() { return "true"; } /** @return How we insert the value FALSE represented as a boolean literal. */ protected String getFalseBoolLiteralSqlInput() { return "false"; } /** @return How a BOOLEAN column with value TRUE is communicated over JDBC */ protected String getTrueBoolDbOutput() { return "true"; } /** @return How a BOOLEAN column with value TRUE is represented in a seq-file * import. */ protected String getTrueBoolSeqOutput() { return "true"; } /** @return How a BOOLEAN column with value FALSE is communicated over JDBC */ protected String getFalseBoolDbOutput() { return "false"; } /** @return How a BOOLEAN column with value FALSE is represented in a seq-file * import. */ protected String getFalseBoolSeqOutput() { return "false"; } protected String padString(int width, String str) { int extra = width - str.length(); for (int i = 0; i < extra; i++) { str = str + " "; } return str; } /** * helper method: return a floating-point string in the same way * it was entered, but integers get a trailing '.0' attached. */ protected String withDecimalZero(String floatingPointStr) { if (floatingPointStr.indexOf(".") == -1) { return floatingPointStr + ".0"; } else { return floatingPointStr; } } /** * A real value inserted as '40' may be returned as '40', '40.', or '40.0', * etc. Given a string that defines how a real value is inserted, determine * how it is returned. * * @param realAsInserted the string we used in the SQL INSERT statement * @return how the string version of this as returned by the database is * represented. */ protected String getRealDbOutput(String realAsInserted) { return withDecimalZero(realAsInserted); } /** * @return how a given real value is represented in an imported sequence * file */ protected String getRealSeqOutput(String realAsInserted) { return getRealDbOutput(realAsInserted); } /** * A float value inserted as '40' may be returned as '40', '40.', or '40.0', * etc. Given a string that defines how a float value is inserted, determine * how it is returned. * * @param floatAsInserted the string we used in the SQL INSERT statement * @return how the string version of this as returned by the database is * represented. */ protected String getFloatDbOutput(String floatAsInserted) { return withDecimalZero(floatAsInserted); } protected String getFloatSeqOutput(String floatAsInserted) { return getFloatDbOutput(floatAsInserted); } /** * A double value inserted as '40' may be returned as '40', '40.', or '40.0', * etc. Given a string that defines how a double value is inserted, determine * how it is returned. * * @param doubleAsInserted the string we used in the SQL INSERT statement * @return how the string version of this as returned by the database is * represented. */ protected String getDoubleDbOutput(String doubleAsInserted) { return withDecimalZero(doubleAsInserted); } protected String getDoubleSeqOutput(String doubleAsInserted) { return getDoubleDbOutput(doubleAsInserted); } /** * Some databases require that we insert dates using a special format. * This takes the canonical string used to insert a DATE into a table, * and specializes it to the SQL dialect used by the database under * test. */ protected String getDateInsertStr(String insertStr) { return insertStr; } /** * Some databases require that we insert times using a special format. * This takes the canonical string used to insert a TIME into a table, * and specializes it to the SQL dialect used by the database under * test. */ protected String getTimeInsertStr(String insertStr) { return insertStr; } /** * Some databases require that we insert timestamps using a special format. * This takes the canonical string used to insert a TIMESTAMP into a table, * and specializes it to the SQL dialect used by the database under * test. */ protected String getTimestampInsertStr(String insertStr) { return insertStr; } protected String getDateDbOutput(String dateAsInserted) { return dateAsInserted; } protected String getDateSeqOutput(String dateAsInserted) { return dateAsInserted; } /** * Convert an input timestamp to the string representation of the timestamp * returned by a database select query. * * @param tsAsInserted the input timestamp * @return the string version of this as returned by the database is * represented. */ protected String getTimestampDbOutput(String tsAsInserted) { if ("null".equals(tsAsInserted)) { return tsAsInserted; } int dotPos = tsAsInserted.indexOf("."); if (-1 == dotPos) { // No dot in the original string; expand to 9 places. return tsAsInserted + ".000000000"; } else { // Default with a dot is to pad the nanoseconds column to 9 places. int numZerosNeeded = tsAsInserted.length() - dotPos; String zeros = ""; for (int i = 0; i < numZerosNeeded; i++) { zeros = zeros + "0"; } return tsAsInserted + zeros; } } /** * Convert an input timestamp to the string representation of the timestamp * returned by a sequencefile-based import. * * @param tsAsInserted the input timestamp * @return the string version of this as returned by the database is * represented. */ protected String getTimestampSeqOutput(String tsAsInserted) { if ("null".equals(tsAsInserted)) { return tsAsInserted; } int dotPos = tsAsInserted.indexOf("."); if (-1 == dotPos) { // No dot in the original string; expand to add a single item after the // dot. return tsAsInserted + ".0"; } else { // all other strings return as-is. return tsAsInserted; } } protected String getNumericDbOutput(String numAsInserted) { return numAsInserted; } protected String getNumericSeqOutput(String numAsInserted) { return getNumericDbOutput(numAsInserted); } protected String getDecimalDbOutput(String numAsInserted) { return numAsInserted; } protected String getDecimalSeqOutput(String numAsInserted) { return getDecimalDbOutput(numAsInserted); } /** * @return how a CHAR(fieldWidth) field is returned by the database * for a given input. */ protected String getFixedCharDbOut(int fieldWidth, String asInserted) { return asInserted; } protected String getFixedCharSeqOut(int fieldWidth, String asInserted) { return asInserted; } /** * Encode a string to be inserted in a BLOB field. * @param blobData the raw text (Without quote marks) to insert for a BLOB. * @return 'blobData' in a String form ready for insertion */ protected String getBlobInsertStr(String blobData) { return "'" + blobData + "'"; } /** * @return A byte array declaring how an inserted BLOB will be returned to * us via the database. */ protected byte[] getBlobDbOutput(String asInserted) { // The database will give us back a byte array; we need to create // an identical byte array. try { return asInserted.getBytes("UTF-8"); } catch (UnsupportedEncodingException uee) { fail("Could not get utf8 bytes"); // Java should always support UTF-8. return null; } } /** * @return A String declaring how an inserted BLOB will be returned to * us via the sequencefile. */ protected String getBlobSeqOutput(String asInserted) { return new BytesWritable(getBlobDbOutput(asInserted)).toString(); } /** * @return A String declaring how an inserted VARBINARY will be * returned to us via the database. */ protected String getVarBinaryDbOutput(String asInserted) { return asInserted; } /** * @return A String declaring how an inserted VARBINARY will be * returned to us via the sequencefile. */ protected String getVarBinarySeqOutput(String asInserted) { return new BytesWritable(getBlobDbOutput(asInserted)).toString(); } /** * Given a string of characters which represent hex values * (e.g., 'ABF00F1238'), return the string as a set of separated * octets, in lower case (e.g., 'ab f0 0f 12 38'). * * @param str the input string of hex digits * @return the input string as space-separated lower-case octets. */ protected String toLowerHexString(String str) { // The inserted text is a hex string of the form 'ABABABAB'. // We return it in the form 'ab ab ab ab'. StringBuilder sb = new StringBuilder(); boolean isOdd = false; boolean first = true; for (char c : str.toCharArray()) { if (!isOdd && !first) { sb.append(' '); } sb.append(Character.toLowerCase(c)); isOdd = !isOdd; first = false; } return sb.toString(); } //////// The actual tests occur below here. //////// /** * Do a full verification test on the singleton value of a given type. * @param colType The SQL type to instantiate the column. * @param insertVal The SQL text to insert a value into the database. * @param returnVal The string representation of the value as extracted * from the db. */ protected void verifyType(String colType, String insertVal, String returnVal) { verifyType(colType, insertVal, returnVal, returnVal); } /** * Do a full verification test on the singleton value of a given type. * @param colType The SQL type to instantiate the column. * @param insertVal The SQL text to insert a value into the database. * @param returnVal The string representation of the value as extracted from * the db. * @param seqFileVal The string representation of the value as extracted * through the DBInputFormat, serialized, and injected into a * SequenceFile and put through toString(). This may be slightly * different than what ResultSet.getString() returns, which is used * by returnVal. */ protected void verifyType(String colType, String insertVal, String returnVal, String seqFileVal) { verifyType(colType, insertVal, returnVal, seqFileVal, false); } protected void verifyType(String colType, String insertVal, String returnVal, String seqFileVal, boolean useIntPrimaryKey) { int readBackCol; String readbackPrepend = ""; if (useIntPrimaryKey) { String[] types = { "INTEGER", colType }; String[] vals = { "0", insertVal }; createTableWithColTypes(types, vals); readBackCol = 2; readbackPrepend = "0,"; // verifyImport will verify the entire row. } else { createTableForColType(colType, insertVal); readBackCol = 1; } verifyReadback(readBackCol, returnVal); verifyImport(readbackPrepend + seqFileVal, null); } static final String STRING_VAL_IN = "'this is a short string'"; static final String STRING_VAL_OUT = "this is a short string"; @Test public void testStringCol1() { verifyType("VARCHAR(32)", STRING_VAL_IN, STRING_VAL_OUT); } @Test public void testStringCol2() { verifyType("CHAR(32)", STRING_VAL_IN, getFixedCharDbOut(32, STRING_VAL_OUT), getFixedCharSeqOut(32, STRING_VAL_OUT)); } @Test public void testEmptyStringCol() { verifyType("VARCHAR(32)", "''", ""); } @Test public void testNullStringCol() { verifyType("VARCHAR(32)", "NULL", null); } @Test public void testInt() { verifyType("INTEGER", "42", "42"); } @Test public void testNullInt() { verifyType("INTEGER", "NULL", null); } @Test public void testBoolean() { if (!supportsBoolean()) { log.info("Skipping boolean test (unsupported)"); skipped = true; return; } verifyType("BOOLEAN", getTrueBoolNumericSqlInput(), getTrueBoolDbOutput(), getTrueBoolSeqOutput()); } @Test public void testBoolean2() { if (!supportsBoolean()) { log.info("Skipping boolean test (unsupported)"); skipped = true; return; } verifyType("BOOLEAN", getFalseBoolNumericSqlInput(), getFalseBoolDbOutput(), getFalseBoolSeqOutput()); } @Test public void testBoolean3() { if (!supportsBoolean()) { log.info("Skipping boolean test (unsupported)"); skipped = true; return; } verifyType("BOOLEAN", getFalseBoolLiteralSqlInput(), getFalseBoolDbOutput(), getFalseBoolSeqOutput()); } @Test public void testTinyInt1() { if (!supportsTinyInt()) { log.info("Skipping tinyint test (unsupported)"); skipped = true; return; } verifyType(getTinyIntType(), "0", "0"); } @Test public void testTinyInt2() { if (!supportsTinyInt()) { log.info("Skipping tinyint test (unsupported)"); skipped = true; return; } verifyType(getTinyIntType(), "42", "42"); } @Test public void testSmallInt1() { verifyType("SMALLINT", "-1024", "-1024"); } @Test public void testSmallInt2() { verifyType("SMALLINT", "2048", "2048"); } @Test public void testBigInt1() { if (!supportsBigInt()) { log.info("Skipping bigint test (unsupported)"); skipped = true; return; } verifyType("BIGINT", "10000000000", "10000000000"); } @Test public void testReal1() { verifyType("REAL", "256", getRealDbOutput("256"), getRealSeqOutput("256")); } @Test public void testReal2() { verifyType("REAL", "256.45", getRealDbOutput("256.45"), getRealSeqOutput("256.45")); } @Test public void testFloat1() { verifyType("FLOAT", "256", getFloatDbOutput("256"), getFloatSeqOutput("256")); } @Test public void testFloat2() { verifyType("FLOAT", "256.5", getFloatDbOutput("256.5"), getFloatSeqOutput("256.5")); } @Test public void testDouble1() { verifyType(getDoubleType(), "-256", getDoubleDbOutput("-256"), getDoubleSeqOutput("-256")); } @Test public void testDouble2() { verifyType(getDoubleType(), "256.45", getDoubleDbOutput("256.45"), getDoubleSeqOutput("256.45")); } @Test public void testDate1() { verifyType("DATE", getDateInsertStr("'2009-01-12'"), getDateDbOutput("2009-01-12"), getDateSeqOutput("2009-01-12")); } @Test public void testDate2() { verifyType("DATE", getDateInsertStr("'2009-04-24'"), getDateDbOutput("2009-04-24"), getDateSeqOutput("2009-04-24")); } @Test public void testTime1() { if (!supportsTime()) { log.info("Skipping time test (unsupported)"); skipped = true; return; } verifyType("TIME", getTimeInsertStr("'12:24:00'"), "12:24:00"); } @Test public void testTime2() { if (!supportsTime()) { log.info("Skipping time test (unsupported)"); skipped = true; return; } verifyType("TIME", getTimeInsertStr("'06:24:00'"), "06:24:00"); } @Test public void testTime3() { if (!supportsTime()) { log.info("Skipping time test (unsupported)"); skipped = true; return; } verifyType("TIME", getTimeInsertStr("'6:24:00'"), "06:24:00"); } @Test public void testTime4() { if (!supportsTime()) { log.info("Skipping time test (unsupported)"); skipped = true; return; } verifyType("TIME", getTimeInsertStr("'18:24:00'"), "18:24:00"); } @Test public void testTimestamp1() { verifyType(getTimestampType(), getTimestampInsertStr("'2009-04-24 18:24:00'"), getTimestampDbOutput("2009-04-24 18:24:00"), getTimestampSeqOutput("2009-04-24 18:24:00")); } @Test public void testTimestamp2() { try { log.debug("Beginning testTimestamp2"); verifyType(getTimestampType(), getTimestampInsertStr("'2009-04-24 18:24:00.0002'"), getTimestampDbOutput("2009-04-24 18:24:00.0002"), getTimestampSeqOutput("2009-04-24 18:24:00.0002")); } finally { log.debug("End testTimestamp2"); } } @Test public void testTimestamp3() { try { log.debug("Beginning testTimestamp3"); verifyType(getTimestampType(), "null", null); } finally { log.debug("End testTimestamp3"); } } @Test public void testNumeric1() { verifyType(getNumericType(), "1", getNumericDbOutput("1"), getNumericSeqOutput("1")); } @Test public void testNumeric2() { verifyType(getNumericType(), "-10", getNumericDbOutput("-10"), getNumericSeqOutput("-10")); } @Test public void testNumeric3() { verifyType(getNumericType(), "3.14159", getNumericDbOutput("3.14159"), getNumericSeqOutput("3.14159")); } @Test public void testNumeric4() { verifyType(getNumericType(), "3000000000000000000.14159", getNumericDbOutput("3000000000000000000.14159"), getNumericSeqOutput("3000000000000000000.14159")); } @Test public void testNumeric5() { verifyType(getNumericType(), "99999999999999999999.14159", getNumericDbOutput("99999999999999999999.14159"), getNumericSeqOutput("99999999999999999999.14159")); } @Test public void testNumeric6() { verifyType(getNumericType(), "-99999999999999999999.14159", getNumericDbOutput("-99999999999999999999.14159"), getNumericSeqOutput("-99999999999999999999.14159")); } @Test public void testDecimal1() { verifyType(getDecimalType(), "1", getDecimalDbOutput("1"), getDecimalSeqOutput("1")); } @Test public void testDecimal2() { verifyType(getDecimalType(), "-10", getDecimalDbOutput("-10"), getDecimalSeqOutput("-10")); } @Test public void testDecimal3() { verifyType(getDecimalType(), "3.14159", getDecimalDbOutput("3.14159"), getDecimalSeqOutput("3.14159")); } @Test public void testDecimal4() { verifyType(getDecimalType(), "3000000000000000000.14159", getDecimalDbOutput("3000000000000000000.14159"), getDecimalSeqOutput("3000000000000000000.14159")); } @Test public void testDecimal5() { verifyType(getDecimalType(), "99999999999999999999.14159", getDecimalDbOutput("99999999999999999999.14159"), getDecimalSeqOutput("99999999999999999999.14159")); } @Test public void testDecimal6() { verifyType(getDecimalType(), "-99999999999999999999.14159", getDecimalDbOutput("-99999999999999999999.14159"), getDecimalSeqOutput("-99999999999999999999.14159")); } @Test public void testLongVarChar() { if (!supportsLongVarChar()) { log.info("Skipping long varchar test (unsupported)"); skipped = true; return; } verifyType(getLongVarCharType(), "'this is a long varchar'", "this is a long varchar"); } protected void verifyClob(String insertVal, String returnVal, String seqFileVal) { String[] types = { "INTEGER NOT NULL", getClobType() }; String[] vals = { "1", insertVal }; String[] checkCol = { "DATA_COL0", "DATA_COL1" }; createTableWithColTypes(types, vals); verifyReadback(2, returnVal); verifyImport("1," + seqFileVal, checkCol); } protected void verifyBlob(String insertVal, byte[] returnVal, String seqFileVal) { String[] types = { "INTEGER NOT NULL", getBlobType() }; String[] vals = { "1", insertVal }; String[] checkCols = { "DATA_COL0", "DATA_COL1" }; createTableWithColTypes(types, vals); // Verify readback of the data. ResultSet results = null; try { results = getManager().readTable(getTableName(), getColNames()); assertNotNull("Null results from readTable()!", results); assertTrue("Expected at least one row returned", results.next()); Blob blob = results.getBlob(2); byte[] databaseBytes = blob.getBytes(1, (int) blob.length()); log.info("Verifying readback of bytes from " + getTableName()); assertEquals("byte arrays differ in size", returnVal.length, databaseBytes.length); for (int i = 0; i < returnVal.length; i++) { assertEquals("bytes differ at position " + i + ". Expected " + returnVal[i] + "; got " + databaseBytes[i], returnVal[i], databaseBytes[i]); } assertFalse("Expected at most one row returned", results.next()); } catch (SQLException sqlE) { fail("Got SQLException: " + sqlE.toString()); } finally { if (null != results) { try { results.close(); } catch (SQLException sqlE) { fail("Got SQLException in resultset.close(): " + sqlE.toString()); } } // Free internal resources after the readTable. getManager().release(); } // Now verify that we can use the Sqoop import mechanism on this data. verifyImport("1," + seqFileVal, checkCols); } @Test public void testClob1() { if (!supportsClob()) { log.info("Skipping CLOB test; database does not support CLOB"); return; } verifyClob("'This is short CLOB data'", "This is short CLOB data", "This is short CLOB data"); } @Test public void testBlob1() { if (!supportsBlob()) { log.info("Skipping BLOB test; database does not support BLOB"); return; } verifyBlob(getBlobInsertStr("This is short BLOB data"), getBlobDbOutput("This is short BLOB data"), getBlobSeqOutput("This is short BLOB data")); } @Test public void testVarBinary() { if (!supportsVarBinary()) { log.info("Skipping VARBINARY test; database does not support VARBINARY"); return; } verifyType(getVarBinaryType(), "'F00FABCD'", getVarBinaryDbOutput("F00FABCD"), getVarBinarySeqOutput("F00FABCD"), true); } }