/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 * Modified by
 * @author  Data Virtuality GmbH
 * @date    $Date$
 *
 */
// https://issues.jboss.org/browse/TEIID-2368
// https://issues.jboss.org/browse/TEIID-2343
// https://issues.jboss.org/browse/TEIID-2904
// https://issues.jboss.org/browse/TEIID-2281
// https://issues.jboss.org/browse/TEIID-2572
// https://issues.jboss.org/browse/TEIID-5180
// https://issues.jboss.org/browse/TEIID-5187
// https://issues.jboss.org/browse/TEIID-3333
// https://issues.redhat.com/browse/TEIID-2384
// https://issues.redhat.com/browse/TEIID-5480
package org.teiid.translator.jdbc;

import com.datavirtuality.dv.core.interfaces.DVCaller;
import org.teiid.core.types.AbstractGeospatialType;
import org.teiid.core.types.DataTypeManager;
import org.teiid.core.types.GeographyType;
import org.teiid.core.types.GeometryType;
import org.teiid.core.util.SqlUtil;
import org.teiid.core.util.StringUtil;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.logging.MessageLevel;
import org.teiid.metadata.*;
import org.teiid.metadata.BaseColumn.NullType;
import org.teiid.metadata.ProcedureParameter.Type;
import org.teiid.translator.*;
import org.teiid.translator.TranslatorProperty.PropertyType;
import org.teiid.translator.jdbc.plugin.JDBCPlugin;

import java.sql.*;
import java.text.MessageFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Reads from {@link DatabaseMetaData} and creates metadata through the {@link MetadataFactory}.
 */
public class JDBCMetadataProcessor implements MetadataProcessor<Connection>{

	/**
	 * A holder for table records that keeps track of catalog and schema information.
	 */
	public static class TableInfo {
		public String catalog;
		public String schema;
		public String name;
		public Table table;

		public TableInfo(String catalog, String schema, String name, Table table) {
			this.catalog = catalog;
			this.schema = schema;
			this.name = name;
			this.table = table;
		}

		public String getName() {
			StringBuilder sb = new StringBuilder();
			sb.append(catalog).append('.').append(schema).append('.').append(name);
			return sb.toString();
		}
	}

	public static class ProcInfo {
		public String catalog;
		public String schema;
		public String name;
		public Procedure proc;
		public int counter = 0;
		public boolean customProcessed = false;
		Set<String> cols = new HashSet<>();
		Set<String> names = new HashSet<>();

		public ProcInfo(String catalog, String schema, String name, Procedure proc) {
			this.catalog = catalog;
			this.schema = schema;
			this.name = name;
			this.proc = proc;
		}

		public static String getKey(String catalog, String schema, String name) {
			return catalog + "###" + schema + "###" + name;
		}
	}

	private boolean importProcedures;
	private boolean importKeys;
	private boolean importForeignKeys = true;
	private boolean importIndexes;
	private String procedureNamePattern;
	protected boolean useFullSchemaName = true;
	private String[] tableTypes;
	private String tableNamePattern;
	private String catalog;
	private String schemaPattern;
	private boolean loadMetadataWithJdbc = false;
	private Integer fetchSize = null;
	private String excludeSchemas = null;
	private boolean importApproximateIndexes = true;
	private boolean widenUnsignedTypes = true;
	private boolean quoteNameInSource = true;
	private boolean useProcedureSpecificName;
	protected boolean useCatalogName = true;
	private boolean autoCreateUniqueConstraints = true;
	private boolean useQualifiedName = true;
	private boolean loadAllOracleColumnComments = true;
	private boolean loadAllOracleTableComments = true;

	private Set<String> unsignedTypes = new HashSet<String>();
	private String quoteString;

	private Pattern excludeTables;
	private Pattern excludeProcedures;
	private boolean replaceSpecSymbsInColNames;

	private String columnNamePattern;

	private String defaultSchema = null;

	private boolean loadColumnsTableByTable = false;
	private boolean loadSourceSystemFunctions;

	public interface IColumnProcessor {
		void process(MetadataFactory metadataFactory, ProcInfo procInfo, Procedure procedure, String procedureCatalog, String procedureSchema, String name, String specificName);
	}
	private IColumnProcessor columnProcessor = null;

	public void setColumnProcessor(IColumnProcessor columnProcessor) {
		this.columnProcessor = columnProcessor;
	}

	public static abstract class ITypeConverter {
		public abstract String getRuntimeType(int type, String typeName, int precision, int scale);

		protected static String noBooleanNumericCheck(String typeName, int precision, int scale) {
			if ("NUMERIC".equalsIgnoreCase(typeName) ||
					"DECIMAL".equalsIgnoreCase(typeName) ||
					"NUMBER".equalsIgnoreCase(typeName)) {
				if (precision == 1 && scale == 0)
					return DataTypeManager.DefaultDataTypes.BOOLEAN;
				if (precision == 0 && scale == -127)
					return DataTypeManager.DefaultDataTypes.BIG_DECIMAL;
				if (scale < 0)
					return DataTypeManager.DefaultDataTypes.BIG_INTEGER;
			}
			return null;
		}

		public void tuneColumn(Column column, int type, String typeName, int precision, int scale) {
			if (type == Types.CHAR) {
				if (precision == 1)
					column.setRuntimeType(DataTypeManager.DefaultDataTypes.CHAR);
				else
					column.setRuntimeType(DataTypeManager.DefaultDataTypes.STRING);
			}
		}

		public String getProcedureColumnRuntimeType(int type, String typeName, int precision, int scale) {
			return null;
		}

		public void tuneColumnType(Column column, int type, String typeName, int precision, int scale) {
			usualNumericTune(column, type, typeName, precision, scale);
			tuneColumn(column, type, typeName, precision, scale);
		}

		public String tuneRuntimeType(int type, String typeName, int precision, int scale) {
			String result = usualNumericCheck(typeName, precision, scale);
			if (result != null)
				return result;
			return getRuntimeType(type, typeName, precision, scale);
		}

		public boolean filterColumn(String column, int type, String typeName, int precision, int scale, int charOctetLength) {
			return true;
		}

		public boolean filterProcedureColumn(String columnName, int type, String typeName, int precision, int scale) {
			return true;
		}

		private static final Pattern TYPE_OF_TYPE = Pattern.compile("^(\\w+)\\s*\\(\\s*(.+)\\s*\\)$");
		public boolean typeOfTypeEq(String typeName, String type1, String type2) {
			if (typeName == null)
				return false;
			typeName = typeName.trim();
			final Matcher matcher = TYPE_OF_TYPE.matcher(typeName);
			if (!matcher.matches())
				return false;
			if (matcher.groupCount() != 2)
				return false;
			if (type1 != null && !matcher.group(1).equalsIgnoreCase(type1))
				return false;
			return type2 == null || matcher.group(2).equalsIgnoreCase(type2);
		}

		protected String usualNumericCheck(String typeName, int precision, int scale) {
			if ("NUMERIC".equalsIgnoreCase(typeName) ||
					"DECIMAL".equalsIgnoreCase(typeName) ||
					"NUMBER".equalsIgnoreCase(typeName)) {
				// scale == -127 for Oracle
				// precision == 0 for PG
				if (scale == 0 && precision < 131072)
					return DataTypeManager.DefaultDataTypes.BIG_INTEGER;
				return DataTypeManager.DefaultDataTypes.BIG_DECIMAL;
			}
			return null;
		}

		protected void usualNumericTune(Column column, int type, String typeName, int precision, int scale) {
			if ("NUMERIC".equalsIgnoreCase(typeName) ||
					"DECIMAL".equalsIgnoreCase(typeName) ||
					"NUMBER".equalsIgnoreCase(typeName)) {
				if (scale <= 0)
					column.setScale(0);
			}
		}
	}

	protected boolean shouldExclude(String fullName) {
		return excludeTables != null && excludeTables.matcher(fullName).matches();
	}

