// NpgsqlCommandBuilder.cs // // Author: // Pedro Martínez Juliá (yoros@wanadoo.es) // // Copyright (C) 2003 Pedro Martínez Juliá // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System; using System.Resources; using System.Data; using System.Data.Common; using System.ComponentModel; using NpgsqlTypes; namespace Npgsql { /// /// This class is responsible to create database commands for automatic insert, update and delete operations. /// public sealed class NpgsqlCommandBuilder : Component { // Logging related values private static readonly String CLASSNAME = "NpgsqlCommandBuilder"; private static ResourceManager resman = new ResourceManager(typeof(NpgsqlCommandBuilder)); bool disposed = false; private NpgsqlDataAdapter data_adapter; private NpgsqlCommand insert_command; private NpgsqlCommand update_command; private NpgsqlCommand delete_command; private string quotePrefix = "\""; private string quoteSuffix = "\""; private DataTable select_schema; public NpgsqlCommandBuilder () {} public NpgsqlCommandBuilder (NpgsqlDataAdapter adapter) { DataAdapter = adapter; } public NpgsqlDataAdapter DataAdapter { get { return data_adapter; } set { if (data_adapter != null) { throw new InvalidOperationException ("DataAdapter is already set"); } data_adapter = value; data_adapter.RowUpdating += new NpgsqlRowUpdatingEventHandler(OnRowUpdating); } } private void OnRowUpdating(Object sender, NpgsqlRowUpdatingEventArgs value) { switch (value.StatementType) { case StatementType.Insert: value.Command = GetInsertCommand(value.Row, false); break; case StatementType.Update: value.Command = GetUpdateCommand(value.Row, false); break; case StatementType.Delete: value.Command = GetDeleteCommand(value.Row, false); break; } DataColumnMappingCollection columnMappings = value.TableMapping.ColumnMappings; foreach (IDataParameter parameter in value.Command.Parameters) { string dsColumnName = parameter.SourceColumn; if (columnMappings.Contains(parameter.SourceColumn)) { DataColumnMapping mapping = columnMappings[parameter.SourceColumn]; if (mapping != null) { dsColumnName = mapping.DataSetColumn; } } DataRowVersion rowVersion = DataRowVersion.Default; if (value.StatementType == StatementType.Update) rowVersion = parameter.SourceVersion; if (value.StatementType == StatementType.Delete) rowVersion = DataRowVersion.Original; parameter.Value = value.Row [dsColumnName, rowVersion]; } } public string QuotePrefix { get { return quotePrefix; } set { quotePrefix = value; } } public string QuoteSuffix { get { return quoteSuffix; } set { quoteSuffix = value; } } /// /// /// This method is reponsible to derive the command parameter list with values obtained from function definition. /// It clears the Parameters collection of command. Also, if there is any parameter type which is not supported by Npgsql, an InvalidOperationException will be thrown. /// Parameters name will be parameter1, parameter2, ... /// For while, only parameter name and NpgsqlDbType are obtained. /// /// NpgsqlCommand whose function parameters will be obtained. public static void DeriveParameters (NpgsqlCommand command) { // Updated after 0.99.3 to support the optional existence of a name qualifying schema and case insensitivity when the schema ror procedure name do not contain a quote. // This fixed an incompatibility with NpgsqlCommand.CheckFunctionReturn(String ReturnType) String query = null; string procedureName = null; string schemaName = null; string[] fullName = command.CommandText.Split('.'); if (fullName.Length > 1 && fullName[0].Length > 0) { query = "select proargtypes from pg_proc p left join pg_namespace n on p.pronamespace = n.oid where proname=:proname and n.nspname=:nspname"; schemaName = (fullName[0].IndexOf("\"") != -1) ? fullName[0] : fullName[0].ToLower(); procedureName = (fullName[1].IndexOf("\"") != -1) ? fullName[1] : fullName[1].ToLower(); } else { query = "select proargtypes from pg_proc where proname = :proname"; procedureName = (fullName[0].IndexOf("\"") != -1) ? fullName[0] : fullName[0].ToLower(); } NpgsqlCommand c = new NpgsqlCommand(query, command.Connection); c.Parameters.Add(new NpgsqlParameter("proname", NpgsqlDbType.Text)); c.Parameters[0].Value = procedureName.Replace("\"", "").Trim(); if (fullName.Length > 1 && schemaName.Length > 0) { NpgsqlParameter prm = c.Parameters.Add(new NpgsqlParameter("nspname", NpgsqlDbType.Text)); prm.Value = schemaName.Replace("\"", "").Trim(); } String types = (String) c.ExecuteScalar(); if (types == null) throw new InvalidOperationException (String.Format(resman.GetString("Exception_InvalidFunctionName"), command.CommandText)); command.Parameters.Clear(); Int32 i = 1; foreach(String s in types.Split()) { if (!c.Connector.OidToNameMapping.ContainsOID(Int32.Parse(s))) { command.Parameters.Clear(); throw new InvalidOperationException(String.Format("Invalid parameter type: {0}", s)); } command.Parameters.Add(new NpgsqlParameter("parameter" + i++, c.Connector.OidToNameMapping[Int32.Parse(s)].NpgsqlDbType)); } } private string GetQuotedName(string str) { string result = str; if ((QuotePrefix != string.Empty) && !str.StartsWith(QuotePrefix)) { result = QuotePrefix + result; } if ((QuoteSuffix != string.Empty) && !str.EndsWith(QuoteSuffix)) { result = result + QuoteSuffix; } return result; } public NpgsqlCommand GetInsertCommand (DataRow row) { return GetInsertCommand(row, true); } private NpgsqlCommand GetInsertCommand(DataRow row, bool setParameterValues) { if (insert_command == null) { string fields = ""; string values = ""; bool first = true; if (select_schema == null) { BuildSchema(); } string schema_name = string.Empty; string table_name = string.Empty; string quotedName; NpgsqlCommand cmdaux = new NpgsqlCommand(); foreach(DataRow schemaRow in select_schema.Rows) { if (!(bool)schemaRow["IsAutoIncrement"]) { if (!first) { fields += ", "; values += ", "; } else { schema_name = (string)schemaRow["BaseSchemaName"]; table_name = (string)schemaRow["BaseTableName"]; if (table_name == null || table_name.Length == 0) { table_name = row.Table.TableName; } } quotedName = GetQuotedName((string)schemaRow["BaseColumnName"]); DataColumn column = row.Table.Columns[(string)schemaRow["ColumnName"]]; fields += quotedName; values += ":param_" + column.ColumnName; first = false; NpgsqlParameter aux = new NpgsqlParameter("param_" + column.ColumnName, NpgsqlTypesHelper.GetNativeTypeInfo(column.DataType)); aux.Direction = ParameterDirection.Input; aux.SourceColumn = column.ColumnName; cmdaux.Parameters.Add(aux); } } cmdaux.CommandText = "insert into " + QualifiedTableName(schema_name, table_name) + " (" + fields + ") values (" + values + ")"; cmdaux.Connection = data_adapter.SelectCommand.Connection; insert_command = cmdaux; } if (setParameterValues) { SetParameterValuesFromRow(insert_command, row); } return insert_command; } public NpgsqlCommand GetUpdateCommand (DataRow row) { return GetUpdateCommand(row, true); } private NpgsqlCommand GetUpdateCommand(DataRow row, bool setParameterValues) { if (update_command == null) { string sets = ""; string wheres = ""; bool first = true; if (select_schema == null) { BuildSchema(); } string schema_name = string.Empty; string table_name = string.Empty; string quotedName; NpgsqlCommand cmdaux = new NpgsqlCommand(); foreach(DataRow schemaRow in select_schema.Rows) { if (!first) { sets += ", "; wheres += " and "; } else { schema_name = (string)schemaRow["BaseSchemaName"]; table_name = (string)schemaRow["BaseTableName"]; if (table_name == null || table_name.Length == 0) { table_name = row.Table.TableName; } } quotedName = GetQuotedName((string)schemaRow["BaseColumnName"]); DataColumn column = row.Table.Columns[(string)schemaRow["ColumnName"]]; sets += String.Format("{0} = :s_param_{1}", quotedName, column.ColumnName); wheres += String.Format("(({0} is null) or ({0} = :w_param_{1}))", quotedName, column.ColumnName); first = false; NpgsqlNativeTypeInfo typeInfo = NpgsqlTypesHelper.GetNativeTypeInfo(column.DataType); NpgsqlParameter aux_set = new NpgsqlParameter("s_param_" + column.ColumnName, typeInfo); aux_set.Direction = ParameterDirection.Input; aux_set.SourceColumn = column.ColumnName; aux_set.SourceVersion = DataRowVersion.Current; cmdaux.Parameters.Add(aux_set); NpgsqlParameter aux_where = new NpgsqlParameter("w_param_" + column.ColumnName, typeInfo); aux_where.Direction = ParameterDirection.Input; aux_where.SourceColumn = column.ColumnName; aux_where.SourceVersion = DataRowVersion.Original; cmdaux.Parameters.Add(aux_where); } cmdaux.CommandText = "update " + QualifiedTableName(schema_name, table_name) + " set " + sets + " where ( " + wheres + " )"; cmdaux.Connection = data_adapter.SelectCommand.Connection; update_command = cmdaux; } if (setParameterValues) { SetParameterValuesFromRow(update_command, row); } return update_command; } public NpgsqlCommand GetDeleteCommand (DataRow row) { return GetDeleteCommand(row, true); } private NpgsqlCommand GetDeleteCommand(DataRow row, bool setParameterValues) { if (delete_command == null) { string wheres = ""; bool first = true; if (select_schema == null) { BuildSchema(); } string schema_name = string.Empty; string table_name = string.Empty; string quotedName; NpgsqlCommand cmdaux = new NpgsqlCommand(); foreach(DataRow schemaRow in select_schema.Rows) { if (!first) { wheres += " and "; } else { schema_name = (string)schemaRow["BaseSchemaName"]; table_name = (string)schemaRow["BaseTableName"]; if (table_name == null || table_name.Length == 0) { table_name = row.Table.TableName; } } quotedName = GetQuotedName((string)schemaRow["BaseColumnName"]); DataColumn column = row.Table.Columns[(string)schemaRow["ColumnName"]]; wheres += String.Format("(({0} is null) or ({0} = :param_{1}))", quotedName , column.ColumnName); first = false; NpgsqlParameter aux = new NpgsqlParameter("param_" + column.ColumnName, NpgsqlTypesHelper.GetNativeTypeInfo(column.DataType)); aux.Direction = ParameterDirection.Input; aux.SourceColumn = column.ColumnName; aux.SourceVersion = DataRowVersion.Original; cmdaux.Parameters.Add(aux); } cmdaux.CommandText = "delete from " + QualifiedTableName(schema_name, table_name) + " where ( " + wheres + " )"; cmdaux.Connection = data_adapter.SelectCommand.Connection; delete_command = cmdaux; } if (setParameterValues) { SetParameterValuesFromRow(delete_command, row); } return delete_command; } public void RefreshSchema () { insert_command = null; update_command = null; delete_command = null; select_schema = null; } protected override void Dispose (bool disposing) { if (!disposed) { if (disposing) { if (insert_command != null) { insert_command.Dispose(); } if (update_command != null) { update_command.Dispose(); } if (delete_command != null) { delete_command.Dispose(); } data_adapter.RowUpdating -= new NpgsqlRowUpdatingEventHandler(OnRowUpdating); } } base.Dispose(disposing); } private void BuildSchema() { if (select_schema == null) { bool openedConnection = false; try { if ((data_adapter.SelectCommand.Connection.State & ConnectionState.Open) != ConnectionState.Open) { data_adapter.SelectCommand.Connection.Open(); openedConnection = true; } using (NpgsqlDataReader reader = data_adapter.SelectCommand.ExecuteReader(CommandBehavior.SchemaOnly|CommandBehavior.KeyInfo)) { select_schema = reader.GetSchemaTable(); } } finally { if (openedConnection) { data_adapter.SelectCommand.Connection.Close(); } } } } /*~NpgsqlCommandBuilder () { Dispose(false); }*/ private string QualifiedTableName(string schema, string tableName) { if (schema == null || schema.Length == 0) { return GetQuotedName(tableName); } else { return GetQuotedName(schema) + "." + GetQuotedName(tableName); } } private static void SetParameterValuesFromRow(NpgsqlCommand command, DataRow row) { foreach (NpgsqlParameter parameter in command.Parameters) { parameter.Value = row[parameter.SourceColumn, parameter.SourceVersion]; } } } }