using Ant.Core; using Ant.Data; using Ant.DbExpressions; using Ant.ORM; using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Reflection; using System.Text; namespace Ant.SQLite { partial class SqlGenerator : DbExpressionVisitor { public const string ParameterPrefix = "@P_"; internal ISqlBuilder _sqlBuilder = new SqlBuilder(); List _parameters = new List(); static readonly Dictionary> MethodHandlers = InitMethodHandlers(); static readonly Dictionary> AggregateHandlers = InitAggregateHandlers(); static readonly Dictionary> BinaryWithMethodHandlers = InitBinaryWithMethodHandlers(); static readonly Dictionary CastTypeMap = null; static readonly List CacheParameterNames = null; static SqlGenerator() { Dictionary castTypeMap = new Dictionary(); castTypeMap.Add(typeof(string), "TEXT"); castTypeMap.Add(typeof(byte), "INTEGER"); castTypeMap.Add(typeof(Int16), "INTEGER"); castTypeMap.Add(typeof(int), "INTEGER"); castTypeMap.Add(typeof(long), "INTEGER"); //castTypeMap.Add(typeof(decimal), "DECIMAL(19,0)");//I think this will be a bug. castTypeMap.Add(typeof(double), "REAL"); castTypeMap.Add(typeof(float), "REAL"); castTypeMap.Add(typeof(bool), "INTEGER"); //castTypeMap.Add(typeof(DateTime), "DATETIME"); //castTypeMap.Add(typeof(Guid), "UNIQUEIDENTIFIER"); CastTypeMap = Utils.Clone(castTypeMap); int cacheParameterNameCount = 2 * 12; List cacheParameterNames = new List(cacheParameterNameCount); for (int i = 0; i < cacheParameterNameCount; i++) { string paramName = ParameterPrefix + i.ToString(); cacheParameterNames.Add(paramName); } CacheParameterNames = cacheParameterNames; } public ISqlBuilder SqlBuilder { get { return this._sqlBuilder; } } public List Parameters { get { return this._parameters; } } public static SqlGenerator CreateInstance() { return new SqlGenerator(); } public override DbExpression Visit(DbEqualExpression exp) { DbExpression left = exp.Left; DbExpression right = exp.Right; left = DbExpressionExtensions.ParseDbExpression(left); right = DbExpressionExtensions.ParseDbExpression(right); //明确 left right 其中一边一定为 null if (DbExpressionExtensions.AffirmExpressionRetValueIsNull(right)) { left.Accept(this); this._sqlBuilder.Append(" IS NULL"); return exp; } if (DbExpressionExtensions.AffirmExpressionRetValueIsNull(left)) { right.Accept(this); this._sqlBuilder.Append(" IS NULL"); return exp; } left.Accept(this); this._sqlBuilder.Append(" = "); right.Accept(this); return exp; } public override DbExpression Visit(DbNotEqualExpression exp) { DbExpression left = exp.Left; DbExpression right = exp.Right; left = DbExpressionExtensions.ParseDbExpression(left); right = DbExpressionExtensions.ParseDbExpression(right); //明确 left right 其中一边一定为 null if (DbExpressionExtensions.AffirmExpressionRetValueIsNull(right)) { left.Accept(this); this._sqlBuilder.Append(" IS NOT NULL"); return exp; } if (DbExpressionExtensions.AffirmExpressionRetValueIsNull(left)) { right.Accept(this); this._sqlBuilder.Append(" IS NOT NULL"); return exp; } left.Accept(this); this._sqlBuilder.Append(" <> "); right.Accept(this); return exp; } public override DbExpression Visit(DbNotExpression exp) { this._sqlBuilder.Append("NOT "); this._sqlBuilder.Append("("); exp.Operand.Accept(this); this._sqlBuilder.Append(")"); return exp; } public override DbExpression Visit(DbAndExpression exp) { Stack operands = GatherBinaryExpressionOperand(exp); this.ConcatOperands(operands, " & "); return exp; } public override DbExpression Visit(DbAndAlsoExpression exp) { Stack operands = GatherBinaryExpressionOperand(exp); this.ConcatOperands(operands, " AND "); return exp; } public override DbExpression Visit(DbOrExpression exp) { Stack operands = GatherBinaryExpressionOperand(exp); this.ConcatOperands(operands, " | "); return exp; } public override DbExpression Visit(DbOrElseExpression exp) { Stack operands = GatherBinaryExpressionOperand(exp); this.ConcatOperands(operands, " OR "); return exp; } // + public override DbExpression Visit(DbAddExpression exp) { MethodInfo method = exp.Method; if (method != null) { Action handler; if (BinaryWithMethodHandlers.TryGetValue(method, out handler)) { handler(exp, this); return exp; } throw UtilExceptions.NotSupportedMethod(exp.Method); } Stack operands = GatherBinaryExpressionOperand(exp); this.ConcatOperands(operands, " + "); return exp; } // - public override DbExpression Visit(DbSubtractExpression exp) { Stack operands = GatherBinaryExpressionOperand(exp); this.ConcatOperands(operands, " - "); return exp; } // * public override DbExpression Visit(DbMultiplyExpression exp) { Stack operands = GatherBinaryExpressionOperand(exp); this.ConcatOperands(operands, " * "); return exp; } // / public override DbExpression Visit(DbDivideExpression exp) { Stack operands = GatherBinaryExpressionOperand(exp); this.ConcatOperands(operands, " / "); return exp; } // < public override DbExpression Visit(DbLessThanExpression exp) { exp.Left.Accept(this); this._sqlBuilder.Append(" < "); exp.Right.Accept(this); return exp; } // <= public override DbExpression Visit(DbLessThanOrEqualExpression exp) { exp.Left.Accept(this); this._sqlBuilder.Append(" <= "); exp.Right.Accept(this); return exp; } // > public override DbExpression Visit(DbGreaterThanExpression exp) { exp.Left.Accept(this); this._sqlBuilder.Append(" > "); exp.Right.Accept(this); return exp; } // >= public override DbExpression Visit(DbGreaterThanOrEqualExpression exp) { exp.Left.Accept(this); this._sqlBuilder.Append(" >= "); exp.Right.Accept(this); return exp; } public override DbExpression Visit(DbAggregateExpression exp) { Action aggregateHandler; if (!AggregateHandlers.TryGetValue(exp.Method.Name, out aggregateHandler)) { throw UtilExceptions.NotSupportedMethod(exp.Method); } aggregateHandler(exp, this); return exp; } public override DbExpression Visit(DbTableExpression exp) { this.QuoteName(exp.Table.Name); return exp; } public override DbExpression Visit(DbColumnAccessExpression exp) { this.QuoteName(exp.Table.Name); this._sqlBuilder.Append("."); this.QuoteName(exp.Column.Name); return exp; } public override DbExpression Visit(DbFromTableExpression exp) { this.AppendTableSegment(exp.Table); this.VisitDbJoinTableExpressions(exp.JoinTables); return exp; } public override DbExpression Visit(DbJoinTableExpression exp) { DbJoinTableExpression joinTablePart = exp; string joinString = null; if (joinTablePart.JoinType == JoinType.InnerJoin) { joinString = " INNER JOIN "; } else if (joinTablePart.JoinType == JoinType.LeftJoin) { joinString = " LEFT JOIN "; } else throw new NotSupportedException("JoinType: " + joinTablePart.JoinType); this._sqlBuilder.Append(joinString); this.AppendTableSegment(joinTablePart.Table); this._sqlBuilder.Append(" ON "); joinTablePart.Condition.Accept(this); this.VisitDbJoinTableExpressions(joinTablePart.JoinTables); return exp; } public override DbExpression Visit(DbSubQueryExpression exp) { this._sqlBuilder.Append("("); exp.SqlQuery.Accept(this); this._sqlBuilder.Append(")"); return exp; } public override DbExpression Visit(DbSqlQueryExpression exp) { this.BuildGeneralSql(exp); return exp; } public override DbExpression Visit(DbInsertExpression exp) { this._sqlBuilder.Append("INSERT INTO "); this.QuoteName(exp.Table.Name); this._sqlBuilder.Append("("); bool first = true; foreach (var item in exp.InsertColumns) { if (first) first = false; else { this._sqlBuilder.Append(","); } this.QuoteName(item.Key.Name); } this._sqlBuilder.Append(")"); this._sqlBuilder.Append(" VALUES("); first = true; foreach (var item in exp.InsertColumns) { if (first) first = false; else { this._sqlBuilder.Append(","); } item.Value.Accept(this); } this._sqlBuilder.Append(")"); return exp; } public override DbExpression Visit(DbUpdateExpression exp) { this._sqlBuilder.Append("UPDATE "); this.QuoteName(exp.Table.Name); this._sqlBuilder.Append(" SET "); bool first = true; foreach (var item in exp.UpdateColumns) { if (first) first = false; else this._sqlBuilder.Append(","); this.QuoteName(item.Key.Name); this._sqlBuilder.Append("="); item.Value.Accept(this); } this.BuildWhereState(exp.Condition); return exp; } public override DbExpression Visit(DbDeleteExpression exp) { this._sqlBuilder.Append("DELETE FROM "); this.QuoteName(exp.Table.Name); this.BuildWhereState(exp.Condition); return exp; } public override DbExpression Visit(DbCaseWhenExpression exp) { this._sqlBuilder.Append("CASE"); foreach (var whenThen in exp.WhenThenPairs) { this._sqlBuilder.Append(" WHEN "); whenThen.When.Accept(this); this._sqlBuilder.Append(" THEN "); whenThen.Then.Accept(this); } this._sqlBuilder.Append(" ELSE "); exp.Else.Accept(this); this._sqlBuilder.Append(" END"); return exp; } public override DbExpression Visit(DbConvertExpression exp) { DbExpression stripedExp = DbExpressionHelper.StripInvalidConvert(exp); if (stripedExp.NodeType != DbExpressionType.Convert) { stripedExp.Accept(this); return exp; } exp = (DbConvertExpression)stripedExp; string dbTypeString; if (TryGetCastTargetDbTypeString(exp.Operand.Type, exp.Type, out dbTypeString, false)) { this.BuildCastState(exp.Operand, dbTypeString); } else { Type targetUnType = Utils.GetUnderlyingType(exp.Type); if (targetUnType == UtilConstants.TypeOfDateTime) { /* DATETIME('2016-08-06 09:01:24') */ this._sqlBuilder.Append("DATETIME("); exp.Operand.Accept(this); this._sqlBuilder.Append(")"); } else exp.Operand.Accept(this); } return exp; } public override DbExpression Visit(DbMethodCallExpression exp) { Action methodHandler; if (!MethodHandlers.TryGetValue(exp.Method.Name, out methodHandler)) { throw UtilExceptions.NotSupportedMethod(exp.Method); } methodHandler(exp, this); return exp; } public override DbExpression Visit(DbMemberExpression exp) { MemberInfo member = exp.Member; if (member.DeclaringType == UtilConstants.TypeOfDateTime) { if (member == UtilConstants.PropertyInfo_DateTime_Now) { this._sqlBuilder.Append("DATETIME('NOW','LOCALTIME')"); return exp; } if (member == UtilConstants.PropertyInfo_DateTime_UtcNow) { this._sqlBuilder.Append("DATETIME()"); return exp; } if (member == UtilConstants.PropertyInfo_DateTime_Today) { this._sqlBuilder.Append("DATE('NOW','LOCALTIME')"); return exp; } if (member == UtilConstants.PropertyInfo_DateTime_Date) { this._sqlBuilder.Append("DATETIME(DATE("); exp.Expression.Accept(this); this._sqlBuilder.Append("))"); return exp; } if (IsDbFunction_DATEPART(exp)) { return exp; } } DbParameterExpression newExp; if (DbExpressionExtensions.TryParseToParameterExpression(exp, out newExp)) { return newExp.Accept(this); } if (member.Name == "Length" && member.DeclaringType == UtilConstants.TypeOfString) { this._sqlBuilder.Append("LENGTH("); exp.Expression.Accept(this); this._sqlBuilder.Append(")"); return exp; } else if (member.Name == "Value" && Utils.IsNullable(exp.Expression.Type)) { exp.Expression.Accept(this); return exp; } throw new NotSupportedException(string.Format("'{0}.{1}' is not supported.", member.DeclaringType.FullName, member.Name)); } public override DbExpression Visit(DbConstantExpression exp) { if (exp.Value == null || exp.Value == DBNull.Value) { this._sqlBuilder.Append("NULL"); return exp; } var objType = exp.Value.GetType(); if (objType == UtilConstants.TypeOfBoolean) { this._sqlBuilder.Append(((bool)exp.Value) ? "1" : "0"); return exp; } else if (objType == UtilConstants.TypeOfString) { this._sqlBuilder.Append("'", exp.Value, "'"); return exp; } else if (objType.IsEnum) { this._sqlBuilder.Append(((int)exp.Value).ToString()); return exp; } this._sqlBuilder.Append(exp.Value); return exp; } public override DbExpression Visit(DbParameterExpression exp) { object paramValue = exp.Value; Type paramType = exp.Type; if (paramType.IsEnum) { paramType = UtilConstants.TypeOfInt32; if (paramValue != null) { paramValue = (int)paramValue; } } if (paramValue == null) paramValue = DBNull.Value; DbParam p; if (paramValue == DBNull.Value) { p = this._parameters.Where(a => Utils.AreEqual(a.Value, paramValue) && a.Type == paramType).FirstOrDefault(); } else p = this._parameters.Where(a => Utils.AreEqual(a.Value, paramValue)).FirstOrDefault(); if (p != null) { this._sqlBuilder.Append(p.Name); return exp; } string paramName = GenParameterName(this._parameters.Count); p = DbParam.Create(paramName, paramValue, paramType); if (paramValue.GetType() == UtilConstants.TypeOfString) { if (((string)paramValue).Length <= 4000) p.Size = 4000; } this._parameters.Add(p); this._sqlBuilder.Append(paramName); return exp; } void AppendTableSegment(DbTableSegment seg) { seg.Body.Accept(this); this._sqlBuilder.Append(" AS "); this.QuoteName(seg.Alias); } internal void AppendColumnSegment(DbColumnSegment seg) { seg.Body.Accept(this); this._sqlBuilder.Append(" AS "); this.QuoteName(seg.Alias); } void AppendOrdering(DbOrdering ordering) { if (ordering.OrderType == OrderType.Asc) { ordering.Expression.Accept(this); this._sqlBuilder.Append(" ASC"); return; } else if (ordering.OrderType == OrderType.Desc) { ordering.Expression.Accept(this); this._sqlBuilder.Append(" DESC"); return; } throw new NotSupportedException("OrderType: " + ordering.OrderType); } void VisitDbJoinTableExpressions(List tables) { foreach (var table in tables) { table.Accept(this); } } void BuildGeneralSql(DbSqlQueryExpression exp) { this._sqlBuilder.Append("SELECT "); List columns = exp.ColumnSegments; for (int i = 0; i < columns.Count; i++) { DbColumnSegment column = columns[i]; if (i > 0) this._sqlBuilder.Append(","); this.AppendColumnSegment(column); } this._sqlBuilder.Append(" FROM "); exp.Table.Accept(this); this.BuildWhereState(exp.Condition); this.BuildGroupState(exp); this.BuildOrderState(exp.Orderings); if (exp.SkipCount == null && exp.TakeCount == null) return; int skipCount = exp.SkipCount ?? 0; long takeCount = long.MaxValue; if (exp.TakeCount != null) takeCount = exp.TakeCount.Value; this._sqlBuilder.Append(" LIMIT ", takeCount.ToString(), " OFFSET ", skipCount.ToString()); } void BuildWhereState(DbExpression whereExpression) { if (whereExpression != null) { this._sqlBuilder.Append(" WHERE "); whereExpression.Accept(this); } } void BuildOrderState(List orderings) { if (orderings.Count > 0) { this._sqlBuilder.Append(" ORDER BY "); this.ConcatOrderings(orderings); } } void ConcatOrderings(List orderings) { for (int i = 0; i < orderings.Count; i++) { if (i > 0) { this._sqlBuilder.Append(","); } this.AppendOrdering(orderings[i]); } } void BuildGroupState(DbSqlQueryExpression exp) { var groupSegments = exp.GroupSegments; if (groupSegments.Count == 0) return; this._sqlBuilder.Append(" GROUP BY "); for (int i = 0; i < groupSegments.Count; i++) { if (i > 0) this._sqlBuilder.Append(","); groupSegments[i].Accept(this); } if (exp.HavingCondition != null) { this._sqlBuilder.Append(" HAVING "); exp.HavingCondition.Accept(this); } } void ConcatOperands(IEnumerable operands, string connector) { this._sqlBuilder.Append("("); bool first = true; foreach (DbExpression operand in operands) { if (first) first = false; else this._sqlBuilder.Append(connector); operand.Accept(this); } this._sqlBuilder.Append(")"); return; } void QuoteName(string name) { if (string.IsNullOrEmpty(name)) throw new ArgumentException("name"); this._sqlBuilder.Append("[", name, "]"); } void BuildCastState(DbExpression castExp, string targetDbTypeString) { this._sqlBuilder.Append("CAST("); castExp.Accept(this); this._sqlBuilder.Append(" AS ", targetDbTypeString, ")"); } bool IsDbFunction_DATEPART(DbMemberExpression exp) { MemberInfo member = exp.Member; if (member == UtilConstants.PropertyInfo_DateTime_Year) { DbFunction_DATEPART(this, "Y", exp.Expression); return true; } if (member == UtilConstants.PropertyInfo_DateTime_Month) { DbFunction_DATEPART(this, "m", exp.Expression); return true; } if (member == UtilConstants.PropertyInfo_DateTime_Day) { DbFunction_DATEPART(this, "d", exp.Expression); return true; } if (member == UtilConstants.PropertyInfo_DateTime_Hour) { DbFunction_DATEPART(this, "H", exp.Expression); return true; } if (member == UtilConstants.PropertyInfo_DateTime_Minute) { DbFunction_DATEPART(this, "M", exp.Expression); return true; } if (member == UtilConstants.PropertyInfo_DateTime_Second) { DbFunction_DATEPART(this, "S", exp.Expression); return true; } /* SQLite is not supports MILLISECOND */ if (member == UtilConstants.PropertyInfo_DateTime_DayOfWeek) { DbFunction_DATEPART(this, "w", exp.Expression); return true; } return false; } #region BinaryWithMethodHandlers static Dictionary> InitBinaryWithMethodHandlers() { var binaryWithMethodHandlers = new Dictionary>(); binaryWithMethodHandlers.Add(UtilConstants.MethodInfo_String_Concat_String_String, StringConcat); binaryWithMethodHandlers.Add(UtilConstants.MethodInfo_String_Concat_Object_Object, StringConcat); var ret = Utils.Clone(binaryWithMethodHandlers); return ret; } static void StringConcat(DbBinaryExpression exp, SqlGenerator generator) { MethodInfo method = exp.Method; List operands = new List(); operands.Add(exp.Right); DbExpression left = exp.Left; DbAddExpression e = null; while ((e = (left as DbAddExpression)) != null && (e.Method == UtilConstants.MethodInfo_String_Concat_String_String || e.Method == UtilConstants.MethodInfo_String_Concat_Object_Object)) { operands.Add(e.Right); left = e.Left; } operands.Add(left); DbExpression whenExp = null; List operandExps = new List(operands.Count); for (int i = operands.Count - 1; i >= 0; i--) { DbExpression operand = operands[i]; DbExpression opBody = operand; if (opBody.Type != UtilConstants.TypeOfString) { // 需要 cast type opBody = DbExpression.Convert(opBody, UtilConstants.TypeOfString); } DbExpression equalNullExp = DbExpression.Equal(opBody, UtilConstants.DbConstant_Null_String); if (whenExp == null) whenExp = equalNullExp; else whenExp = DbExpression.AndAlso(whenExp, equalNullExp); DbExpression thenExp = DbConstantExpression.StringEmpty; DbCaseWhenExpression.WhenThenExpressionPair whenThenPair = new DbCaseWhenExpression.WhenThenExpressionPair(equalNullExp, thenExp); List whenThenExps = new List(1); whenThenExps.Add(whenThenPair); DbExpression elseExp = opBody; DbCaseWhenExpression caseWhenExpression = DbExpression.CaseWhen(whenThenExps, elseExp, UtilConstants.TypeOfString); operandExps.Add(caseWhenExpression); } generator._sqlBuilder.Append("CASE", " WHEN "); whenExp.Accept(generator); generator._sqlBuilder.Append(" THEN "); DbConstantExpression.Null.Accept(generator); generator._sqlBuilder.Append(" ELSE "); generator._sqlBuilder.Append("("); for (int i = 0; i < operandExps.Count; i++) { if (i > 0) generator._sqlBuilder.Append(" || "); operandExps[i].Accept(generator); } generator._sqlBuilder.Append(")"); generator._sqlBuilder.Append(" END"); } #endregion } }