	protected ITypeConverter typeConv;
	public void getConnectorMetadata(Connection conn, MetadataFactory metadataFactory, ITypeConverter typeConv)
			throws SQLException, TranslatorException {
		this.typeConv = typeConv;
		getConnectorMetadata(conn, metadataFactory);
	}

	public void process(MetadataFactory metadataFactory, Connection conn) throws TranslatorException {
		try {
			getConnectorMetadata(conn, metadataFactory);
		} catch (SQLException e) {
			throw new TranslatorException(JDBCPlugin.Event.TEIID11010, e);
		}
	}

	public void getConnectorMetadata(Connection conn, MetadataFactory metadataFactory)
			throws SQLException, TranslatorException {

		// fix excludeSchemas
		String newExcludeSchemas = (String) DVCaller.call(93, excludeSchemas, schemaPattern, defaultSchema);
		if (newExcludeSchemas != null)
			excludeSchemas = newExcludeSchemas;

		DatabaseMetaData metadata = conn.getMetaData();

		if (quoteString == null) {
		quoteString = metadata.getIdentifierQuoteString();
		if (quoteString != null && quoteString.trim().length() == 0) {
			quoteString = null;
		}
		}

		if (widenUnsignedTypes) {
			ResultSet rs = null;
			try {
				rs = metadata.getTypeInfo();
			} catch (SQLException e) {
				LogManager.logWarning(LogConstants.CTX_CONNECTOR, e, JDBCPlugin.Util.gs(JDBCPlugin.Event.TEIID11021));
			}
			if (rs != null) {
			if (fetchSize != null && fetchSize > 0)
				rs.setFetchSize(fetchSize);
			while (rs.next()) {
				String name = rs.getString(1);
				boolean unsigned = rs.getBoolean(10);
				if (unsigned) {
					unsignedTypes.add(name);
				}
			}
		}
		}

		try {
			Map<String, TableInfo> tableMap = getTables(metadataFactory, metadata, conn, tableTypes);
			HashSet<TableInfo> tables = new LinkedHashSet<TableInfo>(tableMap.values());
			if (importKeys) {
				getPrimaryKeys(metadataFactory, metadata, tables);
				getIndexes(metadataFactory, metadata, tables, !importIndexes);
				if (importForeignKeys) {
				getForeignKeys(metadataFactory, metadata, tables, tableMap);
				}
			} else if (importIndexes) {
				getIndexes(metadataFactory, metadata, tables, false);
			}

			if (importProcedures) {
				getProcedures(metadataFactory, metadata, conn);
			}
		} catch (DuplicateRecordException e) {
			throw new TranslatorException(JDBCPlugin.Event.TEIID11006, e, JDBCPlugin.Util.gs(JDBCPlugin.Event.TEIID11006));
		}

	}

	protected void getProcedures(MetadataFactory metadataFactory, DatabaseMetaData metadata, Connection conn) throws SQLException {
		LogManager.logDetail(LogConstants.CTX_METADATA, "JDBCMetadataProcessor - Importing procedures"); //$NON-NLS-1$
		@SuppressWarnings("unchecked")
		Collection<String> schemaPatterns = (Collection<String>) DVCaller.call(41, schemaPattern, metadata, catalog, excludeSchemas);
		if (schemaPatterns == null) {
			schemaPatterns = new ArrayList<String>();
			schemaPatterns.add(schemaPattern);
		}
		Set<String> fullNames = new HashSet<String>();
		Map<String, ProcInfo> procMap = new HashMap<>();
		for (String pattern : schemaPatterns) {
			PreparedStatement pstmt = null;
			if (!loadMetadataWithJdbc)
				pstmt = (PreparedStatement) DVCaller.callWithThrow(SQLException.class, 143, metadata, catalog, pattern, procedureNamePattern);
			ResultSet procedures = null;
			if (pstmt != null)
				procedures = pstmt.executeQuery();
			else
				procedures = metadata.getProcedures(catalog, pattern, procedureNamePattern);
			try {
			if (fetchSize != null && fetchSize > 0)
				procedures.setFetchSize(fetchSize);
			int rsColumns = procedures.getMetaData().getColumnCount();
			while (procedures.next()) {
				String procedureCatalog = procedures.getString(1);
				String procedureSchema = procedures.getString(2);
				String procedureName = procedures.getString(3);
				procedureName = trimProcName(procedureName);
					LogManager.logTrace(LogConstants.CTX_METADATA, "JDBCMetadataProcessor - Importing procedure: " + procedureName);
				String name = procedureName;
				String specificName = null;
				String nameInSource = procedureName;
				if (useProcedureSpecificName && rsColumns >= 9) {
					specificName = procedures.getString(9);
					procedureName = specificName;
					if (procedureName == null) {
						procedureName = nameInSource;
					}
				}
					String fullProcedureName = getFullyQualifiedName(procedureCatalog, procedureSchema, procedureName,
							executionFactory != null && executionFactory.forceQuotedIdentifiers());
				if (excludeProcedures != null && excludeProcedures.matcher(fullProcedureName).matches()) {
					continue;
				}
				if (fullNames.contains(fullProcedureName))
					continue;
				fullNames.add(fullProcedureName);
					Procedure procedure = metadataFactory.addProcedure(useFullSchemaName ? fullProcedureName : procedureName);
				procedure.setNameInSource(getFullyQualifiedName(procedureCatalog, procedureSchema, nameInSource, true));
					if (rsColumns >= 7)
					procedure.setRemark(procedures.getString(7));
				final ProcInfo procInfo = new ProcInfo(procedureCatalog, procedureSchema, procedureName, procedure);
				if (columnProcessor != null)
					columnProcessor.process(metadataFactory, procInfo, procedure, procedureCatalog, procedureSchema,
							name, specificName);
				procMap.put(ProcInfo.getKey(procedureCatalog, procedureSchema, procedureName),
						procInfo);
				}
			} finally {
			procedures.close();
			if (pstmt != null)
				pstmt.close();
		}
		}
		getProcedureColumns(metadataFactory, metadata, schemaPatterns, procMap);
	}

	protected void getProcedureColumns(MetadataFactory metadataFactory, DatabaseMetaData metadata, Collection<String> schemaPatterns, Map<String, ProcInfo> procMap) throws SQLException {
		for (String pattern : schemaPatterns) {
			try (ResultSet columns = metadata.getProcedureColumns(catalog, pattern, procedureNamePattern, null)) {
			int rsProcColumns = columns.getMetaData().getColumnCount();
			if (fetchSize != null && fetchSize > 0)
				columns.setFetchSize(fetchSize);
			while (columns.next()) {
					getProcedureColumn(metadataFactory, procMap, columns, rsProcColumns);
				}
			}
		}
	}

