// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
namespace WixToolset.Dtf.WindowsInstaller.Linq
{
using System;
using System.IO;
using System.Text;
using System.Globalization;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
///
/// Generic record entity for queryable databases,
/// and base for strongly-typed entity subclasses.
///
///
/// Several predefined specialized subclasses are provided for common
/// standard tables. Subclasses for additional standard tables
/// or custom tables are not necessary, but they are easy to create
/// and make the coding experience much nicer.
/// When creating subclasses, the following attributes may be
/// useful: ,
///
///
public class QRecord
{
///
/// Do not call. Use QTable.NewRecord() instead.
///
///
/// Subclasses must also provide a public parameterless constructor.
/// QRecord constructors are only public due to implementation
/// reasons (to satisfy the new() constraint on the QTable generic
/// class). They are not intended to be called by user code other than
/// a subclass constructor. If the constructor is invoked directly,
/// the record instance will not be properly initialized (associated
/// with a database table) and calls to methods on the instance
/// will throw a NullReferenceException.
///
///
public QRecord()
{
}
internal QDatabase Database { get; set; }
internal TableInfo TableInfo { get; set; }
internal IList Values { get; set; }
internal bool Exists { get; set; }
///
/// Gets the number of fields in the record.
///
public int FieldCount
{
get
{
return this.Values.Count;
}
}
///
/// Gets or sets a record field.
///
/// column name of the field
///
/// Setting a field value will automatically update the database.
///
public string this[string field]
{
get
{
if (field == null)
{
throw new ArgumentNullException("field");
}
int index = this.TableInfo.Columns.IndexOf(field);
if (index < 0)
{
throw new ArgumentOutOfRangeException("field");
}
return this[index];
}
set
{
if (field == null)
{
throw new ArgumentNullException("field");
}
this.Update(new string[] { field }, new string[] { value });
}
}
///
/// Gets or sets a record field.
///
/// zero-based column index of the field
///
/// Setting a field value will automatically update the database.
///
public string this[int index]
{
get
{
if (index < 0 || index >= this.FieldCount)
{
throw new ArgumentOutOfRangeException("index");
}
return this.Values[index];
}
set
{
if (index < 0 || index >= this.FieldCount)
{
throw new ArgumentOutOfRangeException("index");
}
this.Update(new int[] { index }, new string[] { value });
}
}
///
/// Used by subclasses to get a field as an integer.
///
/// zero-based column index of the field
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "I")]
protected int I(int index)
{
string value = this[index];
return value.Length > 0 ?
Int32.Parse(value, CultureInfo.InvariantCulture) : 0;
}
///
/// Used by subclasses to get a field as a nullable integer.
///
/// zero-based column index of the field
protected int? NI(int index)
{
string value = this[index];
return value.Length > 0 ?
new int?(Int32.Parse(value, CultureInfo.InvariantCulture)) : null;
}
///
/// Dumps all record fields to a string.
///
public override string ToString()
{
StringBuilder buf = new StringBuilder(this.GetType().Name);
buf.Append(" {");
for (int i = 0; i < this.FieldCount; i++)
{
buf.AppendFormat("{0} {1} = {2}",
(i > 0 ? "," : String.Empty),
this.TableInfo.Columns[i].Name,
this[i]);
}
buf.Append(" }");
return buf.ToString();
}
///
/// Update multiple fields in the record (and the database).
///
/// column names of fields to update
/// new values for each field being updated
public void Update(IList fields, IList values)
{
if (fields == null)
{
throw new ArgumentNullException("fields");
}
if (values == null)
{
throw new ArgumentNullException("values");
}
if (fields.Count == 0 || values.Count == 0 ||
fields.Count > this.FieldCount ||
values.Count != fields.Count)
{
throw new ArgumentOutOfRangeException("fields");
}
int[] indexes = new int[fields.Count];
for (int i = 0; i < indexes.Length; i++)
{
if (fields[i] == null)
{
throw new ArgumentNullException("fields[" + i + "]");
}
indexes[i] = this.TableInfo.Columns.IndexOf(fields[i]);
if (indexes[i] < 0)
{
throw new ArgumentOutOfRangeException("fields[" + i + "]");
}
}
this.Update(indexes, values);
}
///
/// Update multiple fields in the record (and the database).
///
/// column indexes of fields to update
/// new values for each field being updated
///
/// The record (primary keys) must already exist in the table.
/// Updating primary key fields is not yet implemented; use Delete()
/// and Insert() instead.
///
public void Update(IList indexes, IList values)
{
if (indexes == null)
{
throw new ArgumentNullException("indexes");
}
if (values == null)
{
throw new ArgumentNullException("values");
}
if (indexes.Count == 0 || values.Count == 0 ||
indexes.Count > this.FieldCount ||
values.Count != indexes.Count)
{
throw new ArgumentOutOfRangeException("indexes");
}
bool primaryKeyChanged = false;
for (int i = 0; i < indexes.Count; i++)
{
int index = indexes[i];
if (index < 0 || index >= this.FieldCount)
{
throw new ArgumentOutOfRangeException("index[" + i + "]");
}
ColumnInfo col = this.TableInfo.Columns[index];
if (this.TableInfo.PrimaryKeys.Contains(col.Name))
{
if (values[i] == null)
{
throw new ArgumentNullException("values[" + i + "]");
}
primaryKeyChanged = true;
}
else if (values[i] == null)
{
if (col.IsRequired)
{
throw new ArgumentNullException("values[" + i + "]");
}
}
this.Values[index] = values[i];
}
if (this.Exists)
{
if (!primaryKeyChanged)
{
int updateRecSize = indexes.Count + this.TableInfo.PrimaryKeys.Count;
using (Record updateRec = this.Database.CreateRecord(updateRecSize))
{
StringBuilder s = new StringBuilder("UPDATE `");
s.Append(this.TableInfo.Name);
s.Append("` SET");
for (int i = 0; i < indexes.Count; i++)
{
ColumnInfo col = this.TableInfo.Columns[indexes[i]];
if (col.Type == typeof(Stream))
{
throw new NotSupportedException(
"Cannot update stream columns via QRecord.");
}
int index = indexes[i];
s.AppendFormat("{0} `{1}` = ?",
(i > 0 ? "," : String.Empty),
col.Name);
if (values[i] != null)
{
updateRec[i + 1] = values[i];
}
}
for (int i = 0; i < this.TableInfo.PrimaryKeys.Count; i++)
{
string key = this.TableInfo.PrimaryKeys[i];
s.AppendFormat(" {0} `{1}` = ?", (i == 0 ? "WHERE" : "AND"), key);
int index = this.TableInfo.Columns.IndexOf(key);
updateRec[indexes.Count + i + 1] = this.Values[index];
}
string updateSql = s.ToString();
TextWriter log = this.Database.Log;
if (log != null)
{
log.WriteLine();
log.WriteLine(updateSql);
for (int field = 1; field <= updateRecSize; field++)
{
log.WriteLine(" ? = " + updateRec.GetString(field));
}
}
this.Database.Execute(updateSql, updateRec);
}
}
else
{
throw new NotImplementedException(
"Update() cannot handle changed primary keys yet.");
// TODO:
// query using old values
// update values
// View.Replace
}
}
}
///
/// Inserts the record in the database.
///
///
/// The record (primary keys) may not already exist in the table.
/// Use to get a new
/// record. Prmary keys and all required fields
/// must be filled in before insertion.
///
public void Insert()
{
this.Insert(false);
}
///
/// Inserts the record into the table.
///
/// true if the record is temporarily
/// inserted, to be visible only as long as the database is open
///
/// The record (primary keys) may not already exist in the table.
/// Use to get a new
/// record. Prmary keys and all required fields
/// must be filled in before insertion.
///
public void Insert(bool temporary)
{
using (Record updateRec = this.Database.CreateRecord(this.FieldCount))
{
string insertSql = this.TableInfo.SqlInsertString;
if (temporary)
{
insertSql += " TEMPORARY";
}
TextWriter log = this.Database.Log;
if (log != null)
{
log.WriteLine();
log.WriteLine(insertSql);
}
for (int index = 0; index < this.FieldCount; index++)
{
ColumnInfo col = this.TableInfo.Columns[index];
if (col.Type == typeof(Stream))
{
throw new NotSupportedException(
"Cannot insert stream columns via QRecord.");
}
if (this.Values[index] != null)
{
updateRec[index + 1] = this.Values[index];
}
if (log != null)
{
log.WriteLine(" ? = " + this.Values[index]);
}
}
this.Database.Execute(insertSql, updateRec);
this.Exists = true;
}
}
///
/// Deletes the record from the table if it exists.
///
public void Delete()
{
using (Record keyRec = this.Database.CreateRecord(this.TableInfo.PrimaryKeys.Count))
{
StringBuilder s = new StringBuilder("DELETE FROM `");
s.Append(this.TableInfo.Name);
s.Append("`");
for (int i = 0; i < this.TableInfo.PrimaryKeys.Count; i++)
{
string key = this.TableInfo.PrimaryKeys[i];
s.AppendFormat(" {0} `{1}` = ?", (i == 0 ? "WHERE" : "AND"), key);
int index = this.TableInfo.Columns.IndexOf(key);
keyRec[i + 1] = this.Values[index];
}
string deleteSql = s.ToString();
TextWriter log = this.Database.Log;
if (log != null)
{
log.WriteLine();
log.WriteLine(deleteSql);
for (int i = 0; i < this.TableInfo.PrimaryKeys.Count; i++)
{
log.WriteLine(" ? = " + keyRec.GetString(i + 1));
}
}
this.Database.Execute(deleteSql, keyRec);
this.Exists = false;
}
}
///
/// Not yet implemented.
///
public void Refresh()
{
throw new NotImplementedException();
}
///
/// Not yet implemented.
///
public void Assign()
{
throw new NotImplementedException();
}
///
/// Not yet implemented.
///
public bool Merge()
{
throw new NotImplementedException();
}
///
/// Not yet implemented.
///
public ICollection Validate()
{
throw new NotImplementedException();
}
///
/// Not yet implemented.
///
[SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")]
public ICollection ValidateNew()
{
throw new NotImplementedException();
}
///
/// Not yet implemented.
///
public ICollection ValidateFields()
{
throw new NotImplementedException();
}
///
/// Not yet implemented.
///
public ICollection ValidateDelete()
{
throw new NotImplementedException();
}
}
}