Added missing files

This commit is contained in:
grzegorz.russek
2013-06-05 20:08:05 +00:00
parent 3aa20eb50b
commit 52eb3e7844
8 changed files with 3019 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Some of methods in this code file is based on Kerosene ORM solution
* for parsing dynamic lambda expressions by Moisés Barba Cebeira
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using DynamORM.Builders.Implementation;
using DynamORM.Helpers;
using DynamORM.Mapper;
namespace DynamORM.Builders.Extensions
{
internal static class DynamicModifyBuilderExtensions
{
internal static T Table<T>(this T builder, string tableName, Dictionary<string, DynamicSchemaColumn> schema = null) where T : DynamicModifyBuilder
{
var tuple = tableName.Validated("Table Name").SplitSomethingAndAlias();
if (!string.IsNullOrEmpty(tuple.Item2))
throw new ArgumentException(string.Format("Can not use aliases in INSERT steatement. ({0})", tableName), "tableName");
var parts = tuple.Item1.Split('.');
builder.Tables.Clear();
builder.Tables.Add(new DynamicQueryBuilder.TableInfo(builder.Database,
builder.Database.StripName(parts.Last()).Validated("Table"), null,
parts.Length == 2 ? builder.Database.StripName(parts.First()).Validated("Owner", canbeNull: true) : null));
if (schema != null)
(builder.Tables[0] as DynamicQueryBuilder.TableInfo).Schema = schema;
return builder;
}
internal static T Table<T>(this T builder, Type type) where T : DynamicQueryBuilder
{
var mapper = DynamicMapperCache.GetMapper(type);
string name = string.Empty;
if (mapper == null)
throw new InvalidOperationException("Cant assign unmapable type as a table.");
if (builder is DynamicModifyBuilder)
{
builder.Tables.Clear();
builder.Tables.Add(new DynamicQueryBuilder.TableInfo(builder.Database, type));
}
else if (builder is DynamicSelectQueryBuilder)
(builder as DynamicSelectQueryBuilder).From(x => x(type));
return builder;
}
}
}

View File

@@ -0,0 +1,232 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Some of methods in this code file is based on Kerosene ORM solution
* for parsing dynamic lambda expressions by Moisés Barba Cebeira
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using DynamORM.Builders.Implementation;
using DynamORM.Helpers;
using DynamORM.Helpers.Dynamics;
using DynamORM.Mapper;
namespace DynamORM.Builders.Extensions
{
internal static class DynamicWhereQueryExtensions
{
#region Where
internal static T InternalWhere<T>(this T builder, Func<dynamic, object> func) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithWhere
{
if (func == null) throw new ArgumentNullException("Array of functions cannot be null.");
using (var parser = DynamicParser.Parse(func))
{
string condition = null;
bool and = true;
var result = parser.Result;
if (result is string)
{
condition = (string)result;
if (condition.ToUpper().IndexOf("OR") == 0)
{
and = false;
condition = condition.Substring(3);
}
else if (condition.ToUpper().IndexOf("AND") == 0)
condition = condition.Substring(4);
}
else if (!(result is DynamicParser.Node) && !result.GetType().IsValueType)
return builder.InternalWhere(result);
else
{
// Intercepting the 'x => x.And()' and 'x => x.Or()' virtual methods...
if (result is DynamicParser.Node.Method && ((DynamicParser.Node.Method)result).Host is DynamicParser.Node.Argument)
{
var node = (DynamicParser.Node.Method)result;
var name = node.Name.ToUpper();
if (name == "AND" || name == "OR")
{
object[] args = ((DynamicParser.Node.Method)node).Arguments;
if (args == null) throw new ArgumentNullException("arg", string.Format("{0} is not a parameterless method.", name));
if (args.Length != 1) throw new ArgumentException(string.Format("{0} requires one and only one parameter: {1}.", name, args.Sketch()));
and = name == "AND" ? true : false;
result = args[0];
}
}
// Just parsing the contents now...
condition = builder.Parse(result, pars: builder.Parameters).Validated("Where condition");
}
if (builder.WhereCondition == null) builder.WhereCondition = condition;
else builder.WhereCondition = string.Format("{0} {1} {2}", builder.WhereCondition, and ? "AND" : "OR", condition);
}
return builder;
}
internal static T InternalWhere<T>(this T builder, DynamicColumn column) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithWhere
{
builder.VirtualMode = column.VirtualColumn;
bool prepend = false;
if (column.BeginBlock)
{
if (string.IsNullOrEmpty(builder.WhereCondition))
prepend = true;
else
builder.WhereCondition += " (";
}
// It's kind of uglu, but... well it works.
if (column.Or)
switch (column.Operator)
{
default:
case DynamicColumn.CompareOperator.Eq: builder.InternalWhere(x => x.Or(x(builder.FixObjectName(column.ColumnName)) == column.Value)); break;
case DynamicColumn.CompareOperator.Not: builder.InternalWhere(x => x.Or(x(builder.FixObjectName(column.ColumnName)) != column.Value)); break;
case DynamicColumn.CompareOperator.Like: builder.InternalWhere(x => x.Or(x(builder.FixObjectName(column.ColumnName)).Like(column.Value))); break;
case DynamicColumn.CompareOperator.NotLike: builder.InternalWhere(x => x.Or(x(builder.FixObjectName(column.ColumnName)).NotLike(column.Value))); break;
case DynamicColumn.CompareOperator.In: builder.InternalWhere(x => x.Or(x(builder.FixObjectName(column.ColumnName)).In(column.Value))); break;
case DynamicColumn.CompareOperator.Lt: builder.InternalWhere(x => x.Or(x(builder.FixObjectName(column.ColumnName)) < column.Value)); break;
case DynamicColumn.CompareOperator.Lte: builder.InternalWhere(x => x.Or(x(builder.FixObjectName(column.ColumnName)) <= column.Value)); break;
case DynamicColumn.CompareOperator.Gt: builder.InternalWhere(x => x.Or(x(builder.FixObjectName(column.ColumnName)) > column.Value)); break;
case DynamicColumn.CompareOperator.Gte: builder.InternalWhere(x => x.Or(x(builder.FixObjectName(column.ColumnName)) >= column.Value)); break;
case DynamicColumn.CompareOperator.Between: builder.InternalWhere(x => x.Or(x(builder.FixObjectName(column.ColumnName)).Between(column.Value))); break;
}
else
switch (column.Operator)
{
default:
case DynamicColumn.CompareOperator.Eq: builder.InternalWhere(x => x(builder.FixObjectName(column.ColumnName)) == column.Value); break;
case DynamicColumn.CompareOperator.Not: builder.InternalWhere(x => x(builder.FixObjectName(column.ColumnName)) != column.Value); break;
case DynamicColumn.CompareOperator.Like: builder.InternalWhere(x => x(builder.FixObjectName(column.ColumnName)).Like(column.Value)); break;
case DynamicColumn.CompareOperator.NotLike: builder.InternalWhere(x => x(builder.FixObjectName(column.ColumnName)).NotLike(column.Value)); break;
case DynamicColumn.CompareOperator.In: builder.InternalWhere(x => x(builder.FixObjectName(column.ColumnName)).In(column.Value)); break;
case DynamicColumn.CompareOperator.Lt: builder.InternalWhere(x => x(builder.FixObjectName(column.ColumnName)) < column.Value); break;
case DynamicColumn.CompareOperator.Lte: builder.InternalWhere(x => x(builder.FixObjectName(column.ColumnName)) <= column.Value); break;
case DynamicColumn.CompareOperator.Gt: builder.InternalWhere(x => x(builder.FixObjectName(column.ColumnName)) > column.Value); break;
case DynamicColumn.CompareOperator.Gte: builder.InternalWhere(x => x(builder.FixObjectName(column.ColumnName)) >= column.Value); break;
case DynamicColumn.CompareOperator.Between: builder.InternalWhere(x => x(builder.FixObjectName(column.ColumnName)).Between(column.Value)); break;
}
if (prepend)
builder.WhereCondition = string.Format("({0}", builder.WhereCondition);
if (column.EndBlock)
builder.WhereCondition += ")";
return builder;
}
internal static T InternalWhere<T>(this T builder, string column, DynamicColumn.CompareOperator op, object value) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithWhere
{
if (value is DynamicColumn)
{
var v = (DynamicColumn)value;
if (string.IsNullOrEmpty(v.ColumnName))
v.ColumnName = column;
return builder.InternalWhere(v);
}
else if (value is IEnumerable<DynamicColumn>)
{
foreach (DynamicColumn v in (IEnumerable<DynamicColumn>)value)
builder.InternalWhere(v);
return builder;
}
return builder.InternalWhere(new DynamicColumn
{
ColumnName = column,
Operator = op,
Value = value
});
}
internal static T InternalWhere<T>(this T builder, string column, object value) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithWhere
{
return builder.InternalWhere(column, DynamicColumn.CompareOperator.Eq, value);
}
internal static T InternalWhere<T>(this T builder, object conditions, bool schema = false) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithWhere
{
if (conditions is DynamicColumn)
return builder.InternalWhere((DynamicColumn)conditions);
else if (conditions is IEnumerable<DynamicColumn>)
{
foreach (DynamicColumn v in (IEnumerable<DynamicColumn>)conditions)
builder.InternalWhere(v);
return builder;
}
var dict = conditions.ToDictionary();
var mapper = DynamicMapperCache.GetMapper(conditions.GetType());
var table = dict.TryGetValue("_table").NullOr(x => x.ToString(), string.Empty);
foreach (var condition in dict)
{
if (mapper.Ignored.Contains(condition.Key) || condition.Key == "_table")
continue;
string colName = mapper != null ? mapper.PropertyMap.TryGetValue(condition.Key) ?? condition.Key : condition.Key;
DynamicSchemaColumn? col = null;
// This should be used on typed queries or update/delete steatements, which usualy operate on a single table.
if (schema)
{
col = builder.GetColumnFromSchema(colName, mapper, table);
if ((!col.HasValue || !col.Value.IsKey) &&
(mapper == null || mapper.ColumnsMap.TryGetValue(colName).NullOr(m => m.Ignore || m.Column.NullOr(c => !c.IsKey, true), true)))
continue;
colName = col.HasValue ? col.Value.Name : colName;
}
if (!string.IsNullOrEmpty(table))
builder.InternalWhere(x => x(builder.FixObjectName(string.Format("{0}.{1}", table, colName))) == condition.Value);
else
builder.InternalWhere(x => x(builder.FixObjectName(colName)) == condition.Value);
}
return builder;
}
#endregion Where
}
}