	protected void getProcedureColumn(MetadataFactory metadataFactory, Map<String, ProcInfo> procMap, ResultSet columns, int rsProcColumns) throws SQLException {
				String procedureCatalog = columns.getString(1);
				String procedureSchema = columns.getString(2);
				String procedureName = columns.getString(3);
				String nameInSource = procedureName;
				if (useProcedureSpecificName && columns.getMetaData().getColumnCount() >= 20) {
					procedureName = columns.getString(20);
					if (procedureName == null) {
						procedureName = nameInSource;
					}
				}
				// Oracle returns null here
				procedureName = trimProcName(procedureName);
				ProcInfo procInfo = procMap.get(ProcInfo.getKey(procedureCatalog, procedureSchema, procedureName));
				if (procInfo == null)
			return;
				if (procInfo.customProcessed)
			return;
				Procedure procedure = procInfo.proc;
				String columnName = columns.getString(4);
				short columnType = columns.getShort(5);
				int sqlType = columns.getInt(6);
				String typeName = columns.getString(7);
				sqlType = checkForUnsigned(sqlType, typeName);
				if (columnType == DatabaseMetaData.procedureColumnUnknown) {
			return;
				}
				if (columns.getMetaData().getColumnCount() >= 18) {
					int colIndex = columns.getInt(18);
					String col = MessageFormat.format("{0}_{1}", columnType, colIndex);
					if (procInfo.cols.contains(col))
				return;
					procInfo.cols.add(col);
				}
				if (columnName != null) {
					String columnName0 = columnName;
					while (procInfo.names.contains(columnName0))
						columnName0 = columnName + String.valueOf(procInfo.counter++);
					columnName = columnName0;
					procInfo.names.add(columnName);
				}
				BaseColumn record = null;
				int precision = columns.getInt(8);
				int scale = columns.getInt(10);
		if (typeConv != null) {
			if (!typeConv.filterProcedureColumn(columnName, sqlType, typeName, precision, scale))
				return;
		}
		String runtimeType = null;
		if (typeConv != null)
			runtimeType = typeConv.getProcedureColumnRuntimeType(sqlType, typeName, precision, scale);
				if (runtimeType == null)
			runtimeType = getRuntimeType(sqlType, typeName, precision, scale);
		if (runtimeType == null)
					runtimeType = DataTypeManager.DefaultDataTypes.OBJECT;
				switch (columnType) {
				case DatabaseMetaData.procedureColumnResult:
					record = metadataFactory.addProcedureResultSetColumn(columnName, runtimeType, procedure);
					break;
				case DatabaseMetaData.procedureColumnIn:
					record = metadataFactory.addProcedureParameter(columnName, runtimeType, Type.In, procedure);
					break;
				case DatabaseMetaData.procedureColumnInOut:
					record = metadataFactory.addProcedureParameter(columnName, runtimeType, Type.InOut, procedure);
					break;
				case DatabaseMetaData.procedureColumnOut:
					record = metadataFactory.addProcedureParameter(columnName, runtimeType, Type.Out, procedure);
					break;
				case DatabaseMetaData.procedureColumnReturn:
					// Oracle returns null here
					if (columnName == null)
						columnName = "returnValue";
							// Postgres doesn't have procedures with no return value, so it necessarily has return value of void type
					if ("void".equals(typeName))
					return;
					record = metadataFactory.addProcedureParameter(columnName, runtimeType, Type.ReturnValue, procedure);
					break;
				default:
				return;
				}
				record.setNativeType(typeName);
				record.setPrecision(columns.getInt(8));
				record.setLength(columns.getInt(9));
				record.setScale(columns.getInt(10));
				record.setRadix(columns.getInt(11));
		record.setNullType(NullType.values()[columns.getInt(12)]);
					record.setRemark(columns.getString(13));
				if (rsProcColumns >= 14) {
					String def = columns.getString(14);
					if (def != null) {
						if (def.equalsIgnoreCase("null")) { //$NON-NLS-1$
							record.setNullType(NullType.Nullable);
						} else {
							record.setDefaultValue(def);
							//we can't assume that the default is something we can handle
							record.setProperty(BaseColumn.DEFAULT_HANDLING, BaseColumn.OMIT_DEFAULT);
						}
					}
				}
			}

	private String trimProcName(String procedureName) {
		if (procedureName != null)
			procedureName = Pattern.compile(";[0-9]+$", Pattern.CASE_INSENSITIVE).matcher(procedureName).replaceAll("");
		return procedureName;
	}

	private int checkForUnsigned(int sqlType, String typeName) {
		if (widenUnsignedTypes && unsignedTypes.contains(typeName)) {
			switch (sqlType) {
			case Types.TINYINT:
				sqlType = Types.SMALLINT;
				break;
			case Types.SMALLINT:
				sqlType = Types.INTEGER;
				break;
			case Types.INTEGER:
				sqlType = Types.BIGINT;
				break;
			case Types.BIGINT:
				sqlType = Types.DECIMAL;
				break;
			}
		}
		return sqlType;
	}

	public Map<String, TableInfo> getTables(MetadataFactory metadataFactory,
			DatabaseMetaData metadata, Connection conn, String[] tableTypes) throws SQLException, TranslatorException {

		return this.getTables(metadataFactory, metadata, conn, tableTypes, null, null);
	}

	public Map<String, TableInfo> getTables(MetadataFactory metadataFactory,
											DatabaseMetaData metadata, Connection conn, String[] tableTypes, Set<String> fullNames) throws SQLException, TranslatorException {
		return this.getTables(metadataFactory, metadata, conn, tableTypes, null, fullNames);
	}

	public Map<String, TableInfo> getTables(MetadataFactory metadataFactory,
											DatabaseMetaData metadata, Connection conn, String[] tableTypes, String catalogToFilterList, Set<String> fullNames) throws SQLException, TranslatorException {
		LogManager.logDetail(LogConstants.CTX_METADATA, "JDBCMetadataProcessor - Importing tables"); //$NON-NLS-1$
		Map<String, TableInfo> tableMap = null;
		@SuppressWarnings("unchecked")
		Collection<String> schemaPatterns = (Collection<String>) DVCaller.call(41, schemaPattern, metadata, catalog, excludeSchemas);
		if (schemaPatterns == null) {
			schemaPatterns = new ArrayList<String>();
			schemaPatterns.add(schemaPattern);
		}
		if (fullNames == null) {
			fullNames = new HashSet<String>();
		}
		Collection<String> tablePatterns = getTableNamePatterns();
		for (String individualTablePattern : tablePatterns) {
		for (String pattern : schemaPatterns) {
				FullQualifiedParserFilter filter = new FullQualifiedParserFilter(catalog, pattern, individualTablePattern);
				if (!filter.isProcessableFilter()) {
					LogManager.logTrace(LogConstants.CTX_METADATA, JDBCPlugin.Util.gs(JDBCPlugin.Event.DVCORE89151, filter));
					continue;
				}
				String innerCatalog = filter.getInnerCatalog();
				String innerSchemaPattern = filter.getInnerSchemaPattern();
				String innerTableNamePattern = filter.getInnerTableNamePattern();
			PreparedStatement pstmt = null;
			if (!loadMetadataWithJdbc)
					pstmt = (PreparedStatement) DVCaller.callWithThrow(SQLException.class, 142, metadata, innerCatalog, innerSchemaPattern, innerTableNamePattern, tableTypes, this);
			ResultSet tables = null;
			if (pstmt != null)
				tables = pstmt.executeQuery();
			else
					tables = metadata.getTables(innerCatalog, innerSchemaPattern, innerTableNamePattern, tableTypes);

			try {
					if (fetchSize != null && fetchSize > 0)
						tables.setFetchSize(fetchSize);
					int columnCount = tables.getMetaData().getColumnCount();
					if (tableMap == null)
						tableMap = new HashMap<String, TableInfo>(columnCount * 2);
					while (tables.next()) {
						String tableCatalog = tables.getString(1);
						String tableSchema = tables.getString(2);
						String tableName = tables.getString(3);
						String remarks = columnCount > 4 ? tables.getString(5) : "";
						String tableType = columnCount > 3 ? tables.getString(4) : "";
						if (tableCatalog != null) tableCatalog = tableCatalog.trim();
						if (tableSchema != null) tableSchema = tableSchema.trim();
						String fullName = getFullyQualifiedName(tableCatalog, tableSchema, tableName,
								executionFactory != null && executionFactory.forceQuotedIdentifiers());
						if (excludeTables != null && excludeTables.matcher(fullName).matches()) {
							//excludedTables++;
							continue;
						}
					if (skipAddingObjectToMetadata(tableType)) {
						continue;
					}
					LogManager.logTrace(LogConstants.CTX_METADATA, "JDBCMetadataProcessor - Importing table: " + fullName);
						if (fullNames != null && fullNames.contains(fullName))
							continue;
						fullNames.add(fullName);
						Table table = addTable(metadataFactory, tableCatalog, tableSchema,
								tableName, remarks, fullName, tableType, tables);
						if (table == null) {
							continue;
						}
						TableInfo ti = new TableInfo(tableCatalog, tableSchema, tableName, table);
						tableMap.put(fullName, ti);
					if (!(tableMap.get(tableName) instanceof TableInfo)) tableMap.put(tableName, ti);
					}
				} finally {
					tables.close();
					if (pstmt != null)
						pstmt.close();
				}
		}
		}
		if (tableMap == null)
			tableMap = new HashMap<String, TableInfo>();

		getColumns(metadataFactory, metadata, tableMap, conn);
		return tableMap;
	}

