using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Data.OracleClient; //using Devart.Data.Oracle; namespace Patcher.DB { class OracleDBTraits : IDBTraits { public static readonly IDBTraits instance = new OracleDBTraits(); protected OracleDBTraits() { } private static readonly Regex ALPHANUMERIC = new Regex("^[a-zA-Z]\\w*$", RegexOptions.Compiled | RegexOptions.Singleline); private static string _EscapeName(string name) { if(!ALPHANUMERIC.IsMatch(name)) throw new ApplicationException("Name should contain only alphanumeric characters"); return string.Format("\"{0}\"", name); } string IDBTraits.EscapeName(string name) { return _EscapeName(name); } DbConnection IDBTraits.CreateConnection(string connectionString) { #pragma warning disable 0618 OracleConnection connection = new OracleConnection(connectionString); #pragma warning restore 0618 connection.Open(); return connection; } string IDBTraits.MarkParam(string paramName) { if(!ALPHANUMERIC.IsMatch(paramName)) throw new ApplicationException("Name should contain only alphanumeric characters"); return ":" + paramName; } string IDBTraits.ParamName(string paramName) { if(!ALPHANUMERIC.IsMatch(paramName)) throw new ApplicationException("Name should contain only alphanumeric characters"); return paramName; } private static void AddParam(DbCommand command, string name, DbType type, object value) { var param = command.CreateParameter(); param.ParameterName = name; param.DbType = type; param.Value = value; command.Parameters.Add(param); } private static bool ParseBoolString(string value) { switch(value.ToLower()) { case "n": return false; case "y": return true; default: throw new ApplicationException(string.Format("Unknown value {0}", value)); } } private static T CastResult(object value) where T : class { if(DBNull.Value.Equals(value)) { return null; } else { return (T)value; } } private static T? CastScalarResult(object value) where T : struct { if(DBNull.Value.Equals(value)) { return null; } else { return (T)value; } } private static readonly SQLQueryManager _SQLQueryManager = new SQLQueryManager(_EscapeName); private static string[] GetPackage(Func commandCreator, string package, string type) { using(DbCommand command = commandCreator()) { command.CommandText = "SELECT LINE, TEXT FROM USER_SOURCE WHERE TYPE = :ptype AND NAME = :pname order by line"; AddParam(command, "ptype", DbType.String, type); AddParam(command, "pname", DbType.String, package); List lines = new List(); using(DbDataReader reader = command.ExecuteReader()) { while(reader.Read()) { lines.Add(reader.GetValue(reader.GetOrdinal("TEXT")).ToString()); } } return lines.ToArray(); } } private static readonly Regex PROCEDURE_BODY_HEADER_REGEX = new Regex( "^procedure" + "\\s+" + "(?\\w+)" + "\\s*" + "\\(" + "(?" + "(\\w+\\s+(in|out)\\s+\\w+)" + "(\\,\\s*\\w+\\s+(in|out)\\s+\\w+)*" + ")?" + "\\)$", RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase | RegexOptions.Compiled ); private static readonly Regex PROCEDURE_BODY_FOOTER_REGEX = new Regex( "^end" + "\\s+" + "(?\\w+)" + ";$", RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase | RegexOptions.Compiled ); private static readonly Dictionary DIRECTIONS = new Dictionary { { StoredProcedureReference.ParameterDirection.In, "in" }, { StoredProcedureReference.ParameterDirection.Out, "out" }, }; private struct ProcedureBodyPosition { public int header; public int begin; public int end; } private class ProcedureNotFoundException : FormattableException { public ProcedureNotFoundException(string message, params object[] args) : base(message, args) { } } private class ViewNotFoundException : FormattableException { public ViewNotFoundException(string message, params object[] args) : base(message, args) { } } private static int FindProcedureHeader(string[] package, StoredProcedureReference procedure) { for(int i=0; i commandCreator, StoredProcedureReference procedure) { string[] package = GetPackage(commandCreator, procedure.packageName, "PACKAGE BODY"); var position = FindProcedureBodyPosition(package, procedure); StringBuilder declarationsBuilder = new StringBuilder(); for(int i=position.header+2; i commandCreator, StoredProcedureReference procedure, StoredProcedureBody newBody) { string[] package = GetPackage(commandCreator, procedure.packageName, "PACKAGE BODY"); var position = FindProcedureBodyPosition(package, procedure); StringBuilder packageBuilder = new StringBuilder(); packageBuilder.AppendLine("create or replace"); for(int i=0; i commandCreator, StoredProcedureReference procedure) { { string[] packageSpec = GetPackage(commandCreator, procedure.packageName, "PACKAGE"); int position = FindProcedureHeader(packageSpec, procedure); StringBuilder packageSpecBuilder = new StringBuilder(); packageSpecBuilder.AppendLine("create or replace"); for(int i=0; i commandCreator, string viewName, string body) { using(DbCommand command = commandCreator()) { command.CommandText = string.Format("CREATE VIEW {0} as {1}", _EscapeName(viewName), body); Console.WriteLine(command.CommandText); command.ExecuteNonQuery(); } } void IDBTraits.RemoveView(Func commandCreator, string viewName) { using(DbCommand command = commandCreator()) { command.CommandText = string.Format("DROP VIEW {0}", _EscapeName(viewName)); Console.WriteLine(command.CommandText); command.ExecuteNonQuery(); } } void IDBTraits.CreateStoredProcedure(Func commandCreator, StoredProcedureReference procedure, StoredProcedureBody body) { string packageEnd = string.Format("end {0};", procedure.packageName); string packageEndMarker = packageEnd.ToLower(); { string[] package = GetPackage(commandCreator, procedure.packageName, "PACKAGE BODY"); try { var position = FindProcedureBodyPosition(package, procedure); throw new ApplicationException(String.Format("Procedure {0} body is already declared", procedure.procedureName)); } catch(ProcedureNotFoundException) { } StringBuilder packageBuilder = new StringBuilder(); packageBuilder.AppendLine("create or replace"); for(int i=0; i i+1) { throw new ApplicationException("Unexpected package end"); } break; } packageBuilder.Append(package[i]); } packageBuilder.Append(FormatStoredProcedure(procedure, body)); packageBuilder.AppendLine(packageEnd); using(DbCommand command = commandCreator()) { command.CommandText = packageBuilder.ToString(); /*Console.WriteLine(); Console.WriteLine("=====PACKAGE BODY====="); Console.WriteLine(command.CommandText); Console.WriteLine("=====END PACKAGE BODY=====");*/ command.ExecuteNonQuery(); } } { string[] packageSpec = GetPackage(commandCreator, procedure.packageName, "PACKAGE"); try { var position = FindProcedureHeader(packageSpec, procedure); throw new ApplicationException(String.Format("Procedure {0} is already declared", procedure.procedureName)); } catch(ProcedureNotFoundException) { } StringBuilder packageSpecBuilder = new StringBuilder(); packageSpecBuilder.AppendLine("create or replace"); for(int i=0; i i+1) { throw new ApplicationException("Unexpected package end"); } break; } packageSpecBuilder.Append(packageSpec[i]); } packageSpecBuilder.Append(FormatStoredProcedureHeader(procedure)); packageSpecBuilder.AppendLine(";"); packageSpecBuilder.AppendLine(packageEnd); using(DbCommand command = commandCreator()) { command.CommandText = packageSpecBuilder.ToString(); /*Console.WriteLine(); Console.WriteLine("=====PACKAGE SPEC====="); Console.WriteLine(command.CommandText); Console.WriteLine("=====END PACKAGE SPEC=====");*/ command.ExecuteNonQuery(); } } } string IDBTraits.GetViewBody(Func commandCreator, string name) { using(DbCommand command = commandCreator()) { command.CommandText = "SELECT TEXT FROM USER_VIEWS WHERE VIEW_NAME = :pname"; AddParam(command, "pname", DbType.String, name); using(DbDataReader reader = command.ExecuteReader()) { if(!reader.Read()) { throw new ViewNotFoundException("View {0} not found", name); } return reader.GetValue("TEXT").ToString(); } } } ColumnOptions IDBTraits.GetColumnOptions(Func commandCreator, ColumnReference column) { using(DbCommand command = commandCreator()) { command.CommandText = "select * from user_tab_columns where table_name = :ptable and column_name = :pcolumn"; AddParam(command, "ptable", DbType.String, column.tableName); AddParam(command, "pcolumn", DbType.String, column.columnName); using(DbDataReader reader = command.ExecuteReader()) { if(!reader.Read()) { throw new ApplicationException("Column not found"); } string dataType = (string)reader.GetValue("DATA_TYPE"); decimal? dataLength = CastScalarResult(reader.GetValue("DATA_LENGTH")); decimal? dataPrecision = CastScalarResult(reader.GetValue("DATA_PRECISION")); decimal? dataScale = CastScalarResult(reader.GetValue("DATA_SCALE")); string sizeSpecifier = null; if(dataPrecision.HasValue) { if(dataScale.HasValue) { sizeSpecifier = string.Format("{0}, {1}", dataPrecision, dataScale); } else { sizeSpecifier = dataPrecision.ToString(); } } else if(dataLength.HasValue) { sizeSpecifier = dataLength.ToString(); } else { throw new ApplicationException("Field without a length"); } string defaultValue = CastResult(reader.GetValue(reader.GetOrdinal("DATA_DEFAULT"))); return new ColumnOptions( string.Format("{0}({1})", dataType, sizeSpecifier), defaultValue != null ? defaultValue.Trim() : null, !ParseBoolString((string)reader.GetValue(reader.GetOrdinal("NULLABLE"))) ); } } } void IDBTraits.RemoveColumn(Func commandCreator, ColumnReference column) { using(DbCommand command = commandCreator()) { command.CommandText = _SQLQueryManager.RemoveColumn(column); command.ExecuteNonQuery(); } } void IDBTraits.CreateColumn(Func commandCreator, ColumnDescription description) { using(DbCommand command = commandCreator()) { command.CommandText = _SQLQueryManager.CreateColumn(description); command.ExecuteNonQuery(); } } void IDBTraits.ModifyColumn(Func commandCreator, ColumnDescription description) { using(DbCommand command = commandCreator()) { command.CommandText = _SQLQueryManager.ModifyColumnOracleStyle(description); Console.WriteLine(); Console.WriteLine(command.CommandText); command.ExecuteNonQuery(); } } private static string GetStringRepresentation(ForeignKeyConstraint.ReferentialAction action) { switch(action) { case ForeignKeyConstraint.ReferentialAction.NoAction: return "NO ACTION"; case ForeignKeyConstraint.ReferentialAction.Cascade: return "CASCADE"; case ForeignKeyConstraint.ReferentialAction.SetNull: return "SET NULL"; case ForeignKeyConstraint.ReferentialAction.SetDefault: return "SET DEFAULT"; default: throw new ApplicationException("Unknown referential action"); } } private void CheckConstraint(Func commandCreator, ForeignKeyConstraint constraint) { string referenced; using(DbCommand command = commandCreator()) { command.CommandText = "SELECT * FROM USER_CONSTRAINTS WHERE TABLE_NAME = :ptable and CONSTRAINT_NAME = :pconstraint"; AddParam(command, "ptable", DbType.String, constraint.table); AddParam(command, "pconstraint", DbType.String, constraint.name); using(var reader = command.ExecuteReader()) { if(!reader.Read()) { throw new FormattableException("Constraint {0}.{1} not found", constraint.table, constraint.name); } if(reader.GetValue("CONSTRAINT_TYPE").ToString() != "R") { throw new FormattableException("Constraint {0}.{1} wrong type: expected {2}, got {3}", constraint.table, constraint.name, "R", reader.GetValue("CONSTRAINT_TYPE")); } if(reader.GetValue("DELETE_RULE").ToString() != GetStringRepresentation(constraint.onDelete)) { throw new FormattableException("Constraint {0}.{1} wrong on delete action: expected {2}, got {3}", constraint.table, constraint.name, GetStringRepresentation(constraint.onDelete), reader.GetValue("DELETE_RULE")); } referenced = reader.GetValue("R_CONSTRAINT_NAME").ToString(); } } using(DbCommand command = commandCreator()) { command.CommandText = "SELECT * FROM USER_CONSTRAINTS WHERE CONSTRAINT_NAME = :pconstraint AND CONSTRAINT_TYPE = :ptype"; AddParam(command, "pconstraint", DbType.String, referenced); AddParam(command, "ptype", DbType.String, "P"); using(var reader = command.ExecuteReader()) { reader.Read(); if(reader.GetValue("TABLE_NAME").ToString() != constraint.referencedTable) { throw new FormattableException("Constraint {0}.{1} wrong referenced table: expected {2}, got {3}", constraint.table, constraint.name, constraint.referencedTable, reader.GetValue("TABLE_NAME")); } } } using(DbCommand command = commandCreator()) { command.CommandText = "SELECT * FROM USER_CONS_COLUMNS WHERE TABLE_NAME = :ptable AND CONSTRAINT_NAME = :pconstraint"; AddParam(command, "ptable", DbType.String, constraint.table); AddParam(command, "pconstraint", DbType.String, constraint.name); using(var reader = command.ExecuteReader()) { reader.Read(); if(reader.GetValue("COLUMN_NAME").ToString() != constraint.column) { throw new FormattableException("Constraint {0}.{1} wrong column: expected {2}, got {3}", constraint.table, constraint.name, constraint.column, reader.GetValue("COLUMN_NAME")); } } } } private void CheckConstraint(Func commandCreator, UniqueConstraint constraint) { using(DbCommand command = commandCreator()) { command.CommandText = "SELECT * FROM USER_CONSTRAINTS WHERE TABLE_NAME = :ptable and CONSTRAINT_NAME = :pconstraint"; AddParam(command, "ptable", DbType.String, constraint.table); AddParam(command, "pconstraint", DbType.String, constraint.name); using(var reader = command.ExecuteReader()) { if(!reader.Read()) { throw new FormattableException("Constraint {0}.{1} not found", constraint.table, constraint.name); } if(reader.GetValue("CONSTRAINT_TYPE").ToString() != "U") { throw new FormattableException("Constraint {0}.{1} wrong type: expected {2}, got {3}", constraint.table, constraint.name, "U", reader.GetValue("CONSTRAINT_TYPE")); } } } using(DbCommand command = commandCreator()) { command.CommandText = "SELECT * FROM USER_CONS_COLUMNS WHERE TABLE_NAME = :ptable AND CONSTRAINT_NAME = :pconstraint"; AddParam(command, "ptable", DbType.String, constraint.table); AddParam(command, "pconstraint", DbType.String, constraint.name); using(var reader = command.ExecuteReader()) { HashSet dbColumns = new HashSet(); while(reader.Read()) { dbColumns.Add(reader.GetValue("COLUMN_NAME").ToString()); } if(!dbColumns.IsSubsetOf(constraint.columns)) { throw new FormattableException("Some columns are not mentioned in constraint definition: {0}", string.Join(",", dbColumns.Except(constraint.columns).ToArray())); } if(!dbColumns.IsSupersetOf(constraint.columns)) { throw new FormattableException("Some columns are missed in DB: {0}", string.Join(",", constraint.columns.Except(dbColumns).ToArray())); } } } } private void CheckConstraint(Func commandCreator, CheckConstraint constraint) { using(DbCommand command = commandCreator()) { command.CommandText = "SELECT * FROM USER_CONSTRAINTS WHERE TABLE_NAME = :ptable and CONSTRAINT_NAME = :pconstraint"; AddParam(command, "ptable", DbType.String, constraint.table); AddParam(command, "pconstraint", DbType.String, constraint.name); using(var reader = command.ExecuteReader()) { if(!reader.Read()) { throw new FormattableException("Constraint {0}.{1} not found", constraint.table, constraint.name); } if(reader.GetValue("CONSTRAINT_TYPE").ToString() != "C") { throw new FormattableException("Constraint {0}.{1} wrong type: expected {2}, got {3}", constraint.table, constraint.name, "C", reader.GetValue("CONSTRAINT_TYPE")); } if(reader.GetValue("SEARCH_CONDITION").ToString() != constraint.condition) { throw new FormattableException("Constraint {0}.{1} wrong condition: expected {2}, got {3}", constraint.table, constraint.name, constraint.condition, reader.GetValue("SEARCH_CONDITION")); } } } } private void CheckConstraint(Func commandCreator, AbstractConstraint constraint) { constraint.Accept(fkc => CheckConstraint(commandCreator, fkc), uc => CheckConstraint(commandCreator, uc), cc => CheckConstraint(commandCreator, cc)); } public void RemoveConstraint(Func commandCreator, AbstractConstraint constraint) { CheckConstraint(commandCreator, constraint); using(DbCommand command = commandCreator()) { command.CommandText = _SQLQueryManager.DropConstraint(constraint); Console.WriteLine(); Console.WriteLine(command.CommandText); command.ExecuteNonQuery(); } } public void CreateConstraint(Func commandCreator, AbstractConstraint constraint) { using(DbCommand command = commandCreator()) { command.CommandText = _SQLQueryManager.CreateConstraint(constraint); Console.WriteLine(); Console.WriteLine(command.CommandText); command.ExecuteNonQuery(); } } public void CreateTable(Func commandCreator, TableDescription table) { using(DbCommand command = commandCreator()) { command.CommandText = _SQLQueryManager.CreateTable(table); Console.WriteLine(); Console.WriteLine(command.CommandText); command.ExecuteNonQuery(); } } private void CheckTable(Func commandCreator, TableDescription table) { HashSet columns = new HashSet(from column in table.columns select column.column.columnName); columns.Add(table.primaryKey.column.columnName); using(DbCommand command = commandCreator()) { command.CommandText = "SELECT * FROM user_tab_columns WHERE TABLE_NAME = :ptable"; AddParam(command, "ptable", DbType.String, table.table); using(var reader = command.ExecuteReader()) { HashSet dbColumns = new HashSet(); while(reader.Read()) { dbColumns.Add(reader.GetValue("COLUMN_NAME").ToString()); } if(!dbColumns.IsSubsetOf(columns)) { throw new FormattableException("Some columns are not mentioned in table definition: {0}", string.Join(",", dbColumns.Except(columns).ToArray())); } if(!dbColumns.IsSupersetOf(columns)) { throw new FormattableException("Some columns are missed in DB: {0}", string.Join(",", columns.Except(dbColumns).ToArray())); } } } var options = (this as IDBTraits).GetColumnOptions(commandCreator, table.primaryKey.column); Console.WriteLine(); Console.WriteLine("'{0}' vs '{1}'", table.primaryKey.options.type, options.type); Console.WriteLine("'{0}' vs '{1}'", table.primaryKey.options.defaultValue, options.defaultValue); Console.WriteLine("'{0}' vs '{1}'", table.primaryKey.options.isNotNull, options.isNotNull); if(!table.primaryKey.options.Equals((this as IDBTraits).GetColumnOptions(commandCreator, table.primaryKey.column))) { throw new FormattableException("Column {0} definition mismatch", table.primaryKey.column.columnName); } foreach(var column in table.columns) { options = (this as IDBTraits).GetColumnOptions(commandCreator, column.column); Console.WriteLine(); Console.WriteLine("'{0}' vs '{1}'", column.options.type, options.type); Console.WriteLine("'{0}' vs '{1}'", column.options.defaultValue, options.defaultValue); Console.WriteLine("'{0}' vs '{1}'", column.options.isNotNull, options.isNotNull); if(!column.options.Equals((this as IDBTraits).GetColumnOptions(commandCreator, column.column))) { throw new FormattableException("Column {0} definition mismatch", column.column.columnName); } } } void IDBTraits.RemoveTable(Func commandCreator, TableDescription table) { this.CheckTable(commandCreator, table); using(DbCommand command = commandCreator()) { command.CommandText = _SQLQueryManager.DropTable(table.table); Console.WriteLine(); Console.WriteLine(command.CommandText); command.ExecuteNonQuery(); } } bool IDBTraits.IsDDLTransactional { get { return false; } } } }