View File

@@ -0,0 +1,127 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Some of methods in this code file is based on Kerosene ORM solution
* for parsing dynamic lambda expressions by Moisés Barba Cebeira
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Linq;
using DynamORM.Builders.Extensions;
namespace DynamORM.Builders.Implementation
{
/// <summary>Implementation of dynamic delete query builder.</summary>
internal class DynamicDeleteQueryBuilder : DynamicModifyBuilder, IDynamicDeleteQueryBuilder, DynamicQueryBuilder.IQueryWithWhere
{
/// <summary>
/// Initializes a new instance of the <see cref="DynamicDeleteQueryBuilder"/> class.
/// </summary>
/// <param name="db">The database.</param>
internal DynamicDeleteQueryBuilder(DynamicDatabase db)
: base(db)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DynamicDeleteQueryBuilder"/> class.
/// </summary>
/// <param name="db">The database.</param>
/// <param name="tableName">Name of the table.</param>
public DynamicDeleteQueryBuilder(DynamicDatabase db, string tableName)
: base(db, tableName)
{
}
/// <summary>Generates the text this command will execute against the underlying database.</summary>
/// <returns>The text to execute against the underlying database.</returns>
/// <remarks>This method must be override by derived classes.</remarks>
public override string CommandText()
{
var info = Tables.Single();
return string.Format("DELETE FROM {0}{1} WHERE {2}",
string.IsNullOrEmpty(info.Owner) ? string.Empty : string.Format("{0}.", Database.DecorateName(info.Owner)),
Database.DecorateName(info.Name), WhereCondition);
}
#region Where
/// <summary>
/// Adds to the 'Where' clause the contents obtained from parsing the dynamic lambda expression given. The condition
/// is parsed to the appropriate syntax, where the specific customs virtual methods supported by the parser are used
/// as needed.
/// <para>- If several Where() methods are chained their contents are, by default, concatenated with an 'AND' operator.</para>
/// <para>- The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in:
/// 'Where( x => x.Or( condition ) )'.</para>
/// </summary>
/// <param name="func">The specification.</param>
/// <returns>This instance to permit chaining.</returns>
public virtual IDynamicDeleteQueryBuilder Where(Func<dynamic, object> func)
{
return this.InternalWhere(func);
}
/// <summary>Add where condition.</summary>
/// <param name="column">Condition column with operator and value.</param>
/// <returns>Builder instance.</returns>
public virtual IDynamicDeleteQueryBuilder Where(DynamicColumn column)
{
return this.InternalWhere(column);
}
/// <summary>Add where condition.</summary>
/// <param name="column">Condition column.</param>
/// <param name="op">Condition operator.</param>
/// <param name="value">Condition value.</param>
/// <returns>Builder instance.</returns>
public virtual IDynamicDeleteQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value)
{
return this.InternalWhere(column, op, value);
}
/// <summary>Add where condition.</summary>
/// <param name="column">Condition column.</param>
/// <param name="value">Condition value.</param>
/// <returns>Builder instance.</returns>
public virtual IDynamicDeleteQueryBuilder Where(string column, object value)
{
return this.InternalWhere(column, value);
}
/// <summary>Add where condition.</summary>
/// <param name="conditions">Set conditions as properties and values of an object.</param>
/// <param name="schema">If <c>true</c> use schema to determine key columns and ignore those which
/// aren't keys.</param>
/// <returns>Builder instance.</returns>
public virtual IDynamicDeleteQueryBuilder Where(object conditions, bool schema = false)
{
return this.InternalWhere(conditions, schema);
}
#endregion Where
}
}