	private Collection<String> getTableNamePatterns() {
		String tableNamePatterns = getTableNamePattern();
		if (tableNamePatterns != null && tableNamePatterns.contains("\\\\")) {
			tableNamePatterns = tableNamePatterns.replace("\\\\", "\\");
		}
		Collection<String> tablePatterns = new HashSet<>();
		Collection<String> splitTablePatterns = (Collection<String>) DVCaller.call(257, tableNamePatterns);
		if (splitTablePatterns != null && !splitTablePatterns.isEmpty()) {
			tablePatterns.addAll(splitTablePatterns);
		} else {
			tablePatterns.add(tableNamePatterns);
		}
		return tablePatterns;
	}

	public boolean skipAddingObjectToMetadata(String tableType) {
		return false;
	}

	/**
	 * @throws SQLException
	 */
	protected Table addTable(MetadataFactory metadataFactory,
 	                String tableCatalog, String tableSchema, String tableName,
 	                String remarks, String fullName, String tableType, ResultSet tables) {
		return addTable(metadataFactory, tableCatalog, tableSchema, tableName,
						remarks, fullName, tableType);
	}

	/**
	 *
	 * @param metadataFactory
	 * @param tableCatalog
	 * @param tableSchema
	 * @param tableName
	 * @param remarks
	 * @param fullName
	 * @return
	 */
 	protected Table addTable(MetadataFactory metadataFactory,
 	                String tableCatalog, String tableSchema, String tableName,
 	                String remarks, String fullName, String tableType) {
	    Table table = metadataFactory.addTable(useFullSchemaName?fullName:tableName);
	    table.setNameInSource(getFullyQualifiedName(tableCatalog, tableSchema, tableName, true));
	    table.setSupportsUpdate(true);
		table.setRemark(remarks);
	    if ("VIEW".equals(tableType))
		    table.setTableType(Table.Type.View);
	    return table;
    }

	private void getColumns(MetadataFactory metadataFactory,
			DatabaseMetaData metadata, Map<String, TableInfo> tableMap, Connection conn)
					throws SQLException, TranslatorException {
		LogManager.logDetail(LogConstants.CTX_METADATA, "JDBCMetadataProcessor - Importing columns"); //$NON-NLS-1$
		@SuppressWarnings("unchecked")
		Collection<String> schemaPatterns = (Collection<String>) DVCaller.call(41, schemaPattern, metadata, catalog, excludeSchemas);
		if (schemaPatterns == null) {
			schemaPatterns = new ArrayList<String>();
			schemaPatterns.add(schemaPattern);
		}
		Set<String> fullNames = new HashSet<String>(Math.max(tableMap.size() * 10 + 1, 10));
		Collection<String> tablePatterns = getTableNamePatterns();
		for (String individualTablePattern : tablePatterns) {
		for (String pattern : schemaPatterns) {
			LogManager.logTrace(LogConstants.CTX_METADATA, "JDBCMetadataProcessor - Importing columns for schema pattern: " + pattern);
				FullQualifiedParserFilter filter = new FullQualifiedParserFilter(catalog, pattern, individualTablePattern);
				String innerCatalog = filter.getInnerCatalog();
				String innerSchemaPattern = filter.getInnerSchemaPattern();
				String innerTableNamePattern = filter.getInnerTableNamePattern();
			if (loadColumnsTableByTable) {
					processByTable(metadata, innerSchemaPattern, tableMap, fullNames, metadataFactory, conn, tableTypes);
				continue;
			}
			PreparedStatement pstmt = null;

			if (!loadMetadataWithJdbc)
					pstmt = (PreparedStatement) DVCaller.call(84, metadataFactory, metadata, innerCatalog, innerSchemaPattern, innerTableNamePattern, this, executionFactory);
			ResultSet columns = null;
			if (pstmt != null)
				columns = pstmt.executeQuery();
			try {
				if (columns == null)
						columns = metadata.getColumns(innerCatalog, innerSchemaPattern, innerTableNamePattern, columnNamePattern);
				if (fetchSize != null && fetchSize > 0)
					columns.setFetchSize(fetchSize);
				processColumns(columns, tableMap, fullNames, metadataFactory, conn);
			} catch (SQLException e) {
                if (SqlUtil.isInterruptedException(e))
                    throw e;
                LogManager.logWarning(LogConstants.CTX_METADATA, e, String.format("An exception %s (%s). Columns will be processed table by table.", e.getClass().getName(), e.getMessage())); //$NON-NLS-1$
                processByTable(metadata, pattern, tableMap, fullNames, metadataFactory, conn, tableTypes);
			} finally {
				if (pstmt != null)
					pstmt.close();
				LogManager.logTrace(LogConstants.CTX_METADATA, "JDBCMetadataProcessor - finished columns import for schema pattern: " + pattern);
			}
		}
		}
		if (replaceSpecSymbsInColNames) {
			for (TableInfo tinf : tableMap.values()) {
				final Table table = tinf.table;
				final List<Column> cols = table.getColumns();
				if (cols != null)
					for (Column col : cols) {
						final String origName = col.getName();
						final String colName = Pattern.compile("\\W", 0).matcher(origName).replaceAll("_");
						if (origName.equals(colName))
							continue;
						String colName1 = colName;
						int prefix = 0;
						while (table.getColumnByName(colName1) != null) {
							if (prefix > 10000)
								throw new TranslatorException(TranslatorJdbcPlugin.Util.gs(TranslatorJdbcPlugin.Event.DVCORE94097));
							colName1 = colName + prefix++;
						}
						col.setName(colName1);
					}
				// this is needed for the table instance to reset it's internal column map which uses names as keys (but we probably changed some names)
				table.setColumns(table.getColumns());
			}
		}
	}

    private void processColumns(ResultSet columns, Map<String, TableInfo> tableMap, Set<String> fullNames, MetadataFactory metadataFactory, Connection conn) throws SQLException {
 		try {
		int rsColumns = columns.getMetaData().getColumnCount();
		int initialCapacity = (int) (tableMap.size() / rsColumns) + 1;
		if (initialCapacity < 10)
			initialCapacity = 10;
		while (columns.next()) {
			String tableCatalog = columns.getString(1);
			String tableSchema = columns.getString(2);
			String tableName = columns.getString(3);
			if (tableCatalog != null) tableCatalog = tableCatalog.trim();
			if (tableSchema != null) tableSchema = tableSchema.trim();
				String fullTableName = getFullyQualifiedName(tableCatalog, tableSchema, tableName,
						executionFactory != null && executionFactory.forceQuotedIdentifiers());
			TableInfo tableInfo = tableMap.get(fullTableName);
			if (tableInfo == null) {
				continue;
			}
				//TODO: geometry/geography arrays
				Column c = addColumn(columns, tableInfo, metadataFactory, rsColumns, fullTableName, fullNames, initialCapacity);
				if (c != null) {
					if (TypeFacility.RUNTIME_TYPES.GEOMETRY.equals(c.getJavaType())) {
						String columnName = columns.getString(4);
						getGeometryMetadata(c, conn, tableCatalog, tableSchema, tableName, columnName);
					} else if (TypeFacility.RUNTIME_TYPES.GEOGRAPHY.equals(c.getJavaType())) {
						String columnName = columns.getString(4);
						getGeographyMetadata(c, conn, tableCatalog, tableSchema, tableName, columnName);
					}
				}
			}
		} finally {
		columns.close();
	}
	}

	/**
	 * Add a column to the given table based upon the current row of the columns resultset
	 * @param columns
	 * @param proc
	 * @param metadataFactory
	 * @param rsColumns
	 * @param fullTableName
	 * @param fullNames
	 * @param tableInfo
	 * @param initialCapacity
	 * @return the column added
	 * @throws SQLException
	 */
	protected Column addColumn(ResultSet columns, TableInfo tableInfo, MetadataFactory metadataFactory, int rsColumns, String fullTableName, Set<String> fullNames, int initialCapacity) throws SQLException {

		String columnName = columns.getString(4);
		LogManager.logTrace(LogConstants.CTX_COLUMNS, "JDBCMetadataProcessor - importing column: " + columnName);
		if (columnName == null)
		    return null;
		String fullName = fullTableName + "." + columnName;
		if (fullNames.contains(fullName))
			return null;
		fullNames.add(fullName);
		int type = columns.getInt(5);
		String typeName = columns.getString(6);
		int columnSize = columns.getInt(7);
		int scale = columns.getInt(9);
		String runtimeType = getRuntimeType(type, typeName, columnSize, scale);

		tableInfo.table.setupColumns(initialCapacity);
		final String nameInSource = quoteName(columnName);
		final int radix = columns.getInt(10);
		final NullType nullType = NullType.values()[columns.getInt(11)];
		String remarks = columns.getString(12);
		String defaultValue = columns.getString(13);
		final int charOctetLength = columns.getInt(16);
		if (!typeConv.filterColumn(columnName, type, typeName, columnSize, scale, charOctetLength))
			return null;
		//note that the resultset is already ordered by position, so we can rely on just adding columns in order
		Column column = metadataFactory.addColumn(columnName, runtimeType, tableInfo.table);
		column.setNameInSource(nameInSource);
		column.setPrecision(columnSize);
		column.setLength(columnSize);
		column.setNativeType(typeName);
		column.setScale(scale);
		column.setNativePrecision(columnSize);
		column.setNativeLength(columnSize);
		column.setNativeScale(scale);
		column.setRadix(radix);
		column.setNullType(nullType);
		column.setUpdatable(true);
		column.setRemark(remarks);
		column.setDefaultValue(defaultValue);
		if (defaultValue != null && type == Types.BIT && TypeFacility.RUNTIME_NAMES.BOOLEAN.equals(runtimeType)) {
			//try to determine a usable boolean value
			if(defaultValue.length() == 1) {
				int charIntVal = defaultValue.charAt(0);
				// Set boolean FALse for incoming 0, TRUE for 1
				if(charIntVal==0) {
					column.setDefaultValue(Boolean.FALSE.toString());
				} else if(charIntVal==1) {
					column.setDefaultValue(Boolean.TRUE.toString());
				}
			} else { //SQLServer quotes bit values
				String trimedDefault = defaultValue.trim();
				if (defaultValue.startsWith("(") && defaultValue.endsWith(")")) { //$NON-NLS-1$ //$NON-NLS-2$
					trimedDefault = defaultValue.substring(1, defaultValue.length() - 1);
				}
				column.setDefaultValue(trimedDefault);
			}
		}
		column.setCharOctetLength(charOctetLength);
		if (rsColumns >= 23) {
			column.setAutoIncremented("YES".equalsIgnoreCase(columns.getString(23))); //$NON-NLS-1$
		}
		if (typeConv != null) {
			typeConv.tuneColumnType(column, type, typeName, columnSize, scale);
			column.setDatatype(metadataFactory.getDataTypes().get(column.getRuntimeType()), false);
		}
		if (LogManager.isMessageToBeRecorded(LogConstants.CTX_COLUMNS, MessageLevel.TRACE))
			LogManager.logTrace(LogConstants.CTX_COLUMNS,
					String.format("JDBCMetadataProcessor - importing column: before adding: %s, type: [%d] %s(%d:%d)",
							fullName, type, column.getRuntimeType(), column.getPrecision(), column.getScale()));
		return column;
	}

	public void processByTable(DatabaseMetaData metadata, String pattern, Map<String, TableInfo> tableMap, Set<String> fullNames, MetadataFactory metadataFactory, Connection conn, String[] tableTypes) throws TranslatorException {
		ResultSet columns = null;

		try {
			LogManager.logTrace(LogConstants.CTX_METADATA, "JDBCMetadataProcessor - processByTable");
			ResultSet tables;
				tables = metadata.getTables(catalog, schemaPattern, null, tableTypes);
			if (fetchSize != null && fetchSize > 0)
				tables.setFetchSize(fetchSize);
			while (tables.next()) {
				String tableName = tables.getString("TABLE_NAME");
				LogManager.logTrace(LogConstants.CTX_METADATA, "JDBCMetadataProcessor - processByTable - importing columns for table: " + tableName);
				try {
					columns = metadata.getColumns(catalog, schemaPattern, tableName, columnNamePattern);
					processColumns(columns, tableMap, fullNames, metadataFactory, conn);
				} catch (SQLException e) {
					LogManager.logWarning(LogConstants.CTX_METADATA, String.format("It was not possible to process table %s due to an exception %s (%s). Table will be skipped", tableName, e.getClass().getName(), e.getMessage())); //$NON-NLS-1$
				}
				LogManager.logTrace(LogConstants.CTX_METADATA, "JDBCMetadataProcessor - processByTable - finished columns import for table: " + tableName);
			}

		} catch (SQLException e) {
			throw new TranslatorException(e, TranslatorJdbcPlugin.Util.gs(TranslatorJdbcPlugin.Event.DVCORE94098));
		}
	}

	protected String getRuntimeType(int type, String typeName, int precision, int scale) {
		if (typeConv != null) {
			String runtimeType = typeConv.tuneRuntimeType(type, typeName, precision, scale);
			if (runtimeType != null)
				return runtimeType;
		}
		if (type == Types.BIT && precision > 1) {
			type = Types.BINARY;
		}
		type = checkForUnsigned(type, typeName);
		return TypeFacility.getDataTypeNameFromSQLType(type);
	}

	protected String quoteName(String name) {
		if (quoteNameInSource && quoteString != null) {
			return quoteString + StringUtil.replaceAll(name, quoteString, quoteString + quoteString) + quoteString;
		}
		return name;
	}

	private void getPrimaryKeys(MetadataFactory metadataFactory,
			DatabaseMetaData metadata, Collection<TableInfo> tables)
					throws SQLException {
		LogManager.logDetail(LogConstants.CTX_METADATA, "JDBCMetadataProcessor - Importing primary keys"); //$NON-NLS-1$
		for (TableInfo tableInfo : tables) {
			try (ResultSet pks = metadata.getPrimaryKeys(tableInfo.catalog, tableInfo.schema, tableInfo.name)) {
			if (fetchSize != null && fetchSize > 0)
				pks.setFetchSize(fetchSize);
			TreeMap<Short, String> keyColumns = null;
			String pkName = null;
			while (pks.next()) {
				String columnName = pks.getString(4);
				short seqNum = pks.getShort(5);
				if (keyColumns == null) {
					keyColumns = new TreeMap<Short, String>();
				}
				keyColumns.put(seqNum, columnName);
				if (pkName == null) {
					pkName = pks.getString(6);
					if (pkName == null) {
						pkName = "PK_" + tableInfo.table.getCanonicalName(); //$NON-NLS-1$
					}
				}
			}
			if (keyColumns != null) {
				metadataFactory.addPrimaryKey(pkName, new ArrayList<String>(keyColumns.values()), tableInfo.table);
			}
		}
	}
	}