View File

@@ -0,0 +1,201 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Some of methods in this code file is based on Kerosene ORM solution
* for parsing dynamic lambda expressions by Moisés Barba Cebeira
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Linq;
using DynamORM.Helpers.Dynamics;
using DynamORM.Mapper;
namespace DynamORM.Builders.Implementation
{
/// <summary>Implementation of dynamic insert query builder.</summary>
internal class DynamicInsertQueryBuilder : DynamicModifyBuilder, IDynamicInsertQueryBuilder
{
private string _columns;
private string _values;
/// <summary>
/// Initializes a new instance of the <see cref="DynamicInsertQueryBuilder"/> class.
/// </summary>
/// <param name="db">The database.</param>
internal DynamicInsertQueryBuilder(DynamicDatabase db)
: base(db)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DynamicInsertQueryBuilder"/> class.
/// </summary>
/// <param name="db">The database.</param>
/// <param name="tableName">Name of the table.</param>
public DynamicInsertQueryBuilder(DynamicDatabase db, string tableName)
: base(db, tableName)
{
}
/// <summary>Generates the text this command will execute against the underlying database.</summary>
/// <returns>The text to execute against the underlying database.</returns>
/// <remarks>This method must be override by derived classes.</remarks>
public override string CommandText()
{
var info = Tables.Single();
return string.Format("INSERT INTO {0}{1} ({2}) VALUES ({3})",
string.IsNullOrEmpty(info.Owner) ? string.Empty : string.Format("{0}.", Database.DecorateName(info.Owner)),
Database.DecorateName(info.Name), _columns, _values);
}
#region Insert
/// <summary>
/// Specifies the columns to insert using the dynamic lambda expressions given. Each expression correspond to one
/// column, and can:
/// <para>- Resolve to a string, in this case a '=' must appear in the string.</para>
/// <para>- Resolve to a expression with the form: 'x => x.Column = Value'.</para>
/// </summary>
/// <param name="func">The specifications.</param>
/// <returns>This instance to permit chaining.</returns>
public virtual IDynamicInsertQueryBuilder Insert(params Func<dynamic, object>[] func)
{
if (func == null)
throw new ArgumentNullException("Array of specifications cannot be null.");
int index = -1;
foreach (var f in func)
{
index++;
if (f == null)
throw new ArgumentNullException(string.Format("Specification #{0} cannot be null.", index));
using (var parser = DynamicParser.Parse(f))
{
var result = parser.Result;
if (result == null)
throw new ArgumentException(string.Format("Specification #{0} resolves to null.", index));
string main = null;
string value = null;
string str = null;
// When 'x => x.Table.Column = value' or 'x => x.Column = value'...
if (result is DynamicParser.Node.SetMember)
{
var node = (DynamicParser.Node.SetMember)result;
DynamicSchemaColumn? col = GetColumnFromSchema(node.Name);
main = Database.DecorateName(node.Name);
value = Parse(node.Value, pars: Parameters, nulls: true, columnSchema: col);
_columns = _columns == null ? main : string.Format("{0}, {1}", _columns, main);
_values = _values == null ? value : string.Format("{0}, {1}", _values, value);
continue;
}
else if (!(result is DynamicParser.Node) && !result.GetType().IsValueType)
{
Insert(result);
continue;
}
// Other specifications are considered invalid...
var err = string.Format("Specification '{0}' is invalid.", result);
str = Parse(result);
if (str.Contains("=")) err += " May have you used a '==' instead of a '=' operator?";
throw new ArgumentException(err);
}
}
return this;
}
/// <summary>Add insert fields.</summary>
/// <param name="column">Insert column and value.</param>
/// <returns>Builder instance.</returns>
public virtual IDynamicInsertQueryBuilder Insert(DynamicColumn column)
{
DynamicSchemaColumn? col = column.Schema ?? GetColumnFromSchema(column.ColumnName);
string main = FixObjectName(column.ColumnName, onlyColumn: true);
string value = Parse(column.Value, pars: Parameters, nulls: true, columnSchema: col);
_columns = _columns == null ? main : string.Format("{0}, {1}", _columns, main);
_values = _values == null ? value : string.Format("{0}, {1}", _values, value);
return this;
}
/// <summary>Add insert fields.</summary>
/// <param name="column">Insert column.</param>
/// <param name="value">Insert value.</param>
/// <returns>Builder instance.</returns>
public virtual IDynamicInsertQueryBuilder Insert(string column, object value)
{
if (value is DynamicColumn)
{
var v = (DynamicColumn)value;
if (string.IsNullOrEmpty(v.ColumnName))
v.ColumnName = column;
return Insert(v);
}
return Insert(new DynamicColumn
{
ColumnName = column,
Value = value,
});
}
/// <summary>Add insert fields.</summary>
/// <param name="o">Set insert value as properties and values of an object.</param>
/// <returns>Builder instance.</returns>
public virtual IDynamicInsertQueryBuilder Insert(object o)
{
var dict = o.ToDictionary();
var mapper = DynamicMapperCache.GetMapper(o.GetType());
if (mapper != null)
{
foreach (var con in dict)
if (!mapper.Ignored.Contains(con.Key))
Insert(mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key, con.Value);
}
else
foreach (var con in dict)
Insert(con.Key, con.Value);
return this;
}
#endregion Insert
}
}

View File

@@ -0,0 +1,71 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using DynamORM.Builders.Extensions;
namespace DynamORM.Builders.Implementation
{
/// <summary>Base query builder for insert/update/delete statements.</summary>
internal abstract class DynamicModifyBuilder : DynamicQueryBuilder
{
/// <summary>
/// Initializes a new instance of the <see cref="DynamicModifyBuilder"/> class.
/// </summary>
/// <param name="db">The database.</param>
public DynamicModifyBuilder(DynamicDatabase db)
: base(db)
{
VirtualMode = false;
}
/// <summary>
/// Initializes a new instance of the <see cref="DynamicModifyBuilder" /> class.
/// </summary>
/// <param name="db">The database.</param>
/// <param name="tableName">Name of the table.</param>
public DynamicModifyBuilder(DynamicDatabase db, string tableName)
: this(db)
{
VirtualMode = false;
this.Table(tableName);
}
/// <summary>Execute this builder.</summary>
/// <returns>Result of an execution..</returns>
public virtual int Execute()
{
using (var con = Database.Open())
using (var cmd = con.CreateCommand())
{
return cmd
.SetCommand(this)
.ExecuteNonQuery();
}
}
}
}

View File