	private void getForeignKeys(MetadataFactory metadataFactory,
			DatabaseMetaData metadata, Collection<TableInfo> tables, Map<String, TableInfo> tableMap) throws SQLException, TranslatorException {
		LogManager.logDetail(LogConstants.CTX_METADATA, "JDBCMetadataProcessor - Importing foreign keys"); //$NON-NLS-1$
		for (TableInfo tableInfo : tables) {
			try (ResultSet fks = metadata.getImportedKeys(tableInfo.catalog, tableInfo.schema, tableInfo.name)) {
			if (fetchSize != null && fetchSize > 0)
				fks.setFetchSize(fetchSize);
			TreeMap<Short, String> keyColumns = new TreeMap<Short, String>();
			TreeMap<Short, String> referencedKeyColumns = new TreeMap<Short, String>();
			String fkName = null;
			TableInfo pkTable = null;
			short savedSeqNum = Short.MAX_VALUE;
			while (fks.next()) {
				String columnName = fks.getString(8).replaceAll("^\"|\"$", "");
				short seqNum = fks.getShort(9);
				String pkColumnName = fks.getString(4).replaceAll("^\"|\"$", "");

				if (seqNum <= savedSeqNum) {
					if (pkTable != null) {
							KeyRecord record = autoCreateUniqueKeys(autoCreateUniqueConstraints, metadataFactory, fkName, referencedKeyColumns, pkTable.table);
						ForeignKey fk = metadataFactory.addForiegnKey(fkName, new ArrayList<String>(keyColumns.values()), new ArrayList<String>(referencedKeyColumns.values()), pkTable.table.getName(), tableInfo.table);
						if (record != null) {
								fk.setReferenceKey(record);
						}
					}
					keyColumns.clear();
					referencedKeyColumns.clear();
					fkName = null;
					pkTable = null;
				}
				savedSeqNum = seqNum;
				keyColumns.put(seqNum, columnName);
				referencedKeyColumns.put(seqNum, pkColumnName);
				if (fkName == null) {
					String tableCatalog = fks.getString(1);
					String tableSchema = fks.getString(2);
					String tableName = fks.getString(3);
						String fullTableName = getFullyQualifiedName(tableCatalog, tableSchema, tableName.replaceAll("^\"|\"$", ""),
								executionFactory != null && executionFactory.forceQuotedIdentifiers());
					pkTable = tableMap.get(fullTableName);
					if (pkTable == null) {
						//throw new TranslatorException(JDBCPlugin.Util.getString("JDBCMetadataProcessor.cannot_find_primary", fullTableName)); //$NON-NLS-1$
						continue; //just drop the foreign key, the user probably didn't import the other table
					}
					fkName = fks.getString(12);
					if (fkName == null) {
						fkName = "FK_" + tableInfo.table.getCanonicalName(); //$NON-NLS-1$
					}
			}
				}
			if (pkTable != null) {
				KeyRecord record = autoCreateUniqueKeys(autoCreateUniqueConstraints, metadataFactory, fkName, referencedKeyColumns, pkTable.table);
				ForeignKey fk = metadataFactory.addForiegnKey(fkName, new ArrayList<String>(keyColumns.values()), new ArrayList<String>(referencedKeyColumns.values()), pkTable.table.getName(), tableInfo.table);
				if (record != null) {
						fk.setReferenceKey(record);
				}
			}
		}
	}
	}

	private KeyRecord autoCreateUniqueKeys(boolean create, MetadataFactory factory, String name, TreeMap<Short, String> referencedKeyColumns, Table pkTable) throws TranslatorException {
		if (referencedKeyColumns != null && pkTable.getPrimaryKey() == null && pkTable.getUniqueKeys().isEmpty()) {
			factory.addIndex(name + "_unique", false, new ArrayList<String>(referencedKeyColumns.values()), pkTable); //$NON-NLS-1$
		}

		KeyRecord uniqueKey = null;
		if (referencedKeyColumns == null) {
			uniqueKey = pkTable.getPrimaryKey();
		} else {
			for (KeyRecord record : pkTable.getUniqueKeys()) {
				if (keyMatches(new ArrayList<String>(referencedKeyColumns.values()), record)) {
					uniqueKey = record;
					break;
				}
			}
			if (uniqueKey == null && pkTable.getPrimaryKey() != null && keyMatches(new ArrayList<String>(referencedKeyColumns.values()), pkTable.getPrimaryKey())) {
				uniqueKey = pkTable.getPrimaryKey();
			}
		}
		if (uniqueKey == null && create) {
			uniqueKey = factory.addIndex(name + "_unique", false, new ArrayList<String>(referencedKeyColumns.values()), pkTable); //$NON-NLS-1$
		}
		return uniqueKey;
	}

	private boolean keyMatches(List<String> names, KeyRecord record) {
		if (names.size() != record.getColumns().size()) {
			return false;
		}
		for (int i = 0; i < names.size(); i++) {
			if (!names.get(i).equals(record.getColumns().get(i).getName())) {
				return false;
			}
		}
		return true;
	}

	void getIndexes(MetadataFactory metadataFactory,
			DatabaseMetaData metadata, Collection<TableInfo> tables, boolean uniqueOnly) throws SQLException {
		LogManager.logDetail(LogConstants.CTX_METADATA, "JDBCMetadataProcessor - Importing index info"); //$NON-NLS-1$
		if (!loadMetadataWithJdbc)
			if (DVCaller.call(48, metadataFactory, metadata, catalog, schemaPattern, tables, uniqueOnly, importApproximateIndexes, excludeSchemas) != null)
				return;
		for (TableInfo tableInfo : tables) {
			ResultSet indexInfo;
			try {
				indexInfo = metadata.getIndexInfo(tableInfo.catalog, tableInfo.schema, tableInfo.name, uniqueOnly, importApproximateIndexes);
			} catch (SQLException e) {
				LogManager.logWarning(LogConstants.CTX_METADATA, String.format("Not possible to process indexes for table %s due to exception %s (%s).", tableInfo.name, e.getClass().getName(), e.getMessage())); //$NON-NLS-1$
				continue;
			}
			try {
			if (fetchSize != null && fetchSize > 0)
				indexInfo.setFetchSize(fetchSize);
			TreeMap<Short, String> indexColumns = null;
			String indexName = null;
			short savedOrdinalPosition = Short.MAX_VALUE;
			boolean nonUnique = false;
			boolean valid = true;
			while (indexInfo.next()) {
				short type = indexInfo.getShort(7);
				if (type == DatabaseMetaData.tableIndexStatistic) {
					tableInfo.table.setCardinality(indexInfo.getInt(11));
					continue;
				}
				short ordinalPosition = indexInfo.getShort(8);
				if (ordinalPosition <= savedOrdinalPosition) {
					if (valid && indexColumns != null && (!uniqueOnly || !nonUnique)) {
						metadataFactory.addIndex(indexName, nonUnique, new ArrayList<String>(indexColumns.values()), tableInfo.table);
					}
					indexColumns = new TreeMap<Short, String>();
					indexName = null;
					valid = true;
				}
				savedOrdinalPosition = ordinalPosition;
				String columnName = indexInfo.getString(9);
				if (valid && columnName == null || tableInfo.table.getColumnByName(columnName) == null) {
					LogManager.logDetail(LogConstants.CTX_METADATA, "Skipping the import of non-simple index", indexInfo.getString(6)); //$NON-NLS-1$
					valid = false;
				}
				nonUnique = indexInfo.getBoolean(4);
				indexColumns.put(ordinalPosition, columnName);
				if (indexName == null) {
					indexName = indexInfo.getString(6);
					if (indexName == null) {
						indexName = "NDX_" + tableInfo.table.getCanonicalName(); //$NON-NLS-1$
					}
				}
			}
			if (valid && indexColumns != null && (!uniqueOnly || !nonUnique)) {
				metadataFactory.addIndex(indexName, nonUnique, new ArrayList<String>(indexColumns.values()), tableInfo.table);
			}
			} finally {
			indexInfo.close();
		}
	}
	}