@@ -0,0 +1,744 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Some of methods in this code file is based on Kerosene ORM solution
* for parsing dynamic lambda expressions by Moisés Barba Cebeira
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using DynamORM.Helpers;
using DynamORM.Helpers.Dynamics;
using DynamORM.Mapper;
namespace DynamORM.Builders.Implementation
{
/// <summary>Implementation of dynamic query builder base interface.</summary>
internal abstract class DynamicQueryBuilder : IDynamicQueryBuilder
{
/// <summary>Empty interface to allow where query builder implementation use universal approach.</summary>
internal interface IQueryWithWhere { }
private DynamicQueryBuilder _parent = null;
/// <summary>Gets or sets a value indicating whether add virtual.</summary>
internal bool VirtualMode { get; set; }
#region TableInfo
/// <summary>Table information.</summary>
internal class TableInfo : ITableInfo
{
/// <summary>
/// Initializes a new instance of the <see cref="TableInfo"/> class.
/// </summary>
internal TableInfo()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="TableInfo" /> class.
/// </summary>
/// <param name="db">The database.</param>
/// <param name="name">The name of table.</param>
/// <param name="alias">The table alias.</param>
/// <param name="owner">The table owner.</param>
public TableInfo(DynamicDatabase db, string name, string alias = null, string owner = null)
{
Name = name;
Alias = alias;
Owner = owner;
if (!name.ContainsAny(StringExtensions.InvalidMemberChars))
Schema = db.GetSchema(name, owner: owner);
}
/// <summary>
/// Initializes a new instance of the <see cref="TableInfo" /> class.
/// </summary>
/// <param name="db">The database.</param>
/// <param name="type">The type which can be mapped to database.</param>
/// <param name="alias">The table alias.</param>
/// <param name="owner">The table owner.</param>
public TableInfo(DynamicDatabase db, Type type, string alias = null, string owner = null)
{
var mapper = DynamicMapperCache.GetMapper(type);
Name = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ?
mapper.Type.Name : mapper.Table.Name;
Owner = (mapper.Table != null) ? mapper.Table.Owner : owner;
Alias = alias;
Schema = db.GetSchema(type);
}
/// <summary>Gets or sets table owner name.</summary>
public string Owner { get; internal set; }
/// <summary>Gets or sets table name.</summary>
public string Name { get; internal set; }
/// <summary>Gets or sets table alias.</summary>
public string Alias { get; internal set; }
/// <summary>Gets or sets table schema.</summary>
public Dictionary<string, DynamicSchemaColumn> Schema { get; internal set; }
}
/// <summary>Generic based table information.</summary>
/// <typeparam name="T">Type of class that is represented in database.</typeparam>
internal class TableInfo<T> : TableInfo
{
/// <summary>
/// Initializes a new instance of the <see cref="TableInfo{T}" /> class.
/// </summary>
/// <param name="db">The database.</param>
/// <param name="alias">The table alias.</param>
/// <param name="owner">The table owner.</param>
public TableInfo(DynamicDatabase db, string alias = null, string owner = null)
: base(db, typeof(T), alias, owner)
{
}
}
#endregion TableInfo
#region Parameter
/// <summary>Interface describing parameter info.</summary>
internal class Parameter : IParameter
{
/// <summary>Gets or sets the parameter temporary name.</summary>
public string Name { get; internal set; }
/// <summary>Gets or sets the parameter value.</summary>
public object Value { get; set; }
/// <summary>Gets or sets a value indicating whether this <see cref="Parameter"/> is virtual.</summary>
public bool Virtual { get; set; }
/// <summary>Gets or sets the parameter schema information.</summary>
public DynamicSchemaColumn? Schema { get; internal set; }
}
#endregion Parameter
internal string WhereCondition { get; set; }
/// <summary>Gets <see cref="DynamicDatabase"/> instance.</summary>
public DynamicDatabase Database { get; private set; }
/// <summary>Gets the tables used in this builder.</summary>
public IList<ITableInfo> Tables { get; private set; }
/// <summary>Gets the tables used in this builder.</summary>
public IDictionary<string, IParameter> Parameters { get; private set; }
/// <summary>Gets a value indicating whether database supports standard schema.</summary>
public bool SupportSchema { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="DynamicQueryBuilder"/> class.
/// </summary>
/// <param name="db">The database.</param>
public DynamicQueryBuilder(DynamicDatabase db)
{
VirtualMode = false;
Tables = new List<ITableInfo>();
Parameters = new Dictionary<string, IParameter>();
Database = db;
SupportSchema = (db.Options & DynamicDatabaseOptions.SupportSchema) == DynamicDatabaseOptions.SupportSchema;
}
/// <summary>Initializes a new instance of the <see cref="DynamicQueryBuilder"/> class.</summary>
/// <param name="db">The database.</param>
/// <param name="parent">The parent query.</param>
internal DynamicQueryBuilder(DynamicDatabase db, DynamicQueryBuilder parent)
: this(db)
{
_parent = parent;
}
internal bool IsTableAlias(string name)
{
DynamicQueryBuilder builder = this;
while (builder != null)
{
if (builder.Tables.Any(t => t.Alias == name))
return true;
builder = builder._parent;
}
return false;
}
internal bool IsTable(string name, string owner)
{
DynamicQueryBuilder builder = this;
while (builder != null)
{
if ((string.IsNullOrEmpty(owner) && builder.Tables.Any(t => t.Name.ToLower() == name.ToLower())) ||
(!string.IsNullOrEmpty(owner) && builder.Tables.Any(t => t.Name.ToLower() == name.ToLower() &&
!string.IsNullOrEmpty(t.Owner) && t.Owner.ToLower() == owner.ToLower())))
return true;
builder = builder._parent;
}
return false;
}
/// <summary>Creates sub query.</summary>
/// <returns>Sub query builder.</returns>
public IDynamicSelectQueryBuilder SubQuery()
{
return new DynamicSelectQueryBuilder(Database, this);
}
/// <summary>Adds to the 'From' clause of sub query the contents obtained by
/// parsing the dynamic lambda expressions given. The supported formats are:
/// <para>- Resolve to a string: 'x => "Table AS Alias', where the alias part is optional.</para>
/// <para>- Resolve to an expression: 'x => x.Table.As( x.Alias )', where the alias part is optional.</para>
/// <para>- Generic expression: 'x => x( expression ).As( x.Alias )', where the alias part is mandatory. In this
/// case the alias is not annotated.</para>
/// </summary>
/// <param name="func">The specification.</param>
/// <returns>This instance to permit chaining.</returns>
public IDynamicSelectQueryBuilder SubQuery(params Func<dynamic, object>[] func)
{
return SubQuery().From(func);
}
/// <summary>
/// Generates the text this command will execute against the underlying database.
/// </summary>
/// <returns>The text to execute against the underlying database.</returns>
/// <remarks>This method must be override by derived classes.</remarks>
public abstract string CommandText();
/// <summary>Fill command with query.</summary>
/// <param name="command">Command to fill.</param>
/// <returns>Filled instance of <see cref="IDbCommand"/>.</returns>
public virtual IDbCommand FillCommand(IDbCommand command)
{
return command.SetCommand(CommandText()
.FillStringWithVariables(s =>
{
return Parameters.TryGetValue(s).NullOr(p =>
{
return ((IDbDataParameter)command
.AddParameter(this, p.Schema, p.Value)
.Parameters[command.Parameters.Count - 1])
.ParameterName;
}, s);
}));
}
internal DynamicSchemaColumn? GetColumnFromSchema(string colName, DynamicTypeMap mapper = null, string table = null)
{
// This is tricky and will not always work unfortunetly.
if (colName.ContainsAny(StringExtensions.InvalidMultipartMemberChars))
return null;
// First we need to get real column name and it's owner if exist.
var parts = colName.Split('.')
.Select(c => Database.StripName(c))
.ToArray();
var columnName = parts.Last();
// Get table name from mapper
string tableName = table;
if (string.IsNullOrEmpty(tableName))
{
tableName = (mapper != null && mapper.Table != null) ? mapper.Table.Name : string.Empty;
if (parts.Length > 1 && string.IsNullOrEmpty(tableName))
{
// OK, we have a multi part identifier, that's good, we can get table name
tableName = string.Join(".", parts.Take(parts.Length - 1));
}
}
// Try to get table info from cache
var tableInfo = !string.IsNullOrEmpty(tableName) ?
Tables.FirstOrDefault(x => !string.IsNullOrEmpty(x.Alias) && x.Alias.ToLower() == tableName) ??
Tables.FirstOrDefault(x => x.Name.ToLower() == tableName.ToLower()) ?? Tables.FirstOrDefault() :
this is DynamicModifyBuilder ? Tables.FirstOrDefault() : null;
// Try to get column from schema
if (tableInfo != null && tableInfo.Schema != null)
return tableInfo.Schema.TryGetNullable(columnName.ToLower());
// Well, we failed to find a column
return null;
}
#region Parser
/// <summary>Parses the arbitrary object given and translates it into a string with the appropriate
/// syntax for the database this parser is specific to.</summary>
/// <param name="node">The object to parse and translate. It can be any arbitrary object, including null values (if
/// permitted) and dynamic lambda expressions.</param>
/// <param name="pars">If not null, the parameters' list where to store the parameters extracted by the parsing.</param>
/// <param name="rawstr">If true, literal (raw) string are allowed. If false and the node is a literal then, as a
/// security measure, an exception is thrown.</param>
/// <param name="nulls">True to accept null values and translate them into the appropriate syntax accepted by the
/// database. If false and the value is null, then an exception is thrown.</param>
/// <param name="decorate">If set to <c>true</c> decorate element.</param>
/// <param name="isMultiPart">If set parse argument as alias. This is workaround for AS method.</param>
/// <param name="columnSchema">This parameter is used to determine type of parameter used in query.</param>
/// <returns>A string containing the result of the parsing, along with the parameters extracted in the
/// <see cref="pars" /> instance if such is given.</returns>
/// <exception cref="System.ArgumentNullException">Null nodes are not accepted.</exception>
internal virtual string Parse(object node, IDictionary<string, IParameter> pars = null, bool rawstr = false, bool nulls = false, bool decorate = true, bool isMultiPart = true, DynamicSchemaColumn? columnSchema = null)
{
// Null nodes are accepted or not depending upon the "nulls" flag...
if (node == null)
{
if (!nulls)
throw new ArgumentNullException("node", "Null nodes are not accepted.");
return Dispatch(node, pars, decorate);
}
// Nodes that are strings are parametrized or not depending the "rawstr" flag...
if (node is string)
{
if (rawstr) return (string)node;
else return Dispatch(node, pars, decorate);
}
// If node is a delegate, parse it to create the logical tree...
if (node is Delegate)
{
node = DynamicParser.Parse((Delegate)node).Result;
return Parse(node, pars, rawstr, decorate: decorate, columnSchema: columnSchema); // Intercept containers as in (x => "string")
}
return Dispatch(node, pars, decorate, isMultiPart, columnSchema);
}
private string Dispatch(object node, IDictionary<string, IParameter> pars = null, bool decorate = true, bool isMultiPart = true, DynamicSchemaColumn? columnSchema = null)
{
if (node != null)
{
if (node is DynamicQueryBuilder) return ParseCommand((DynamicQueryBuilder)node, pars);
else if (node is DynamicParser.Node.Argument) return ParseArgument((DynamicParser.Node.Argument)node, isMultiPart);
else if (node is DynamicParser.Node.GetMember) return ParseGetMember((DynamicParser.Node.GetMember)node, pars, decorate, isMultiPart, columnSchema);
else if (node is DynamicParser.Node.SetMember) return ParseSetMember((DynamicParser.Node.SetMember)node, pars, decorate, isMultiPart, columnSchema);
else if (node is DynamicParser.Node.Unary) return ParseUnary((DynamicParser.Node.Unary)node, pars);
else if (node is DynamicParser.Node.Binary) return ParseBinary((DynamicParser.Node.Binary)node, pars);
else if (node is DynamicParser.Node.Method) return ParseMethod((DynamicParser.Node.Method)node, pars);
else if (node is DynamicParser.Node.Invoke) return ParseInvoke((DynamicParser.Node.Invoke)node, pars);
else if (node is DynamicParser.Node.Convert) return ParseConvert((DynamicParser.Node.Convert)node, pars);
}
// All other cases are considered constant parameters...
return ParseConstant(node, pars, columnSchema);
}
protected virtual string ParseCommand(DynamicQueryBuilder node, IDictionary<string, IParameter> pars = null)
{
// Getting the command's text...
string str = node.CommandText(); // Avoiding spurious "OUTPUT XXX" statements
// If there are parameters to transform, but cannot store them, it is an error
if (node.Parameters.Count != 0 && pars == null)
throw new InvalidOperationException(string.Format("The parameters in this command '{0}' cannot be added to a null collection.", node.Parameters));
// Copy parameters to new comand
foreach (var parameter in node.Parameters)
pars.Add(parameter.Key, parameter.Value);
return string.Format("({0})", str);
}
protected virtual string ParseArgument(DynamicParser.Node.Argument node, bool isMultiPart = true, bool isOwner = false)
{
if (!string.IsNullOrEmpty(node.Name) && (isOwner || (isMultiPart && IsTableAlias(node.Name))))
return node.Name;
return null;
}
protected virtual string ParseGetMember(DynamicParser.Node.GetMember node, IDictionary<string, IParameter> pars = null, bool decorate = true, bool isMultiPart = true, DynamicSchemaColumn? columnSchema = null)
{
if (node.Host is DynamicParser.Node.Argument && IsTableAlias(node.Name))
{
decorate = false;
isMultiPart = false;
}
// This hack allows to use argument as alias, but when it is not nesesary use other column.
// Let say we hace a table Users with alias usr, and we join to table with alias ua which also has a column Users
// This allow use of usr => usr.ua.Users to result in ua."Users" instead of "Users" or usr."ua"."Users", se tests for examples.
string parent = null;
if (node.Host != null)
{
if (isMultiPart && node.Host is DynamicParser.Node.GetMember && IsTable(node.Host.Name, null))
{
if (node.Host.Host != null && node.Host.Host is DynamicParser.Node.GetMember && IsTable(node.Host.Name, node.Host.Host.Name))
parent = string.Format("{0}.{1}", Parse(node.Host.Host, pars, isMultiPart: false), Parse(node.Host, pars, isMultiPart: false));
else
parent = Parse(node.Host, pars, isMultiPart: false);
}
else if (isMultiPart)
parent = Parse(node.Host, pars, isMultiPart: isMultiPart);
}
////string parent = node.Host == null || !isMultiPart ? null : Parse(node.Host, pars, isMultiPart: !IsTable(node.Name, node.Host.Name));
string name = parent == null ?
decorate ? Database.DecorateName(node.Name) : node.Name :
string.Format("{0}.{1}", parent, decorate ? Database.DecorateName(node.Name) : node.Name);
columnSchema = GetColumnFromSchema(name);
return name;
}
protected virtual string ParseSetMember(DynamicParser.Node.SetMember node, IDictionary<string, IParameter> pars = null, bool decorate = true, bool isMultiPart = true, DynamicSchemaColumn? columnSchema = null)
{
if (node.Host is DynamicParser.Node.Argument && IsTableAlias(node.Name))
{
decorate = false;
isMultiPart = false;
}
string parent = null;
if (node.Host != null)
{
if (isMultiPart && node.Host is DynamicParser.Node.GetMember && IsTable(node.Host.Name, null))
{
if (node.Host.Host != null && node.Host.Host is DynamicParser.Node.GetMember && IsTable(node.Name, node.Host.Name))
parent = string.Format("{0}.{1}", Parse(node.Host.Host, pars, isMultiPart: false), Parse(node.Host, pars, isMultiPart: false));
else
parent = Parse(node.Host, pars, isMultiPart: false);
}
else if (isMultiPart)
parent = Parse(node.Host, pars, isMultiPart: isMultiPart);
}
////string parent = node.Host == null || !isMultiPart ? null : Parse(node.Host, pars, isMultiPart: !IsTable(node.Name, node.Host.Name));
string name = parent == null ?
decorate ? Database.DecorateName(node.Name) : node.Name :
string.Format("{0}.{1}", parent, decorate ? Database.DecorateName(node.Name) : node.Name);
columnSchema = GetColumnFromSchema(name);
string value = Parse(node.Value, pars, nulls: true, columnSchema: columnSchema);
return string.Format("{0} = ({1})", name, value);
}
protected virtual string ParseUnary(DynamicParser.Node.Unary node, IDictionary<string, IParameter> pars = null)
{
switch (node.Operation)
{
// Artifacts from the DynamicParser class that are not usefull here...
case ExpressionType.IsFalse:
case ExpressionType.IsTrue: return Parse(node.Target, pars);
// Unary supported operations...
case ExpressionType.Not: return string.Format("(NOT {0})", Parse(node.Target, pars));
case ExpressionType.Negate: return string.Format("!({0})", Parse(node.Target, pars));
}
throw new ArgumentException("Not supported unary operation: " + node);
}
protected virtual string ParseBinary(DynamicParser.Node.Binary node, IDictionary<string, IParameter> pars = null)
{
string op = string.Empty;
switch (node.Operation)
{
// Arithmetic binary operations...
case ExpressionType.Add: op = "+"; break;
case ExpressionType.Subtract: op = "-"; break;
case ExpressionType.Multiply: op = "*"; break;
case ExpressionType.Divide: op = "/"; break;
case ExpressionType.Modulo: op = "%"; break;
case ExpressionType.Power: op = "^"; break;
case ExpressionType.And: op = "AND"; break;
case ExpressionType.Or: op = "OR"; break;
// Logical comparisons...
case ExpressionType.GreaterThan: op = ">"; break;
case ExpressionType.GreaterThanOrEqual: op = ">="; break;
case ExpressionType.LessThan: op = "<"; break;
case ExpressionType.LessThanOrEqual: op = "<="; break;
// Comparisons against 'NULL' require the 'IS' or 'IS NOT' operator instead the numeric ones...
case ExpressionType.Equal: op = node.Right == null && !VirtualMode ? "IS" : "="; break;
case ExpressionType.NotEqual: op = node.Right == null && !VirtualMode ? "IS NOT" : "<>"; break;
default: throw new ArgumentException("Not supported operator: '" + node.Operation);
}
DynamicSchemaColumn? columnSchema = null;
string left = Parse(node.Left, pars, columnSchema: columnSchema); // Not nulls: left is assumed to be an object
string right = Parse(node.Right, pars, nulls: true, columnSchema: columnSchema);
return string.Format("({0} {1} {2})", left, op, right);
}
protected virtual string ParseMethod(DynamicParser.Node.Method node, IDictionary<string, IParameter> pars = null)
{
string method = node.Name.ToUpper();
string parent = node.Host == null ? null : Parse(node.Host, pars: pars);
string item = null;
// Root-level methods...
if (node.Host == null)
{
switch (method)
{
case "NOT":
if (node.Arguments == null || node.Arguments.Length != 1) throw new ArgumentNullException("NOT method expects one argument: " + node.Arguments.Sketch());
item = Parse(node.Arguments[0], pars: pars);
return string.Format("(NOT {0})", item);
}
}
// Column-level methods...
if (node.Host != null)
{
switch (method)
{
case "BETWEEN":
{
if (node.Arguments == null || node.Arguments.Length == 0)
throw new ArgumentException("BETWEEN method expects at least one argument: " + node.Arguments.Sketch());
if (node.Arguments.Length > 2)
throw new ArgumentException("BETWEEN method expects at most two arguments: " + node.Arguments.Sketch());
var arguments = node.Arguments;
if (arguments.Length == 1 && (arguments[0] is IEnumerable<object> || arguments[0] is Array) && !(arguments[0] is byte[]))
{
var vals = arguments[0] as IEnumerable<object>;
if (vals == null && arguments[0] is Array)
vals = ((Array)arguments[0]).Cast<object>() as IEnumerable<object>;
if (vals != null)
arguments = vals.ToArray();
else
throw new ArgumentException("BETWEEN method expects single argument to be enumerable of exactly two elements: " + node.Arguments.Sketch());
}
return string.Format("{0} BETWEEN {1} AND {2}", parent, Parse(arguments[0], pars: pars), Parse(arguments[1], pars: pars));
}
case "IN":
{
if (node.Arguments == null || node.Arguments.Length == 0)
throw new ArgumentException("IN method expects at least one argument: " + node.Arguments.Sketch());
bool firstParam = true;
StringBuilder sbin = new StringBuilder();
foreach (var arg in node.Arguments)
{
if (!firstParam)
sbin.Append(", ");
if ((arg is IEnumerable<object> || arg is Array) && !(arg is byte[]))
{
var vals = arg as IEnumerable<object>;
if (vals == null && arg is Array)
vals = ((Array)arg).Cast<object>() as IEnumerable<object>;
if (vals != null)
foreach (var val in vals)
{
if (!firstParam)
sbin.Append(", ");
else
firstParam = false;
sbin.Append(Parse(val, pars: pars));
}
else
sbin.Append(Parse(arg, pars: pars));
}
else
sbin.Append(Parse(arg, pars: pars));
firstParam = false;
}
return string.Format("{0} IN({1})", parent, sbin.ToString());
}
case "LIKE":
if (node.Arguments == null || node.Arguments.Length != 1)
throw new ArgumentException("LIKE method expects one argument: " + node.Arguments.Sketch());
return string.Format("{0} LIKE {1}", parent, Parse(node.Arguments[0], pars: pars));
case "NOTLIKE":
if (node.Arguments == null || node.Arguments.Length != 1)
throw new ArgumentException("NOT LIKE method expects one argument: " + node.Arguments.Sketch());
return string.Format("{0} NOT LIKE {1}", parent, Parse(node.Arguments[0], pars: pars));
case "AS":
if (node.Arguments == null || node.Arguments.Length != 1)
throw new ArgumentException("AS method expects one argument: " + node.Arguments.Sketch());
item = Parse(node.Arguments[0], pars: null, rawstr: true, isMultiPart: false); // pars=null to avoid to parameterize aliases
item = item.Validated("Alias"); // Intercepting null and empty aliases
return string.Format("{0} AS {1}", parent, item);
case "COUNT":
if (node.Arguments != null && node.Arguments.Length > 1)
throw new ArgumentException("COUNT method expects one or none argument: " + node.Arguments.Sketch());
if (node.Arguments == null || node.Arguments.Length == 0)
return "COUNT(*)";
return string.Format("COUNT({0})", Parse(node.Arguments[0], pars: Parameters, nulls: true));
}
}
// Default case: parsing the method's name along with its arguments...
method = parent == null ? node.Name : string.Format("{0}.{1}", parent, node.Name);
StringBuilder sb = new StringBuilder();
sb.AppendFormat("{0}(", method);
if (node.Arguments != null && node.Arguments.Length != 0)
{
bool first = true;
foreach (object argument in node.Arguments)
{
if (!first)
sb.Append(", ");
else
first = false;
sb.Append(Parse(argument, pars, nulls: true)); // We don't accept raw strings here!!!
}
}
sb.Append(")");
return sb.ToString();
}
protected virtual string ParseInvoke(DynamicParser.Node.Invoke node, IDictionary<string, IParameter> pars = null)
{
// This is used as an especial syntax to merely concatenate its arguments. It is used as a way to extend the supported syntax without the need of treating all the possible cases...
if (node.Arguments == null || node.Arguments.Length == 0)
return string.Empty;
StringBuilder sb = new StringBuilder();
foreach (object arg in node.Arguments)
{
if (arg is string)
sb.Append((string)arg);
else
sb.Append(Parse(arg, pars, rawstr: true, nulls: true));
}
return sb.ToString();
}
protected virtual string ParseConvert(DynamicParser.Node.Convert node, IDictionary<string, IParameter> pars = null)
{
// The cast mechanism is left for the specific database implementation, that should override this method
// as needed...
string r = Parse(node.Target, pars);
return r;
}
protected virtual string ParseConstant(object node, IDictionary<string, IParameter> pars = null, DynamicSchemaColumn? columnSchema = null)
{
if (node == null) return ParseNull();
if (pars != null)
{
// If we have a list of parameters to store it, let's parametrize it
var name = Guid.NewGuid().ToString();
var par = new Parameter()
{
Name = string.Format("[${0}]", name),
Value = node,
Virtual = VirtualMode,
Schema = columnSchema,
};
pars.Add(name, par);
return par.Name;
}
return node.ToString(); // Last resort case
}
protected virtual string ParseNull()
{
return "NULL"; // Override if needed
}
internal string FixObjectName(string main, bool onlyColumn = false)
{
if (main.IndexOf("(") > 0 && main.IndexOf(")") > 0)
return main.FillStringWithVariables(f => string.Format("({0})", FixObjectNamePrivate(f, onlyColumn)), "(", ")");
else
return FixObjectNamePrivate(main, onlyColumn);
}
private string FixObjectNamePrivate(string f, bool onlyColumn = false)
{
var objects = f.Split('.')
.Select(x => Database.StripName(x));
if (onlyColumn || objects.Count() == 1)
f = Database.DecorateName(objects.Last());
else if (!IsTableAlias(objects.First()))
f = string.Join(".", objects.Select(o => Database.DecorateName(o)));
else
f = string.Format("{0}.{1}", objects.First(), string.Join(".", objects.Skip(1).Select(o => Database.DecorateName(o))));
return f;
}
#endregion Parser
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,324 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Some of methods in this code file is based on Kerosene ORM solution
* for parsing dynamic lambda expressions by Moisés Barba Cebeira
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Linq;
using DynamORM.Builders.Extensions;
using DynamORM.Helpers.Dynamics;
using DynamORM.Mapper;
namespace DynamORM.Builders.Implementation
{
/// <summary>Update query builder.</summary>
internal class DynamicUpdateQueryBuilder : DynamicModifyBuilder, IDynamicUpdateQueryBuilder, DynamicQueryBuilder.IQueryWithWhere
{
private string _columns;
internal DynamicUpdateQueryBuilder(DynamicDatabase db)
: base(db)
{
}
public DynamicUpdateQueryBuilder(DynamicDatabase db, string tableName)
: base(db, tableName)
{
}
/// <summary>Generates the text this command will execute against the underlying database.</summary>
/// <returns>The text to execute against the underlying database.</returns>
/// <remarks>This method must be override by derived classes.</remarks>
public override string CommandText()
{
var info = Tables.Single();
return string.Format("UPDATE {0}{1} SET {2} WHERE {3}",
string.IsNullOrEmpty(info.Owner) ? string.Empty : string.Format("{0}.", Database.DecorateName(info.Owner)),
Database.DecorateName(info.Name), _columns, WhereCondition);
}
#region Update
/// <summary>Add update value or where condition using schema.</summary>
/// <param name="column">Update or where column name and value.</param>
/// <returns>Builder instance.</returns>
public virtual IDynamicUpdateQueryBuilder Update(DynamicColumn column)
{
DynamicSchemaColumn? col = column.Schema ?? GetColumnFromSchema(column.ColumnName);
if (!col.HasValue && SupportSchema)
throw new InvalidOperationException(string.Format("Column '{0}' not found in schema, can't use universal approach.", column));
if (col.HasValue && col.Value.IsKey)
Where(column);
else
Values(column.ColumnName, column.Value);
return this;
}
/// <summary>Add update value or where condition using schema.</summary>
/// <param name="column">Update or where column name.</param>
/// <param name="value">Column value.</param>
/// <returns>Builder instance.</returns>
public virtual IDynamicUpdateQueryBuilder Update(string column, object value)
{
DynamicSchemaColumn? col = GetColumnFromSchema(column);
if (!col.HasValue && SupportSchema)
throw new InvalidOperationException(string.Format("Column '{0}' not found in schema, can't use universal approach.", column));
if (col.HasValue && col.Value.IsKey)
Where(column, value);
else
Values(column, value);
return this;
}
/// <summary>Add update values and where condition columns using schema.</summary>
/// <param name="conditions">Set values or conditions as properties and values of an object.</param>
/// <returns>Builder instance.</returns>
public virtual IDynamicUpdateQueryBuilder Update(object conditions)
{
if (conditions is DynamicColumn)
return Update((DynamicColumn)conditions);
var dict = conditions.ToDictionary();
var mapper = DynamicMapperCache.GetMapper(conditions.GetType());
foreach (var con in dict)
{
if (mapper.Ignored.Contains(con.Key))
continue;
string colName = mapper != null ? mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key : con.Key;
DynamicSchemaColumn? col = GetColumnFromSchema(colName);
if (!col.HasValue && SupportSchema)
throw new InvalidOperationException(string.Format("Column '{0}' not found in schema, can't use universal approach.", colName));
if (col.HasValue)
{
colName = col.Value.Name;
if (col.Value.IsKey)
{
Where(colName, con.Value);
continue;
}
}
Values(colName, con.Value);
}
return this;
}
#endregion Update
#region Values
/// <summary>
/// Specifies the columns to update using the dynamic lambda expressions given. Each expression correspond to one
/// column, and can:
/// <para>- Resolve to a string, in this case a '=' must appear in the string.</para>
/// <para>- Resolve to a expression with the form: 'x =&gt; x.Column = Value'.</para>
/// </summary>
/// <param name="func">The specifications.</param>
/// <returns>This instance to permit chaining.</returns>
public virtual IDynamicUpdateQueryBuilder Values(params Func<dynamic, object>[] func)
{
if (func == null)
throw new ArgumentNullException("Array of specifications cannot be null.");
int index = -1;
foreach (var f in func)
{
index++;
if (f == null)
throw new ArgumentNullException(string.Format("Specification #{0} cannot be null.", index));
var result = DynamicParser.Parse(f).Result;
if (result == null)
throw new ArgumentException(string.Format("Specification #{0} resolves to null.", index));
string main = null;
string value = null;
string str = null;
// When 'x => x.Table.Column = value' or 'x => x.Column = value'...
if (result is DynamicParser.Node.SetMember)
{
var node = (DynamicParser.Node.SetMember)result;
DynamicSchemaColumn? col = GetColumnFromSchema(node.Name);
main = Database.DecorateName(node.Name);
value = Parse(node.Value, pars: Parameters, nulls: true, columnSchema: col);
str = string.Format("{0} = {1}", main, value);
_columns = _columns == null ? str : string.Format("{0}, {1}", _columns, str);
continue;
}
else if (!(result is DynamicParser.Node) && !result.GetType().IsValueType)
{
Values(result);
continue;
}
// Other specifications are considered invalid...
var err = string.Format("Specification '{0}' is invalid.", result);
str = Parse(result);
if (str.Contains("=")) err += " May have you used a '==' instead of a '=' operator?";
throw new ArgumentException(err);
}
return this;
}
/// <summary>Add insert fields.</summary>
/// <param name="column">Insert column and value.</param>
/// <returns>Builder instance.</returns>
public virtual IDynamicUpdateQueryBuilder Values(DynamicColumn column)
{
DynamicSchemaColumn? col = column.Schema ?? GetColumnFromSchema(column.ColumnName);
string main = FixObjectName(column.ColumnName, onlyColumn: true);
string value = Parse(column.Value, pars: Parameters, nulls: true, columnSchema: col);
var str = string.Format("{0} = {1}", main, value);
_columns = _columns == null ? str : string.Format("{0}, {1}", _columns, str);
return this;
}
/// <summary>Add insert fields.</summary>
/// <param name="column">Insert column.</param>
/// <param name="value">Insert value.</param>
/// <returns>Builder instance.</returns>
public virtual IDynamicUpdateQueryBuilder Values(string column, object value)
{
if (value is DynamicColumn)
{
var v = (DynamicColumn)value;
if (string.IsNullOrEmpty(v.ColumnName))
v.ColumnName = column;
return Values(v);
}
return Values(new DynamicColumn
{
ColumnName = column,
Value = value,
});
}
/// <summary>Add insert fields.</summary>
/// <param name="o">Set insert value as properties and values of an object.</param>
/// <returns>Builder instance.</returns>
public virtual IDynamicUpdateQueryBuilder Values(object o)
{
var dict = o.ToDictionary();
var mapper = DynamicMapperCache.GetMapper(o.GetType());
if (mapper != null)
{
foreach (var con in dict)
if (!mapper.Ignored.Contains(con.Key))
Values(mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key, con.Value);
}
else
foreach (var con in dict)
Values(con.Key, con.Value);
return this;
}
#endregion Values
#region Where
/// <summary>
/// Adds to the 'Where' clause the contents obtained from parsing the dynamic lambda expression given. The condition
/// is parsed to the appropriate syntax, where the specific customs virtual methods supported by the parser are used
/// as needed.
/// <para>- If several Where() methods are chained their contents are, by default, concatenated with an 'AND' operator.</para>
/// <para>- The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in:
/// 'Where( x => x.Or( condition ) )'.</para>
/// </summary>
/// <param name="func">The specification.</param>
/// <returns>This instance to permit chaining.</returns>
public virtual IDynamicUpdateQueryBuilder Where(Func<dynamic, object> func)
{
return this.InternalWhere(func);
}
/// <summary>Add where condition.</summary>
/// <param name="column">Condition column with operator and value.</param>
/// <returns>Builder instance.</returns>
public virtual IDynamicUpdateQueryBuilder Where(DynamicColumn column)
{
return this.InternalWhere(column);
}
/// <summary>Add where condition.</summary>
/// <param name="column">Condition column.</param>
/// <param name="op">Condition operator.</param>
/// <param name="value">Condition value.</param>
/// <returns>Builder instance.</returns>
public virtual IDynamicUpdateQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value)
{
return this.InternalWhere(column, op, value);
}
/// <summary>Add where condition.</summary>
/// <param name="column">Condition column.</param>
/// <param name="value">Condition value.</param>
/// <returns>Builder instance.</returns>
public virtual IDynamicUpdateQueryBuilder Where(string column, object value)
{
return this.InternalWhere(column, value);
}
/// <summary>Add where condition.</summary>
/// <param name="conditions">Set conditions as properties and values of an object.</param>
/// <param name="schema">If <c>true</c> use schema to determine key columns and ignore those which
/// aren't keys.</param>
/// <returns>Builder instance.</returns>
public virtual IDynamicUpdateQueryBuilder Where(object conditions, bool schema = false)
{
return this.InternalWhere(conditions, schema);
}
#endregion Where
}
}