	protected String getFullyQualifiedName(String catalogName, String schemaName, String objectName, boolean quoted) {
		String fullName = (quoted?quoteName(objectName):objectName);
		if (useQualifiedName) {
		if (schemaName != null && schemaName.length() > 0) {
			fullName = (quoted?quoteName(schemaName):schemaName) + AbstractMetadataRecord.NAME_DELIM_CHAR + fullName;
		}
		if (useCatalogName && catalogName != null && catalogName.length() > 0) {
			fullName = (quoted?quoteName(catalogName):catalogName) + AbstractMetadataRecord.NAME_DELIM_CHAR + fullName;
		}
		}
		return fullName;
	}

	public void setTableNamePattern(String tableNamePattern) {
		this.tableNamePattern = tableNamePattern;
	}

	@TranslatorProperty (display="Table Types", category=PropertyType.IMPORT, description="Comma separated list - without spaces - of imported table types. null returns all types")
	public String[] getTableTypes() {
		return tableTypes;
	}

	public void setTableTypes(String[] tableTypes) {
		this.tableTypes = tableTypes;
	}

	public void setUseFullSchemaName(boolean useFullSchemaName) {
		this.useFullSchemaName = useFullSchemaName;
	}

	public void setProcedureNamePattern(String procedureNamePattern) {
		this.procedureNamePattern = procedureNamePattern;
	}

	public void setImportIndexes(boolean importIndexes) {
		this.importIndexes = importIndexes;
	}

	public void setImportKeys(boolean importKeys) {
		this.importKeys = importKeys;
	}

	public void setImportForeignKeys(boolean importForeignKeys) {
		this.importForeignKeys = importForeignKeys;
	}

	protected void setColumnNamePattern(String columnNamePattern) {
		this.columnNamePattern = columnNamePattern;
	}

	public void setImportProcedures(boolean importProcedures) {
		this.importProcedures = importProcedures;
	}

	public void setImportApproximateIndexes(boolean importApproximateIndexes) {
		this.importApproximateIndexes = importApproximateIndexes;
	}

	public void setWidenUnsignedTypes(boolean widenUnsignedTypes) {
		this.widenUnsignedTypes = widenUnsignedTypes;
	}

	public void setQuoteNameInSource(boolean quoteIdentifiers) {
		this.quoteNameInSource = quoteIdentifiers;
	}

	// Importer specific properties
	public void setCatalog(String catalog) {
		String cleanedCatalog = (String) DVCaller.call(260, catalog);
		this.catalog = cleanedCatalog != null ? cleanedCatalog : catalog;
	}

	public void setSchemaPattern(String schema) {
		this.schemaPattern = schema;
	}

	public void setLoadMetadataWithJdbc(boolean loadMetadataWithJdbc) {
		this.loadMetadataWithJdbc = loadMetadataWithJdbc;
	}

	public void setFetchSize(Integer fetchSize) {
		this.fetchSize = fetchSize;
	}

	public void setExcludeSchemas(String excludeSchemas) {
		this.excludeSchemas = excludeSchemas;
	}

	public void setUseProcedureSpecificName(boolean useProcedureSpecificName) {
		this.useProcedureSpecificName = useProcedureSpecificName;
	}

	public void setUseCatalogName(boolean useCatalog) {
		this.useCatalogName = useCatalog;
	}

	public void setAutoCreateUniqueConstraints(
			boolean autoCreateUniqueConstraints) {
		this.autoCreateUniqueConstraints = autoCreateUniqueConstraints;
	}

	public boolean isLoadAllOracleColumnComments() {
		return loadAllOracleColumnComments;
	}

	public void setLoadAllOracleColumnComments(boolean loadAllOracleColumnComments) {
		this.loadAllOracleColumnComments = loadAllOracleColumnComments;
	}

	public boolean isLoadAllOracleTableComments() {
		return loadAllOracleTableComments;
	}

	public void setLoadAllOracleTableComments(boolean loadAllOracleTableComments) {
		this.loadAllOracleTableComments = loadAllOracleTableComments;
	}

	public void setExcludeProcedures(String excludeProcedures) {
		this.excludeProcedures = Pattern.compile(excludeProcedures, Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
	}

	public void setExcludeTables(String excludeTables) {
		this.excludeTables = Pattern.compile(excludeTables, Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
	}

	public void setUseQualifiedName(boolean useQualifiedName) {
		this.useQualifiedName = useQualifiedName;
	}

	public void setReplaceSpecSymbsInColNames(boolean replaceSpecSymbsInColNames) {
		this.replaceSpecSymbsInColNames = replaceSpecSymbsInColNames;
	}

	public void setDefaultSchema(String defaultSchema) {
		this.defaultSchema = defaultSchema;
	}

	public String getDefaultSchema() {
		return defaultSchema;
	}

	public void setLoadColumnsTableByTable(boolean loadColumnsTableByTable) {
		this.loadColumnsTableByTable = loadColumnsTableByTable;
	}

	@TranslatorProperty(display="Import Procedures", category=PropertyType.IMPORT, description="true to import procedures and procedure columns - Note that it is not always possible to import procedure result set columns due to database limitations. It is also not currently possible to import overloaded procedures.")
 	public boolean isImportProcedures() {
		return importProcedures;
	}

	@TranslatorProperty(display="Import Keys", category=PropertyType.IMPORT, description="true to import primary and foreign keys - NOTE foreign keys to tables that are not imported will be ignored")
 	public boolean isImportKeys() {
		return importKeys;
	}

	/*@TranslatorProperty(display="Import Foreign Key", category=PropertyType.IMPORT, description="true to import foreign keys")
 	public boolean isImportForeignKeys() {
		return importForeignKeys;
	}*/

	@TranslatorProperty(display="Import Indexes", category=PropertyType.IMPORT, description="true to import index/unique key/cardinality information")
 	public boolean isImportIndexes() {
		return importIndexes;
	}

	@TranslatorProperty(display="Procedure Name Pattern", category=PropertyType.IMPORT, description=" a procedure name pattern; must match the procedure name as it is stored in the database")
 	public String getProcedureNamePattern() {
		return procedureNamePattern;
	}

	@TranslatorProperty(display="Use Full Schema Name", category=PropertyType.IMPORT, description="When false, directs the importer to drop the source catalog/schema from the Teiid object name, so that the Teiid fully qualified name will be in the form of <model name>.<table name> - Note: when false this may lead to objects with duplicate names when importing from multiple schemas, which results in an exception.  This option does not affect the name in source property.")
 	public boolean isUseFullSchemaName() {
		return useFullSchemaName;
	}

	@TranslatorProperty(display="Table Name Pattern", category=PropertyType.IMPORT, description=" a table name pattern; must match the table name as it is stored in the database")
 	public String getTableNamePattern() {
		return tableNamePattern;
	}

	@TranslatorProperty(display="catalog", category=PropertyType.IMPORT, description="a catalog name; must match the catalog name as it is stored in the database; \"\" retrieves those without a catalog; null means that the catalog name should not be used to narrow the search")
 	public String getCatalog() {
		return catalog;
	}

	@TranslatorProperty(display="Schema Pattern", category=PropertyType.IMPORT, description="a schema name pattern; must match the schema name as it is stored in the database; \"\" retrieves those without a schema; null means that the schema name should not be used to narrow the search")
 	public String getSchemaPattern() {
		return schemaPattern;
	}

	@TranslatorProperty(display="Import Approximate Indexes", category=PropertyType.IMPORT, description="true to import approximate index information. when true, result is allowed to reflect approximate or out of data values; when false, results are requested to be accurate")
 	public boolean isImportApproximateIndexes() {
		return importApproximateIndexes;
	}

	/*@TranslatorProperty(display="Widen unSigned Types", category=PropertyType.IMPORT, description="true to convert unsigned types to the next widest type. For example SQL Server reports tinyint as an unsigned type. With this option enabled, tinyint would be imported as a short instead of a byte.")
 	public boolean isWidenUnsingedTypes() {
		return widenUnsingedTypes;
	}*/

	@TranslatorProperty(display="Quote NameInSource", category=PropertyType.IMPORT, description="false will override the default and direct Teiid to create source queries using unquoted identifiers.")
 	public boolean isQuoteNameInSource() {
		return quoteNameInSource;
	}

	@TranslatorProperty(display="Use Procedure Specific Name", category=PropertyType.IMPORT, description="true will allow the import of overloaded procedures (which will normally result in a duplicate procedure error) by using the unique procedure specific name as the Teiid name. This option will only work with JDBC 4.0 compatible drivers that report specific names.")
 	public boolean isUseProcedureSpecificName() {
		return useProcedureSpecificName;
	}

	@TranslatorProperty(display="Use Catalog Name", category=PropertyType.IMPORT, description="true will use any non-null/non-empty catalog name as part of the name in source, e.g. \"catalog\".\"schema\".\"table\".\"column\", and in the Teiid runtime name if useFullSchemaName is also true. false will not use the catalog name in either the name in source or the Teiid runtime name. Should be set to false for sources that do not fully support a catalog concept, but return a non-null catalog name in their metadata - such as HSQL.")
 	public boolean isUseCatalogName() {
		return useCatalogName;
	}

	@TranslatorProperty(display="Auto Create Unique Constraints", category=PropertyType.IMPORT, description="true to create a unique constraint if one is not found for a foreign keys")
 	public boolean isAutoCreateUniqueConstraints() {
		return autoCreateUniqueConstraints;
	}

	/*@TranslatorProperty(display="use Qualified Name", category=PropertyType.IMPORT, description="true will use name qualification for both the Teiid name and name in source as dictated by the useCatalogName and useFullSchemaName properties.  Set to false to disable all qualification for both the Teiid name and the name in source, which effectively ignores the useCatalogName and useFullSchemaName properties.  Note: when false this may lead to objects with duplicate names when importing from multiple schemas, which results in an exception.")
 	public boolean isUseQualifiedName() {
		return useQualifiedName;
	}

	@TranslatorProperty(display="Use Any Index Cardinality", category=PropertyType.IMPORT, description="true will use the maximum cardinality returned from DatabaseMetaData.getIndexInfo. importKeys or importIndexes needs to be enabled for this setting to have an effect. This allows for better stats gathering from sources that don't support returning a statistical index.")
 	public boolean isUseAnyIndexCardinality() {
		return useAnyIndexCardinality;
	}

	@TranslatorProperty(display="Import Statistics", category=PropertyType.IMPORT, description="true will use database dependent logic to determine the cardinality if none is determined. Not yet supported by all database types - currently only supported by Oracle and MySQL.")
 	public boolean isImportStatistics() {
		return importStatistics;
	}

	@TranslatorProperty(display="Column Name Pattern", category=PropertyType.IMPORT, description="a column name pattern; must match the column name as it is stored in the database. Used to import columns of tables")
 	public String getColumnNamePattern() {
		return columnNamePattern;
	}*/

	@TranslatorProperty(display="Exclude Tables", category=PropertyType.IMPORT, description="A case-insensitive regular expression that when matched against a fully qualified Teiid table name will exclude it from import.  Applied after table names are retrieved.  Use a negative look-ahead (?!<inclusion pattern>).* to act as an inclusion filter")
 	public String getExcludeTables() {
		return this.excludeTables.pattern();
	}

	@TranslatorProperty(display="Exclude Procedures", category=PropertyType.IMPORT, description="A case-insensitive regular expression that when matched against a fully qualified Teiid procedure name will exclude it from import.  Applied after procedure names are retrieved.  Use a negative look-ahead (?!<inclusion pattern>).* to act as an inclusion filter")
 	public String getExcludeProcedures() {
		return this.excludeProcedures.pattern();
	}

	@TranslatorProperty(display="Loads source system functions (system UDFs)", category=PropertyType.IMPORT, description="True, if this translator loads source system functions (system UDFs)", advanced=true)
	public boolean isLoadSourceSystemFunctions() {
		return loadSourceSystemFunctions;
	}

	public void setLoadSourceSystemFunctions(boolean systemFunctions) {
		this.loadSourceSystemFunctions = systemFunctions;
	}

	/**
	 * Get the geospatial metadata for the column, including srid, type, and dimensionality
	 * @param c
	 * @param conn
	 * @param tableCatalog
	 * @param tableSchema
	 * @param tableName
	 * @param columnName
	 */
	protected void getGeometryMetadata(Column c, Connection conn,
	                                   String tableCatalog, String tableSchema, String tableName,
	                                   String columnName) {
		getGeospatialMetadata(c, conn, tableCatalog, tableSchema, tableName,
				columnName, GeometryType.class);
	}

	/**
	 * Get the geospatial metadata for the column, including srid, type, and dimensionality
	 * @param c
	 * @param conn
	 * @param tableCatalog
	 * @param tableSchema
	 * @param tableName
	 * @param columnName
	 */
	protected void getGeographyMetadata(Column c, Connection conn,
	                                    String tableCatalog, String tableSchema, String tableName,
	                                    String columnName) {
		getGeospatialMetadata(c, conn, tableCatalog, tableSchema, tableName,
				columnName, GeographyType.class);
	}

	protected void getGeospatialMetadata(Column c, Connection conn,
	                                     String tableCatalog, String tableSchema, String tableName,
	                                     String columnName, Class<? extends AbstractGeospatialType> clazz) {
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			if (tableCatalog == null) {
				tableCatalog = conn.getCatalog();
			}
			String viewName = clazz== GeometryType.class?getGeometryMetadataTableName():getGeographyMetadataTableName();
			if (viewName == null) {
				return;
			}
			ps = conn.prepareStatement("select coord_dimension, srid, type from " + viewName //$NON-NLS-1$
					+ " where f_table_catalog=? and f_table_schema=? and f_table_name=? and f_geometry_column=?"); //$NON-NLS-1$
			ps.setString(1, tableCatalog);
			ps.setString(2, tableSchema);
			ps.setString(3, tableName);
			ps.setString(4, columnName);
			rs = ps.executeQuery();
			if (rs.next()) {
				c.setProperty(MetadataFactory.SPATIAL_URI + "coord_dimension", rs.getString(1)); //$NON-NLS-1$
				c.setProperty(MetadataFactory.SPATIAL_URI + "srid", rs.getString(2)); //$NON-NLS-1$
				c.setProperty(MetadataFactory.SPATIAL_URI + "type", rs.getString(3)); //$NON-NLS-1$
			}
		} catch (SQLException e) {
			LogManager.logDetail(LogConstants.CTX_CONNECTOR, e, "Could not get geometry metadata for column", tableSchema, tableName, columnName); //$NON-NLS-1$
		} finally {
			if (rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
				}
			}
			if (ps != null) {
				try {
					ps.close();
				} catch (SQLException e) {
				}
			}
		}
	}

	/**
	 * Return the name of the metadata table or view for geography metadata
	 * that conforms to the simple features specification or null if none exists
	 * @return
	 */
	protected String getGeographyMetadataTableName() {
		return null;
	}

	/**
	 * Return the name of the metadata table or view for geometry metadata
	 * that conforms to the simple features specification or null if none exists
	 * @return
	 */
	protected String getGeometryMetadataTableName() {
		return null;
	}

	@SuppressWarnings("rawtypes")
	protected JDBCExecutionFactory executionFactory;

	public void setExecutionFactory(JDBCExecutionFactory executionFactory) {
		this.executionFactory = executionFactory;
	}

	public void setQuoteString(String quoteString) {
		this.quoteString = quoteString;
	}
}
